diff --git a/src/AnnotationReader.php b/src/AnnotationReader.php index 95efef942..39ebb6706 100644 --- a/src/AnnotationReader.php +++ b/src/AnnotationReader.php @@ -24,14 +24,19 @@ use TheCodingMachine\GraphQLite\Annotations\Type; use TheCodingMachine\GraphQLite\Annotations\TypeInterface; +use function array_diff_key; use function array_filter; use function array_key_exists; use function array_map; use function array_merge; use function assert; use function count; +use function get_class; use function is_a; use function reset; +use function trigger_error; + +use const E_USER_DEPRECATED; class AnnotationReader { @@ -248,11 +253,41 @@ public function getParameterAnnotationsPerParameter(array $refParameters): array if (empty($refParameters)) { return []; } - $firstParam = reset($refParameters); + /** @var array> $parameterAnnotationsPerParameter */ + $parameterAnnotationsPerParameter = []; + + // resolve parameter annotations targeted to method + $firstParam = reset($refParameters); $method = $firstParam->getDeclaringFunction(); assert($method instanceof ReflectionMethod); + $parameterAnnotations = $this->getMethodAnnotations($method, ParameterAnnotationInterface::class); + foreach ($parameterAnnotations as $parameterAnnotation) { + trigger_error( + "Using '" . ParameterAnnotationInterface::class . "' over methods is deprecated. " . + "Found attribute '" . $parameterAnnotation::class . + "' over '" . $method->getDeclaringClass()->getName() . ':' . $method->getName() . "'. " . + "Please target annotation to the parameter '$" . $parameterAnnotation->getTarget() . "' instead", + E_USER_DEPRECATED, + ); + + $parameterAnnotationsPerParameter[$parameterAnnotation->getTarget()][] = $parameterAnnotation; + } + + // Let's check that the referenced parameters actually do exist: + $parametersByKey = []; + foreach ($refParameters as $refParameter) { + $parametersByKey[$refParameter->getName()] = true; + } + $diff = array_diff_key($parameterAnnotationsPerParameter, $parametersByKey); + if (count($diff) > 0) { + foreach ($diff as $parameterName => $parameterAnnotations) { + throw InvalidParameterException::parameterNotFound($parameterName, get_class($parameterAnnotations[0]), $method); + } + } + + // resolve parameter annotations targeted to parameter foreach ($refParameters as $refParameter) { $attributes = $refParameter->getAttributes(); $parameterAnnotationsPerParameter[$refParameter->getName()] = [...$parameterAnnotationsPerParameter[$refParameter->getName()] ?? diff --git a/src/Annotations/Exceptions/InvalidParameterException.php b/src/Annotations/Exceptions/InvalidParameterException.php index 86738a7ec..2323695b3 100644 --- a/src/Annotations/Exceptions/InvalidParameterException.php +++ b/src/Annotations/Exceptions/InvalidParameterException.php @@ -11,6 +11,11 @@ class InvalidParameterException extends BadMethodCallException { + public static function parameterNotFound(string $parameter, string $annotationClass, ReflectionMethod $reflectionMethod): self + { + return new self(sprintf('Parameter "%s" declared in annotation "%s" of method "%s::%s()" does not exist.', $parameter, $annotationClass, $reflectionMethod->getDeclaringClass()->getName(), $reflectionMethod->getName())); + } + public static function parameterNotFoundFromSourceField(string $parameter, string $annotationClass, ReflectionMethod $reflectionMethod): self { return new self(sprintf('Could not find parameter "%s" declared in annotation "%s". This annotation is itself declared in a SourceField attribute targeting resolver "%s::%s()".', $parameter, $annotationClass, $reflectionMethod->getDeclaringClass()->getName(), $reflectionMethod->getName())); diff --git a/tests/AnnotationReaderTest.php b/tests/AnnotationReaderTest.php index 0ec8f2158..f7493dead 100644 --- a/tests/AnnotationReaderTest.php +++ b/tests/AnnotationReaderTest.php @@ -9,12 +9,15 @@ use ReflectionMethod; use TheCodingMachine\GraphQLite\Annotations\Autowire; use TheCodingMachine\GraphQLite\Annotations\Exceptions\ClassNotFoundException; +use TheCodingMachine\GraphQLite\Annotations\Exceptions\InvalidParameterException; use TheCodingMachine\GraphQLite\Annotations\Field; use TheCodingMachine\GraphQLite\Annotations\Security; use TheCodingMachine\GraphQLite\Annotations\Type; use TheCodingMachine\GraphQLite\Fixtures\Annotations\ClassWithInvalidClassAnnotation; use TheCodingMachine\GraphQLite\Fixtures\Annotations\ClassWithInvalidExtendTypeAnnotation; use TheCodingMachine\GraphQLite\Fixtures\Annotations\ClassWithInvalidTypeAnnotation; +use TheCodingMachine\GraphQLite\Fixtures\Annotations\ClassWithTargetMethodParameterAnnotation; +use TheCodingMachine\GraphQLite\Fixtures\Annotations\TargetMethodParameterAnnotation; use TheCodingMachine\GraphQLite\Fixtures\Attributes\TestType; class AnnotationReaderTest extends TestCase @@ -126,6 +129,32 @@ public function testPhp8AttributeParameterAnnotations(): void $this->assertInstanceOf(Autowire::class, $parameterAnnotations['dao']->getAnnotationByType(Autowire::class)); } + /** + * This functionality can be dropped with next major release (8.0) with added explicit deprecations before release. + */ + public function testPhp8AttributeParameterAnnotationsForTargetMethod(): void + { + $annotationReader = new AnnotationReader(); + + $parameterAnnotations = $annotationReader->getParameterAnnotationsPerParameter((new ReflectionMethod(ClassWithTargetMethodParameterAnnotation::class, 'method'))->getParameters()); + + $this->assertInstanceOf(TargetMethodParameterAnnotation::class, $parameterAnnotations['bar']->getAnnotationByType(TargetMethodParameterAnnotation::class)); + } + + /** + * This functionality can be dropped with next major release (8.0) with added explicit deprecations before release. + */ + public function testPhp8AttributeParameterAnnotationsForTargetMethodWithInvalidTargetParameter(): void + { + $annotationReader = new AnnotationReader(); + + $this->expectException(InvalidParameterException::class); + $this->expectExceptionMessage('Parameter "unexistent" declared in annotation "TheCodingMachine\GraphQLite\Fixtures\Annotations\TargetMethodParameterAnnotation" of method "TheCodingMachine\GraphQLite\Fixtures\Annotations\ClassWithTargetMethodParameterAnnotation::methodWithInvalidAnnotation()" does not exist.'); + + $annotationReader->getParameterAnnotationsPerParameter((new ReflectionMethod(ClassWithTargetMethodParameterAnnotation::class, 'methodWithInvalidAnnotation'))->getParameters()); + } + + /** @noinspection PhpUnusedPrivateMethodInspection Used in {@see testPhp8AttributeParameterAnnotations} */ private function method1( #[Autowire('myService')] $dao, diff --git a/tests/Fixtures/Annotations/ClassWithTargetMethodParameterAnnotation.php b/tests/Fixtures/Annotations/ClassWithTargetMethodParameterAnnotation.php new file mode 100644 index 000000000..6830f2bbd --- /dev/null +++ b/tests/Fixtures/Annotations/ClassWithTargetMethodParameterAnnotation.php @@ -0,0 +1,21 @@ +target; + } +}