diff --git a/CHANGELOG.md b/CHANGELOG.md index ad56f6e..215de6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # CHANGELOG +## v2.2.0 + +### New Requirements + +None + +### New features + +None + +### Deprecated Features + +None + +### Backward Incompatible Changes + +None + +### Other Changes + +- [#36](https://github.com/olvlvl/composer-attribute-collector/pull/36) Attribute arguments are now serialized to support [nested attributes introduced in PHP 8.1](https://wiki.php.net/rfc/new_in_initializers). + + + ## v2.1.0 ### New Requirements diff --git a/src/Collection.php b/src/Collection.php index 90e38d0..7c90383 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -13,18 +13,18 @@ final class Collection { /** - * @param array> $targetClasses + * @param array> $targetClasses * Where _key_ is an attribute class and _value_ an array of arrays - * where 0 are the attribute arguments and 1 is a target class. - * @param array> $targetMethods + * where `0` is the serialized attribute arguments and `1` is a target class. + * @param array> $targetMethods * 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 method. - * @param array> $targetProperties + * where `0` is the serialized attribute arguments, `1` is a target class, and `2` is the target method. + * @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. + * where `0` is the serialized 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 serialized attribute arguments, `1` is a target class, `2` is the target method, and `3` is the target parameter. */ public function __construct( private array $targetClasses, @@ -53,15 +53,15 @@ public function findTargetClasses(string $attribute): array * @template T of object * * @param class-string $attribute - * @param array $arguments + * @param string $arguments The serialized arguments * @param class-string $class * * @return TargetClass */ - private static function createClassAttribute(string $attribute, array $arguments, string $class): object + private static function createClassAttribute(string $attribute, string $arguments, string $class): object { try { - $a = new $attribute(...$arguments); + $a = new $attribute(...unserialize($arguments)); return new TargetClass($a, $class); } catch (Throwable $e) { throw new RuntimeException( @@ -90,7 +90,7 @@ public function findTargetMethods(string $attribute): array * @template T of object * * @param class-string $attribute - * @param array $arguments + * @param string $arguments The serialized arguments * @param class-string $class * @param non-empty-string $method * @@ -98,12 +98,12 @@ public function findTargetMethods(string $attribute): array */ private static function createMethodAttribute( string $attribute, - array $arguments, + string $arguments, string $class, string $method, ): object { try { - $a = new $attribute(...$arguments); + $a = new $attribute(...unserialize($arguments)); return new TargetMethod($a, $class, $method); } catch (Throwable $e) { throw new RuntimeException( @@ -132,7 +132,7 @@ public function findTargetParameters(string $attribute): array * @template T of object * * @param class-string $attribute - * @param array $arguments + * @param string $arguments The serialized arguments * @param class-string $class * @param non-empty-string $method * @param non-empty-string $parameter @@ -141,13 +141,13 @@ public function findTargetParameters(string $attribute): array */ private static function createParameterAttribute( string $attribute, - array $arguments, + string $arguments, string $class, string $method, string $parameter, ): object { try { - $a = new $attribute(...$arguments); + $a = new $attribute(...unserialize($arguments)); return new TargetParameter($a, $class, $method, $parameter); } catch (Throwable $e) { throw new RuntimeException( @@ -176,7 +176,7 @@ public function findTargetProperties(string $attribute): array * @template T of object * * @param class-string $attribute - * @param array $arguments + * @param string $arguments The serialized arguments * @param class-string $class * @param non-empty-string $property * @@ -184,12 +184,12 @@ public function findTargetProperties(string $attribute): array */ private static function createPropertyAttribute( string $attribute, - array $arguments, + string $arguments, string $class, string $property, ): object { try { - $a = new $attribute(...$arguments); + $a = new $attribute(...unserialize($arguments)); return new TargetProperty($a, $class, $property); } catch (Throwable $e) { throw new RuntimeException( diff --git a/src/TransientCollectionRenderer.php b/src/TransientCollectionRenderer.php index dbb8fe3..7e68dac 100644 --- a/src/TransientCollectionRenderer.php +++ b/src/TransientCollectionRenderer.php @@ -52,11 +52,12 @@ private static function targetsToCode(iterable $targetByClass): string * @param iterable> $targetByClass * * @return array, + * string, * class-string, * 2?:non-empty-string, * 3?:non-empty-string - * }>> + * }>> Where _key_ is an attribute class and _value_ is an array of parameters, + * where `1` is the serialized arguments and `2` is the target class. */ private static function targetsToArray(iterable $targetByClass): array { @@ -64,7 +65,7 @@ private static function targetsToArray(iterable $targetByClass): array foreach ($targetByClass as $class => $targets) { foreach ($targets as $t) { - $a = [ $t->arguments, $class ]; + $a = [ serialize($t->arguments), $class ]; if ($t instanceof TransientTargetParameter) { $a[] = $t->method; diff --git a/tests/Acme81/Attribute/SampleNested.php b/tests/Acme81/Attribute/SampleNested.php new file mode 100644 index 0000000..af0fdc9 --- /dev/null +++ b/tests/Acme81/Attribute/SampleNested.php @@ -0,0 +1,14 @@ + [ - [ [ 'Permission' => 'is_admin' ], DeleteMenu::class ], + [ serialize([ 'Permission' => 'is_admin' ]), DeleteMenu::class ], ] ], targetMethods: [ Route::class => [ - [ [ 'Method' => 'GET' ], ArticleController::class, 'list' ], + [ serialize([ 'Method' => 'GET' ]), ArticleController::class, 'list' ], ] ], targetProperties: [ Serial::class => [ - [ [ 'Primary' => true ], Article::class, 'id' ], + [ serialize([ 'Primary' => true ]), Article::class, 'id' ], ] ], targetParameters: [ @@ -101,9 +101,9 @@ public function testFilterTargetClasses(): void $collection = new Collection( targetClasses: [ Route::class => [ - [ [ 'pattern' => '/articles' ], ArticleController::class ], - [ [ 'pattern' => '/images' ], ImageController::class ], - [ [ 'pattern' => '/files' ], FileController::class ], + [ serialize([ 'pattern' => '/articles' ]), ArticleController::class ], + [ serialize([ 'pattern' => '/images' ]), ImageController::class ], + [ serialize([ 'pattern' => '/files' ]), FileController::class ], ], ], targetMethods: [ @@ -131,13 +131,13 @@ public function testFilterTargetMethods(): void ], targetMethods: [ Route::class => [ - [ [ 'pattern' => '/recent' ], ArticleController::class, 'recent' ], + [ serialize([ 'pattern' => '/recent' ]), ArticleController::class, 'recent' ], ], Get::class => [ - [ [ ], ArticleController::class, 'show' ], + [ serialize([ ]), ArticleController::class, 'show' ], ], Post::class => [ - [ [ ], ArticleController::class, 'create' ], + [ serialize([ ]), ArticleController::class, 'create' ], ], ], targetProperties: [ @@ -166,12 +166,12 @@ public function testFilterTargetParameters(): void ], targetParameters: [ ParameterA::class => [ - [ [ 'a' ], ArticleController::class, 'myMethod', 'myParamA', ], - [ [ 'a2' ], ArticleController::class, 'myMethod', 'myParamA2' ], - [ [ 'a3' ], ArticleController::class, 'myFoo', 'fooParam' ], + [ serialize([ 'a' ]), ArticleController::class, 'myMethod', 'myParamA', ], + [ serialize([ 'a2' ]), ArticleController::class, 'myMethod', 'myParamA2' ], + [ serialize([ 'a3' ]), ArticleController::class, 'myFoo', 'fooParam' ], ], ParameterB::class => [ - [ [ 'b', 'more data'], ArticleController::class, 'myMethod', 'myParamB' ], + [ serialize([ 'b', 'more data']), ArticleController::class, 'myMethod', 'myParamB' ], ], ] ); @@ -192,27 +192,27 @@ public function testFilterTargetProperties(): void ], targetMethods: [ Route::class => [ - [ [ 'pattern' => '/recent' ], ArticleController::class, 'recent' ], + [ serialize([ 'pattern' => '/recent' ]), ArticleController::class, 'recent' ], ], Get::class => [ - [ [ ], ArticleController::class, 'show' ], + [ serialize([ ]), ArticleController::class, 'show' ], ], Post::class => [ - [ [ ], ArticleController::class, 'create' ], + [ serialize([ ]), ArticleController::class, 'create' ], ], ], targetProperties: [ Id::class => [ - [ [ ], Article::class, 'id' ], + [ serialize([ ]), Article::class, 'id' ], ], Serial::class => [ - [ [ ], Article::class, 'id' ], + [ serialize([ ]), Article::class, 'id' ], ], Varchar::class => [ - [ [ 'size' => 80 ], Article::class, 'title' ], + [ serialize([ 'size' => 80 ]), Article::class, 'title' ], ], Text::class => [ - [ [ ], Article::class, 'body' ], + [ serialize([ ]), Article::class, 'body' ], ] ], targetParameters: [ @@ -236,30 +236,30 @@ public function testForClass(): void $collection = new Collection( targetClasses: [ Index::class => [ - [ [ 'slug', 'unique' => true ], Article::class ], + [ serialize([ 'slug', 'unique' => true ]), Article::class ], ], Route::class => [ // trap - [ [ 'pattern' => '/articles' ], ArticleController::class ], + [ serialize([ 'pattern' => '/articles' ]), ArticleController::class ], ], ], targetMethods: [ Route::class => [ // trap - [ [ 'pattern' => '/recent' ], ArticleController::class, 'recent' ], + [ serialize([ 'pattern' => '/recent' ]), ArticleController::class, 'recent' ], ], ], targetProperties: [ Id::class => [ - [ [ ], Article::class, 'id' ], + [ serialize([ ]), Article::class, 'id' ], ], Serial::class => [ - [ [ ], Article::class, 'id' ], + [ serialize([ ]), Article::class, 'id' ], ], Varchar::class => [ - [ [ 'size' => 80 ], Article::class, 'title' ], - [ [ 'size' => 80 ], Article::class, 'slug' ], + [ serialize([ 'size' => 80 ]), Article::class, 'title' ], + [ serialize([ 'size' => 80 ]), Article::class, 'slug' ], ], Text::class => [ - [ [ ], Article::class, 'body' ], + [ serialize([ ]), Article::class, 'body' ], ] ], targetParameters: [ diff --git a/tests/CollectorTestAbstract.php b/tests/CollectorTestAbstract.php index 3a94581..61038a1 100644 --- a/tests/CollectorTestAbstract.php +++ b/tests/CollectorTestAbstract.php @@ -347,6 +347,24 @@ public function testFilterTargetMethods(): void ], $this->collectMethods($actual)); } + /** + * @requires PHP >= 8.1 + */ + public function testNestedAttributes81(): void + { + $expected = [ + new TargetClass( + new \Acme81\Attribute\SampleNested(new \Acme81\Attribute\SampleNestedValue(1)), + \Acme81\PSR4\Presentation\ArticleController::class, + ) + ]; + $actual = Attributes::filterTargetClasses( + Attributes::predicateForAttributeInstanceOf(\Acme81\Attribute\SampleNested::class) + ); + + $this->assertEquals($expected, $actual); + } + /** * @requires PHP >= 8.1 */