/** * Checks if the specified method matches with the method annotation filter pattern * * @param string $className Name of the class to check against - not used here * @param string $methodName Name of the method * @param string $methodDeclaringClassName Name of the class the method was originally declared in * @param mixed $pointcutQueryIdentifier Some identifier for this query - must at least differ from a previous identifier. Used for circular reference detection - not used here * @return boolean TRUE if the class matches, otherwise FALSE */ public function matches($className, $methodName, $methodDeclaringClassName, $pointcutQueryIdentifier) { if ($methodDeclaringClassName === null || !method_exists($methodDeclaringClassName, $methodName)) { return false; } $designatedAnnotations = $this->reflectionService->getMethodAnnotations($methodDeclaringClassName, $methodName, $this->annotation); if ($designatedAnnotations !== [] || $this->annotationValueConstraints === []) { $matches = $designatedAnnotations !== []; } else { // It makes no sense to check property values for an annotation that is used multiple times, we shortcut and check the value against the first annotation found. $firstFoundAnnotation = $designatedAnnotations; $annotationProperties = $this->reflectionService->getClassPropertyNames($this->annotation); foreach ($this->annotationValueConstraints as $propertyName => $expectedValue) { if (!array_key_exists($propertyName, $annotationProperties)) { $this->systemLogger->log('The property "' . $propertyName . '" declared in pointcut does not exist in annotation ' . $this->annotation, LOG_NOTICE); return false; } if ($firstFoundAnnotation->{$propertyName} === $expectedValue) { $matches = true; } else { return false; } } } return $matches; }
/** * Builds the method documentation block for the specified method keeping the vital annotations * * @param string $className Name of the class the method is declared in * @param string $methodName Name of the method to create the parameters code for * @return string $methodDocumentation DocComment for the given method */ protected function buildMethodDocumentation($className, $methodName) { $methodDocumentation = " /**\n * Autogenerated Proxy Method\n"; if ($this->reflectionService->hasMethod($className, $methodName)) { $methodTags = $this->reflectionService->getMethodTagsValues($className, $methodName); $allowedTags = ['param', 'return', 'throws']; foreach ($methodTags as $tag => $values) { if (in_array($tag, $allowedTags)) { if (count($values) === 0) { $methodDocumentation .= ' * @' . $tag . "\n"; } else { foreach ($values as $value) { $methodDocumentation .= ' * @' . $tag . ' ' . $value . "\n"; } } } } $methodAnnotations = $this->reflectionService->getMethodAnnotations($className, $methodName); foreach ($methodAnnotations as $annotation) { $methodDocumentation .= ' * ' . Compiler::renderAnnotation($annotation) . "\n"; } } $methodDocumentation .= " */\n"; return $methodDocumentation; }
/** * Detects and registers any validators for arguments: * - by the data type specified in the param annotations * - additional validators specified in the validate annotations of a method * * @param string $className * @param string $methodName * @param array $methodParameters Optional pre-compiled array of method parameters * @param array $methodValidateAnnotations Optional pre-compiled array of validate annotations (as array) * @return array An Array of ValidatorConjunctions for each method parameters. * @throws Exception\InvalidValidationConfigurationException * @throws Exception\NoSuchValidatorException * @throws Exception\InvalidTypeHintException */ public function buildMethodArgumentsValidatorConjunctions($className, $methodName, array $methodParameters = null, array $methodValidateAnnotations = null) { $validatorConjunctions = []; if ($methodParameters === null) { $methodParameters = $this->reflectionService->getMethodParameters($className, $methodName); } if (count($methodParameters) === 0) { return $validatorConjunctions; } foreach ($methodParameters as $parameterName => $methodParameter) { $validatorConjunction = $this->createValidator(ConjunctionValidator::class); if (!array_key_exists('type', $methodParameter)) { throw new Exception\InvalidTypeHintException('Missing type information, probably no @param annotation for parameter "$' . $parameterName . '" in ' . $className . '->' . $methodName . '()', 1281962564); } if (strpos($methodParameter['type'], '\\') === false) { $typeValidator = $this->createValidator($methodParameter['type']); } else { $typeValidator = null; } if ($typeValidator !== null) { $validatorConjunction->addValidator($typeValidator); } $validatorConjunctions[$parameterName] = $validatorConjunction; } if ($methodValidateAnnotations === null) { $validateAnnotations = $this->reflectionService->getMethodAnnotations($className, $methodName, Flow\Validate::class); $methodValidateAnnotations = array_map(function ($validateAnnotation) { return ['type' => $validateAnnotation->type, 'options' => $validateAnnotation->options, 'argumentName' => $validateAnnotation->argumentName]; }, $validateAnnotations); } foreach ($methodValidateAnnotations as $annotationParameters) { $newValidator = $this->createValidator($annotationParameters['type'], $annotationParameters['options']); if ($newValidator === null) { throw new Exception\NoSuchValidatorException('Invalid validate annotation in ' . $className . '->' . $methodName . '(): Could not resolve class name for validator "' . $annotationParameters['type'] . '".', 1239853109); } if (isset($validatorConjunctions[$annotationParameters['argumentName']])) { $validatorConjunctions[$annotationParameters['argumentName']]->addValidator($newValidator); } elseif (strpos($annotationParameters['argumentName'], '.') !== false) { $objectPath = explode('.', $annotationParameters['argumentName']); $argumentName = array_shift($objectPath); $validatorConjunctions[$argumentName]->addValidator($this->buildSubObjectValidator($objectPath, $newValidator)); } else { throw new Exception\InvalidValidationConfigurationException('Invalid validate annotation in ' . $className . '->' . $methodName . '(): Validator specified for argument name "' . $annotationParameters['argumentName'] . '", but this argument does not exist.', 1253172726); } } return $validatorConjunctions; }
/** * Creates and returns an aspect from the annotations found in a class which * is tagged as an aspect. The object acting as an advice will already be * fetched (and therefore instantiated if necessary). * * @param string $aspectClassName Name of the class which forms the aspect, contains advices etc. * @return mixed The aspect container containing one or more advisors or FALSE if no container could be built * @throws Aop\Exception */ protected function buildAspectContainer($aspectClassName) { $aspectContainer = new AspectContainer($aspectClassName); $methodNames = get_class_methods($aspectClassName); foreach ($methodNames as $methodName) { foreach ($this->reflectionService->getMethodAnnotations($aspectClassName, $methodName) as $annotation) { $annotationClass = get_class($annotation); switch ($annotationClass) { case Flow\Around::class: $pointcutFilterComposite = $this->pointcutExpressionParser->parse($annotation->pointcutExpression, $this->renderSourceHint($aspectClassName, $methodName, $annotationClass)); $advice = new Aop\Advice\AroundAdvice($aspectClassName, $methodName); $pointcut = new Aop\Pointcut\Pointcut($annotation->pointcutExpression, $pointcutFilterComposite, $aspectClassName); $advisor = new Aop\Advisor($advice, $pointcut); $aspectContainer->addAdvisor($advisor); break; case Flow\Before::class: $pointcutFilterComposite = $this->pointcutExpressionParser->parse($annotation->pointcutExpression, $this->renderSourceHint($aspectClassName, $methodName, $annotationClass)); $advice = new Aop\Advice\BeforeAdvice($aspectClassName, $methodName); $pointcut = new Aop\Pointcut\Pointcut($annotation->pointcutExpression, $pointcutFilterComposite, $aspectClassName); $advisor = new Aop\Advisor($advice, $pointcut); $aspectContainer->addAdvisor($advisor); break; case Flow\AfterReturning::class: $pointcutFilterComposite = $this->pointcutExpressionParser->parse($annotation->pointcutExpression, $this->renderSourceHint($aspectClassName, $methodName, $annotationClass)); $advice = new Aop\Advice\AfterReturningAdvice($aspectClassName, $methodName); $pointcut = new Aop\Pointcut\Pointcut($annotation->pointcutExpression, $pointcutFilterComposite, $aspectClassName); $advisor = new Aop\Advisor($advice, $pointcut); $aspectContainer->addAdvisor($advisor); break; case Flow\AfterThrowing::class: $pointcutFilterComposite = $this->pointcutExpressionParser->parse($annotation->pointcutExpression, $this->renderSourceHint($aspectClassName, $methodName, $annotationClass)); $advice = new Aop\Advice\AfterThrowingAdvice($aspectClassName, $methodName); $pointcut = new Aop\Pointcut\Pointcut($annotation->pointcutExpression, $pointcutFilterComposite, $aspectClassName); $advisor = new Aop\Advisor($advice, $pointcut); $aspectContainer->addAdvisor($advisor); break; case Flow\After::class: $pointcutFilterComposite = $this->pointcutExpressionParser->parse($annotation->pointcutExpression, $this->renderSourceHint($aspectClassName, $methodName, $annotationClass)); $advice = new Aop\Advice\AfterAdvice($aspectClassName, $methodName); $pointcut = new Aop\Pointcut\Pointcut($annotation->pointcutExpression, $pointcutFilterComposite, $aspectClassName); $advisor = new Aop\Advisor($advice, $pointcut); $aspectContainer->addAdvisor($advisor); break; case Flow\Pointcut::class: $pointcutFilterComposite = $this->pointcutExpressionParser->parse($annotation->expression, $this->renderSourceHint($aspectClassName, $methodName, $annotationClass)); $pointcut = new Aop\Pointcut\Pointcut($annotation->expression, $pointcutFilterComposite, $aspectClassName, $methodName); $aspectContainer->addPointcut($pointcut); break; } } } $introduceAnnotation = $this->reflectionService->getClassAnnotation($aspectClassName, Flow\Introduce::class); if ($introduceAnnotation !== null) { if ($introduceAnnotation->interfaceName === null && $introduceAnnotation->traitName === null) { throw new Aop\Exception('The introduction in class "' . $aspectClassName . '" does neither contain an interface name nor a trait name, at least one is required.', 1172694761); } $pointcutFilterComposite = $this->pointcutExpressionParser->parse($introduceAnnotation->pointcutExpression, $this->renderSourceHint($aspectClassName, $introduceAnnotation->interfaceName, Flow\Introduce::class)); $pointcut = new Aop\Pointcut\Pointcut($introduceAnnotation->pointcutExpression, $pointcutFilterComposite, $aspectClassName); if ($introduceAnnotation->interfaceName !== null) { $introduction = new Aop\InterfaceIntroduction($aspectClassName, $introduceAnnotation->interfaceName, $pointcut); $aspectContainer->addInterfaceIntroduction($introduction); } if ($introduceAnnotation->traitName !== null) { $introduction = new TraitIntroduction($aspectClassName, $introduceAnnotation->traitName, $pointcut); $aspectContainer->addTraitIntroduction($introduction); } } foreach ($this->reflectionService->getClassPropertyNames($aspectClassName) as $propertyName) { $introduceAnnotation = $this->reflectionService->getPropertyAnnotation($aspectClassName, $propertyName, Flow\Introduce::class); if ($introduceAnnotation !== null) { $pointcutFilterComposite = $this->pointcutExpressionParser->parse($introduceAnnotation->pointcutExpression, $this->renderSourceHint($aspectClassName, $propertyName, Flow\Introduce::class)); $pointcut = new Aop\Pointcut\Pointcut($introduceAnnotation->pointcutExpression, $pointcutFilterComposite, $aspectClassName); $introduction = new PropertyIntroduction($aspectClassName, $propertyName, $pointcut); $aspectContainer->addPropertyIntroduction($introduction); } } if (count($aspectContainer->getAdvisors()) < 1 && count($aspectContainer->getPointcuts()) < 1 && count($aspectContainer->getInterfaceIntroductions()) < 1 && count($aspectContainer->getTraitIntroductions()) < 1 && count($aspectContainer->getPropertyIntroductions()) < 1) { throw new Aop\Exception('The class "' . $aspectClassName . '" is tagged to be an aspect but doesn\'t contain advices nor pointcut or introduction declarations.', 1169124534); } return $aspectContainer; }