Skip to content

Commit

Permalink
✨ [#708] Re-introduce feature about supporting target method… (#709)
Browse files Browse the repository at this point in the history
* ✨ [#708] Re-introduce feature about supporting target method attributes for resolving parameter annotations

* 📦 Introduce runtime deprecation for deprecated functionality to hint about upgrade path
  • Loading branch information
andrew-demb authored Nov 26, 2024
1 parent 183d373 commit 04d592f
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 1 deletion.
37 changes: 36 additions & 1 deletion src/AnnotationReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -248,11 +253,41 @@ public function getParameterAnnotationsPerParameter(array $refParameters): array
if (empty($refParameters)) {
return [];
}
$firstParam = reset($refParameters);

/** @var array<string, array<int,ParameterAnnotationInterface>> $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()] ??
Expand Down
5 changes: 5 additions & 0 deletions src/Annotations/Exceptions/InvalidParameterException.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
Expand Down
29 changes: 29 additions & 0 deletions tests/AnnotationReaderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace TheCodingMachine\GraphQLite\Fixtures\Annotations;

use stdClass;

/** @internal */
final class ClassWithTargetMethodParameterAnnotation
{
#[TargetMethodParameterAnnotation(target: 'bar')]
public function method(stdClass $bar): void
{
}

#[TargetMethodParameterAnnotation(target: 'unexistent')]
public function methodWithInvalidAnnotation(stdClass $bar): void
{
}
}
20 changes: 20 additions & 0 deletions tests/Fixtures/Annotations/TargetMethodParameterAnnotation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace TheCodingMachine\GraphQLite\Fixtures\Annotations;

use TheCodingMachine\GraphQLite\Annotations\ParameterAnnotationInterface;

#[\Attribute(\Attribute::TARGET_METHOD)]
final class TargetMethodParameterAnnotation implements ParameterAnnotationInterface
{
public function __construct(private readonly string $target)
{
}

public function getTarget(): string
{
return $this->target;
}
}

0 comments on commit 04d592f

Please sign in to comment.