diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4fc3df9..c8bb51b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,10 @@ None
Attributes are now collected from interfaces and traits as well as classes.
+Parameter attributes are now collected. Use the method `findTargetParameters()`
+to find target parameters, and the method `filterTargetParameters()` to filter
+target parameters according to a predicate.
+
### Deprecated Features
None
diff --git a/README.md b/README.md
index a1c44fb..dc000d6 100644
--- a/README.md
+++ b/README.md
@@ -22,9 +22,12 @@ _discover_ attribute targets in a codebase—for known targets you can use refle
- Can cache discoveries to speed up consecutive runs.
> [!NOTE]
-> Currently, the plugin supports class, method, and property targets.
+> Currently, the plugin supports class, method, property, and parameter targets.
> You're welcome to [contribute](CONTRIBUTING.md) if you're interested in expending its support.
+> [!WARNING]
+> Attributes used on functions are ignored at this time.
+
#### Usage
@@ -59,6 +62,11 @@ foreach (Attributes::findTargetProperties(Column::class) as $target) {
var_dump($target->attribute, $target->class, $target->name);
}
+// Find the target method parameters of the UserInput attribute.
+foreach (Attributes::findTargetParameters(UserInput::class) as $target) {
+ var_dump($target->attribute, $target->class, $target->method, $target->name);
+}
+
// Filter target methods using a predicate.
// You can also filter target classes and properties.
$predicate = fn($attribute) => is_a($attribute, Route::class, true);
diff --git a/phpcs.xml b/phpcs.xml
index aff2a22..7f5eccb 100644
--- a/phpcs.xml
+++ b/phpcs.xml
@@ -18,6 +18,7 @@
tests/bootstrap.php
+ src/Collection.php
tests/*
diff --git a/src/Attributes.php b/src/Attributes.php
index e4d6547..939aa98 100644
--- a/src/Attributes.php
+++ b/src/Attributes.php
@@ -61,6 +61,18 @@ public static function findTargetProperties(string $attribute): array
return self::getCollection()->findTargetProperties($attribute);
}
+ /**
+ * @template T of object
+ *
+ * @param class-string $attribute
+ *
+ * @return TargetParameter[]
+ */
+ public static function findTargetParameters(string $attribute): array
+ {
+ return self::getCollection()->findTargetParameters($attribute);
+ }
+
/**
* @param callable(class-string $attribute, class-string $class):bool $predicate
*
@@ -91,6 +103,16 @@ public static function filterTargetProperties(callable $predicate): array
return self::getCollection()->filterTargetProperties($predicate);
}
+ /**
+ * @param callable(class-string $attribute, class-string $class, string $property, string $method):bool $predicate
+ *
+ * @return array>
+ */
+ public static function filterTargetParameters(callable $predicate): array
+ {
+ return self::getCollection()->filterTargetParameters($predicate);
+ }
+
/**
* @param class-string $class
*
diff --git a/src/ClassAttributeCollector.php b/src/ClassAttributeCollector.php
index 30eab8d..290ef17 100644
--- a/src/ClassAttributeCollector.php
+++ b/src/ClassAttributeCollector.php
@@ -9,6 +9,7 @@
/**
* @internal
+ * @readonly
*/
class ClassAttributeCollector
{
@@ -24,6 +25,7 @@ public function __construct(
* array,
* array,
* array,
+ * array,
* }
*
* @throws ReflectionException
@@ -33,7 +35,7 @@ public function collectAttributes(string $class): array
$classReflection = new ReflectionClass($class);
if (self::isAttribute($classReflection)) {
- return [ [], [], [] ];
+ return [ [], [], [], [] ];
}
$classAttributes = [];
@@ -52,24 +54,18 @@ public function collectAttributes(string $class): array
);
}
+ /** @var array $methodAttributes */
$methodAttributes = [];
+ /** @var array $parameterAttributes */
+ $parameterAttributes = [];
foreach ($classReflection->getMethods() as $methodReflection) {
- foreach ($methodReflection->getAttributes() as $attribute) {
- if (self::isAttributeIgnored($attribute)) {
- continue;
- }
-
- $method = $methodReflection->name;
-
- $this->log->debug("Found attribute {$attribute->getName()} on $class::$method");
-
- $methodAttributes[] = new TransientTargetMethod(
- $attribute->getName(),
- $attribute->getArguments(),
- $method,
- );
- }
+ $this->collectMethodAndParameterAttributes(
+ $class,
+ $methodReflection,
+ $methodAttributes,
+ $parameterAttributes,
+ );
}
$propertyAttributes = [];
@@ -93,7 +89,7 @@ public function collectAttributes(string $class): array
}
}
- return [ $classAttributes, $methodAttributes, $propertyAttributes ];
+ return [ $classAttributes, $methodAttributes, $propertyAttributes, $parameterAttributes ];
}
/**
@@ -119,8 +115,75 @@ private static function isAttributeIgnored(ReflectionAttribute $attribute): bool
{
static $ignored = [
\ReturnTypeWillChange::class => true,
+ \SensitiveParameter::class => true,
];
return isset($ignored[$attribute->getName()]); // @phpstan-ignore offsetAccess.nonOffsetAccessible
}
+
+ /**
+ * @param array $methodAttributes
+ * @param array $parameterAttributes
+ */
+ private function collectMethodAndParameterAttributes(
+ string $class,
+ \ReflectionMethod $methodReflection,
+ array &$methodAttributes,
+ array &$parameterAttributes,
+ ): void {
+ foreach ($methodReflection->getAttributes() as $attribute) {
+ if (self::isAttributeIgnored($attribute)) {
+ continue;
+ }
+
+ $method = $methodReflection->name;
+
+ $this->log->debug("Found attribute {$attribute->getName()} on $class::$method");
+
+ $methodAttributes[] = new TransientTargetMethod(
+ $attribute->getName(),
+ $attribute->getArguments(),
+ $method,
+ );
+ }
+
+ $parameterAttributes = array_merge(
+ $parameterAttributes,
+ $this->collectParameterAttributes($methodReflection),
+ );
+ }
+
+ /**
+ * @return array
+ */
+ private function collectParameterAttributes(\ReflectionMethod $reflectionFunctionAbstract): array
+ {
+ $targets = [];
+ $class = $reflectionFunctionAbstract->class;
+ $method = $reflectionFunctionAbstract->name;
+
+ foreach ($reflectionFunctionAbstract->getParameters() as $parameter) {
+ /** @var non-empty-string $name */
+ $name = $parameter->name;
+
+ $paramLabel = $class . '::' . $method . '(' . $name . ')';
+
+ foreach ($parameter->getAttributes() as $attribute) {
+ if (self::isAttributeIgnored($attribute)) {
+ continue;
+ }
+
+ $this->log->debug("Found attribute {$attribute->getName()} on $paramLabel");
+
+ $targets[] = new TransientTargetParameter(
+ $attribute->getName(),
+ $attribute->getArguments(),
+ $method,
+ $name
+ );
+ }
+ }
+
+ return $targets;
+ }
}
diff --git a/src/Collection.php b/src/Collection.php
index d95240e..90e38d0 100644
--- a/src/Collection.php
+++ b/src/Collection.php
@@ -22,11 +22,15 @@ final class Collection
* @param array> $targetProperties
* Where _key_ is an attribute class and _value_ an array of arrays
* where 0 are the attribute arguments, 1 is a target class, and 2 is the target property.
+ * @param array> $targetParameters
+ * Where _key_ is an attribute class and _value_ an array of arrays where 0 are the
+ * attribute arguments, 1 is a target class, 2 is the target method, and 3 is the target parameter.
*/
public function __construct(
private array $targetClasses,
private array $targetMethods,
private array $targetProperties,
+ private array $targetParameters,
) {
}
@@ -109,6 +113,50 @@ private static function createMethodAttribute(
}
}
+ /**
+ * @template T of object
+ *
+ * @param class-string $attribute
+ *
+ * @return array>
+ */
+ public function findTargetParameters(string $attribute): array
+ {
+ return array_map(
+ fn(array $t) => self::createParameterAttribute($attribute, ...$t),
+ $this->targetParameters[$attribute] ?? [],
+ );
+ }
+
+ /**
+ * @template T of object
+ *
+ * @param class-string $attribute
+ * @param array $arguments
+ * @param class-string $class
+ * @param non-empty-string $method
+ * @param non-empty-string $parameter
+ *
+ * @return TargetParameter
+ */
+ private static function createParameterAttribute(
+ string $attribute,
+ array $arguments,
+ string $class,
+ string $method,
+ string $parameter,
+ ): object {
+ try {
+ $a = new $attribute(...$arguments);
+ return new TargetParameter($a, $class, $method, $parameter);
+ } catch (Throwable $e) {
+ throw new RuntimeException(
+ "An error occurred while instantiating attribute $attribute on parameter $class::$method($parameter)",
+ previous: $e,
+ );
+ }
+ }
+
/**
* @template T of object
*
@@ -196,6 +244,32 @@ public function filterTargetMethods(callable $predicate): array
return $ar;
}
+ /**
+ * @param callable(class-string $attribute, class-string $class, non-empty-string $method, non-empty-string $parameter):bool $predicate
+ *
+ * @return array>
+ */
+ public function filterTargetParameters(callable $predicate): array
+ {
+ $ar = [];
+
+ foreach ($this->targetParameters as $attribute => $references) {
+ foreach ($references as [$arguments, $class, $method, $parameter]) {
+ if ($predicate($attribute, $class, $method, $parameter)) {
+ $ar[] = self::createParameterAttribute(
+ $attribute,
+ $arguments,
+ $class,
+ $method,
+ $parameter,
+ );
+ }
+ }
+ }
+
+ return $ar;
+ }
+
/**
* @param callable(class-string $attribute, class-string $class, non-empty-string $property):bool $predicate
*
diff --git a/src/MemoizeAttributeCollector.php b/src/MemoizeAttributeCollector.php
index 63c9835..565c6be 100644
--- a/src/MemoizeAttributeCollector.php
+++ b/src/MemoizeAttributeCollector.php
@@ -22,12 +22,10 @@ class MemoizeAttributeCollector
* array,
* array,
* array,
+ * array,
* }>
- * Where _key_ is a class and _value is an array where:
+ * Where _key_ is a class and _value_ is an array where:
* - `0` is a timestamp
- * - `1` is an array of class attributes
- * - `2` is an array of method attributes
- * - `3` is an array of property attributes
*/
private array $state;
@@ -58,7 +56,8 @@ public function collectAttributes(array $classMap): TransientCollection
$classAttributes,
$methodAttributes,
$propertyAttributes,
- ] = $this->state[$class] ?? [ 0, [], [], [] ];
+ $parameterAttributes,
+ ] = $this->state[$class] ?? [ 0, [], [], [], [] ];
$mtime = filemtime($filepath);
@@ -75,6 +74,7 @@ public function collectAttributes(array $classMap): TransientCollection
$classAttributes,
$methodAttributes,
$propertyAttributes,
+ $parameterAttributes,
] = $classAttributeCollector->collectAttributes($class);
} catch (Throwable $e) {
$this->log->error(
@@ -82,7 +82,13 @@ public function collectAttributes(array $classMap): TransientCollection
);
}
- $this->state[$class] = [ time(), $classAttributes, $methodAttributes, $propertyAttributes ];
+ $this->state[$class] = [
+ time(),
+ $classAttributes,
+ $methodAttributes,
+ $propertyAttributes,
+ $parameterAttributes,
+ ];
}
if (count($classAttributes)) {
@@ -91,6 +97,9 @@ public function collectAttributes(array $classMap): TransientCollection
if (count($methodAttributes)) {
$collector->addMethodAttributes($class, $methodAttributes);
}
+ if (count($parameterAttributes)) {
+ $collector->addParameterAttributes($class, $parameterAttributes);
+ }
if (count($propertyAttributes)) {
$collector->addTargetProperties($class, $propertyAttributes);
}
diff --git a/src/Plugin.php b/src/Plugin.php
index 4606261..a2ac034 100644
--- a/src/Plugin.php
+++ b/src/Plugin.php
@@ -28,7 +28,7 @@ final class Plugin implements PluginInterface, EventSubscriberInterface
{
public const CACHE_DIR = '.composer-attribute-collector';
public const VERSION_MAJOR = 2;
- public const VERSION_MINOR = 0;
+ public const VERSION_MINOR = 1;
/**
* @uses onPostAutoloadDump
diff --git a/src/TargetParameter.php b/src/TargetParameter.php
new file mode 100644
index 0000000..334ded6
--- /dev/null
+++ b/src/TargetParameter.php
@@ -0,0 +1,28 @@
+>
+ */
+ public array $parameters = [];
+
/**
* @var array>
* Where _key_ is a target class.
@@ -45,6 +50,16 @@ public function addMethodAttributes(string $class, iterable $targets): void
$this->methods[$class] = $targets;
}
+ /**
+ * @param class-string $class
+ * @param iterable $targets
+ * The target class.
+ */
+ public function addParameterAttributes(string $class, iterable $targets): void
+ {
+ $this->parameters[$class] = $targets;
+ }
+
/**
* @param class-string $class
* @param iterable $targets
diff --git a/src/TransientCollectionRenderer.php b/src/TransientCollectionRenderer.php
index 60f8018..dbb8fe3 100644
--- a/src/TransientCollectionRenderer.php
+++ b/src/TransientCollectionRenderer.php
@@ -16,6 +16,7 @@ public static function render(TransientCollection $collector): string
$targetClassesCode = self::targetsToCode($collector->classes);
$targetMethodsCode = self::targetsToCode($collector->methods);
$targetPropertiesCode = self::targetsToCode($collector->properties);
+ $targetParametersCode = self::targetsToCode($collector->parameters);
return <<> $targetByClass
+ *
+ * @param iterable> $targetByClass
*
* @return string
*/
@@ -45,9 +48,15 @@ private static function targetsToCode(iterable $targetByClass): string
/**
* //phpcs:disable Generic.Files.LineLength.TooLong
- * @param iterable> $targetByClass
*
- * @return array, class-string, 2?:non-empty-string }>>
+ * @param iterable> $targetByClass
+ *
+ * @return array,
+ * class-string,
+ * 2?:non-empty-string,
+ * 3?:non-empty-string
+ * }>>
*/
private static function targetsToArray(iterable $targetByClass): array
{
@@ -57,7 +66,15 @@ private static function targetsToArray(iterable $targetByClass): array
foreach ($targets as $t) {
$a = [ $t->arguments, $class ];
- if ($t instanceof TransientTargetMethod || $t instanceof TransientTargetProperty) {
+ if ($t instanceof TransientTargetParameter) {
+ $a[] = $t->method;
+ }
+
+ if (
+ $t instanceof TransientTargetMethod
+ || $t instanceof TransientTargetProperty
+ || $t instanceof TransientTargetParameter
+ ) {
$a[] = $t->name;
}
diff --git a/src/TransientTargetParameter.php b/src/TransientTargetParameter.php
new file mode 100644
index 0000000..1418493
--- /dev/null
+++ b/src/TransientTargetParameter.php
@@ -0,0 +1,24 @@
+ $arguments The attribute arguments.
+ * @param non-empty-string $method The target method.
+ * @param non-empty-string $name The target parameter.
+ */
+ public function __construct(
+ public string $attribute,
+ public array $arguments,
+ public string $method,
+ public string $name,
+ ) {
+ }
+}
diff --git a/tests/Acme/PSR4/Presentation/ArticleController.php b/tests/Acme/PSR4/Presentation/ArticleController.php
index 07ccb3a..537bb1f 100644
--- a/tests/Acme/PSR4/Presentation/ArticleController.php
+++ b/tests/Acme/PSR4/Presentation/ArticleController.php
@@ -11,6 +11,8 @@
use Acme\Attribute\Resource;
use Acme\Attribute\Route;
+use Acme81\Attribute\ParameterA;
+use Acme81\Attribute\ParameterB;
#[Resource("articles")]
final class ArticleController
@@ -24,4 +26,15 @@ public function list(): void
public function show(int $id): void
{
}
+
+ #[Route("/articles/method/", 'GET', 'articles:method')]
+ public function aMethod(
+ #[ParameterA("my parameter label")]
+ $myParameter,
+ #[ParameterB("my 2nd parameter label", "some more data")]
+ $anotherParameter,
+ #[ParameterA("my yet another parameter label")]
+ $yetAnotherParameter
+ ) {
+ }
}
diff --git a/tests/Acme81/Attribute/ParameterA.php b/tests/Acme81/Attribute/ParameterA.php
new file mode 100644
index 0000000..f1a4ff7
--- /dev/null
+++ b/tests/Acme81/Attribute/ParameterA.php
@@ -0,0 +1,14 @@
+ 'articles:show', 'pattern' => "/articles/{id}", 'method' => 'GET' ],
'show',
),
+ new TransientTargetMethod(
+ 'Acme\Attribute\Route',
+ [ "/articles/method/", 'GET', 'articles:method' ],
+ 'aMethod',
+ ),
],
[],
+ [
+ new TransientTargetParameter(
+ 'Acme81\Attribute\ParameterA',
+ ["my parameter label"],
+ 'aMethod',
+ 'myParameter'
+ ),
+ new TransientTargetParameter(
+ 'Acme81\Attribute\ParameterB',
+ ["my 2nd parameter label", "some more data"],
+ 'aMethod',
+ 'anotherParameter'
+ ),
+ new TransientTargetParameter(
+ 'Acme81\Attribute\ParameterA',
+ ["my yet another parameter label"],
+ 'aMethod',
+ 'yetAnotherParameter'
+ ),
+ ],
]
],
@@ -108,6 +137,7 @@ public static function provideCollectAttributes(): array
new TransientTargetMethod('Acme\Attribute\Subscribe', [], 'onEventA'),
],
[],
+ [],
]
],
@@ -127,6 +157,7 @@ public static function provideCollectAttributes(): array
new TransientTargetProperty('Acme\Attribute\ActiveRecord\Text', [], 'body'),
new TransientTargetProperty('Acme\Attribute\ActiveRecord\Boolean', [], 'active'),
],
+ [],
]
],
diff --git a/tests/CollectionTest.php b/tests/CollectionTest.php
index 4dfabbb..68f1b55 100644
--- a/tests/CollectionTest.php
+++ b/tests/CollectionTest.php
@@ -18,10 +18,13 @@
use Acme\PSR4\DeleteMenu;
use Acme\PSR4\Presentation\ArticleController;
use Closure;
+use Acme81\Attribute\ParameterA;
+use Acme81\Attribute\ParameterB;
use olvlvl\ComposerAttributeCollector\Attributes;
use olvlvl\ComposerAttributeCollector\Collection;
use olvlvl\ComposerAttributeCollector\TargetClass;
use olvlvl\ComposerAttributeCollector\TargetMethod;
+use olvlvl\ComposerAttributeCollector\TargetParameter;
use olvlvl\ComposerAttributeCollector\TargetProperty;
use PHPUnit\Framework\TestCase;
use RuntimeException;
@@ -52,6 +55,8 @@ public function testInstantiationErrorIsDecorated(string $expectedMessage, Closu
Serial::class => [
[ [ 'Primary' => true ], Article::class, 'id' ],
]
+ ],
+ targetParameters: [
]
);
@@ -104,6 +109,8 @@ public function testFilterTargetClasses(): void
targetMethods: [
],
targetProperties: [
+ ],
+ targetParameters: [
]
);
@@ -134,6 +141,8 @@ public function testFilterTargetMethods(): void
],
],
targetProperties: [
+ ],
+ targetParameters: [
]
);
@@ -146,6 +155,36 @@ public function testFilterTargetMethods(): void
], $actual);
}
+ public function testFilterTargetParameters(): void
+ {
+ $collection = new Collection(
+ targetClasses: [
+ ],
+ targetMethods: [
+ ],
+ targetProperties: [
+ ],
+ targetParameters: [
+ ParameterA::class => [
+ [ [ 'a' ], ArticleController::class, 'myMethod', 'myParamA', ],
+ [ [ 'a2' ], ArticleController::class, 'myMethod', 'myParamA2' ],
+ [ [ 'a3' ], ArticleController::class, 'myFoo', 'fooParam' ],
+ ],
+ ParameterB::class => [
+ [ [ 'b', 'more data'], ArticleController::class, 'myMethod', 'myParamB' ],
+ ],
+ ]
+ );
+
+ $actual = $collection->filterTargetParameters(fn($a) => is_a($a, ParameterA::class, true));
+
+ $this->assertEquals([
+ new TargetParameter(new ParameterA('a'), ArticleController::class, 'myMethod', 'myParamA'),
+ new TargetParameter(new ParameterA('a2'), ArticleController::class, 'myMethod', 'myParamA2'),
+ new TargetParameter(new ParameterA('a3'), ArticleController::class, 'myFoo', 'fooParam'),
+ ], $actual);
+ }
+
public function testFilterTargetProperties(): void
{
$collection = new Collection(
@@ -175,6 +214,8 @@ public function testFilterTargetProperties(): void
Text::class => [
[ [ ], Article::class, 'body' ],
]
+ ],
+ targetParameters: [
]
);
@@ -220,6 +261,8 @@ public function testForClass(): void
Text::class => [
[ [ ], Article::class, 'body' ],
]
+ ],
+ targetParameters: [
]
);
diff --git a/tests/PluginTest.php b/tests/PluginTest.php
index 954202d..25eaaeb 100644
--- a/tests/PluginTest.php
+++ b/tests/PluginTest.php
@@ -24,12 +24,16 @@
use Acme\Attribute\Route;
use Acme\Attribute\Subscribe;
use Acme\PSR4\Presentation\ArticleController;
+use Acme81\Attribute\ParameterA;
+use Acme81\Attribute\ParameterB;
use olvlvl\ComposerAttributeCollector\Attributes;
use olvlvl\ComposerAttributeCollector\Config;
use olvlvl\ComposerAttributeCollector\Plugin;
use olvlvl\ComposerAttributeCollector\TargetClass;
use olvlvl\ComposerAttributeCollector\TargetMethod;
+use olvlvl\ComposerAttributeCollector\TargetParameter;
use olvlvl\ComposerAttributeCollector\TargetProperty;
+use PhpParser\Node\Param;
use PHPUnit\Framework\TestCase;
use ReflectionException;
@@ -164,6 +168,10 @@ public static function provideTargetMethods(): array
[
Route::class,
[
+ [
+ new Route("/articles/method/", 'GET', 'articles:method'),
+ 'Acme\PSR4\Presentation\ArticleController::aMethod'
+ ],
[
new Route("/articles", 'GET', 'articles:list'),
'Acme\PSR4\Presentation\ArticleController::list'
@@ -194,6 +202,52 @@ public static function provideTargetMethods(): array
];
}
+ /**
+ * @dataProvider provideTargetParameters
+ *
+ * @param class-string $attribute
+ * @param array $expected
+ */
+ public function testTargetParameters(string $attribute, array $expected): void
+ {
+ $actual = Attributes::findTargetParameters($attribute);
+
+ $this->assertEquals($expected, $this->collectParameters($actual));
+ }
+
+ /**
+ * @return array }>
+ */
+ public static function provideTargetParameters(): array
+ {
+ return [
+
+ [
+ ParameterA::class,
+ [
+ [
+ new ParameterA('my parameter label'),
+ 'Acme\PSR4\Presentation\ArticleController::aMethod(myParameter)'
+ ],
+ [
+ new ParameterA('my yet another parameter label'),
+ 'Acme\PSR4\Presentation\ArticleController::aMethod(yetAnotherParameter)'
+ ],
+ ]
+ ],
+ [
+ ParameterB::class,
+ [
+ [
+ new ParameterB('my 2nd parameter label', 'some more data'),
+ 'Acme\PSR4\Presentation\ArticleController::aMethod(anotherParameter)'
+ ],
+ ]
+ ],
+
+ ];
+ }
+
/**
* @dataProvider provideTargetProperties
*
@@ -262,6 +316,7 @@ public function testFilterTargetMethods(): void
);
$this->assertEquals([
+ [ new Route("/articles/method/", 'GET', 'articles:method'), 'Acme\PSR4\Presentation\ArticleController::aMethod' ],
[ new Route("/articles", 'GET', 'articles:list'), 'Acme\PSR4\Presentation\ArticleController::list' ],
[ new Route("/articles/{id}", 'GET', 'articles:show'), 'Acme\PSR4\Presentation\ArticleController::show' ],
[ new Get(), 'Acme\Presentation\FileController::list' ],
@@ -301,6 +356,18 @@ public function testFilterTargetMethods81(): void
$this->assertEquals($expected, $actual);
}
+ public function testFilterTargetParameters(): void
+ {
+ $actual = Attributes::filterTargetParameters(
+ Attributes::predicateForAttributeInstanceOf(ParameterA::class)
+ );
+
+ $this->assertEquals([
+ [ new ParameterA("my parameter label"), 'Acme\PSR4\Presentation\ArticleController::aMethod(myParameter)' ],
+ [ new ParameterA('my yet another parameter label'), 'Acme\PSR4\Presentation\ArticleController::aMethod(yetAnotherParameter)' ],
+ ], $this->collectParameters($actual));
+ }
+
public function testFilterTargetProperties(): void
{
$actual = Attributes::filterTargetProperties(
@@ -326,8 +393,9 @@ public function testForClass(): void
], $forClass->classAttributes);
$this->assertEquals([
- 'list' => [ new Route("/articles", id: 'articles:list') ],
- 'show' => [ new Route("/articles/{id}", id: 'articles:show') ],
+ 'list' => [ new Route("/articles", 'GET', 'articles:list') ],
+ 'show' => [ new Route("/articles/{id}", 'GET', 'articles:show') ],
+ 'aMethod' => [ new Route("/articles/method/", 'GET', 'articles:method') ],
], $forClass->methodsAttributes);
}
@@ -371,6 +439,26 @@ private function collectMethods(array $targets): array
return $methods;
}
+ /**
+ * @template T of object
+ *
+ * @param TargetParameter[] $targets
+ *
+ * @return array
+ */
+ private function collectParameters(array $targets): array
+ {
+ $parameters = [];
+
+ foreach ($targets as $target) {
+ $parameters[] = [ $target->attribute, "$target->class::$target->method($target->name)" ];
+ }
+
+ usort($parameters, fn($a, $b) => $a[1] <=> $b[1]);
+
+ return $parameters;
+ }
+
/**
* @template T of object
*