Skip to content

Commit 6e954f3

Browse files
committed
Serialize attribute arguments
1 parent 8f48126 commit 6e954f3

File tree

8 files changed

+123
-53
lines changed

8 files changed

+123
-53
lines changed

CHANGELOG.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,29 @@
11
# CHANGELOG
22

3+
## v2.2.0
4+
5+
### New Requirements
6+
7+
None
8+
9+
### New features
10+
11+
None
12+
13+
### Deprecated Features
14+
15+
None
16+
17+
### Backward Incompatible Changes
18+
19+
None
20+
21+
### Other Changes
22+
23+
Attribute arguments are now serialized to support [nested attributes introduced in PHP 8.1](https://wiki.php.net/rfc/new_in_initializers).
24+
25+
26+
327
## v2.1.0
428

529
### New Requirements
@@ -97,7 +121,6 @@ None
97121
- #26 Fix enum support on PHP < 8.2.0 (@mnavarrocarter)
98122

99123

100-
101124
## v2.0.0
102125

103126
### New Requirements

src/Collection.php

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,18 @@
1313
final class Collection
1414
{
1515
/**
16-
* @param array<class-string, array<array{ mixed[], class-string }>> $targetClasses
16+
* @param array<class-string, array<array{ string, class-string }>> $targetClasses
1717
* Where _key_ is an attribute class and _value_ an array of arrays
18-
* where 0 are the attribute arguments and 1 is a target class.
19-
* @param array<class-string, array<array{ mixed[], class-string, non-empty-string }>> $targetMethods
18+
* where `0` is the serialized attribute arguments and `1` is a target class.
19+
* @param array<class-string, array<array{ string, class-string, non-empty-string }>> $targetMethods
2020
* Where _key_ is an attribute class and _value_ an array of arrays
21-
* where 0 are the attribute arguments, 1 is a target class, and 2 is the target method.
22-
* @param array<class-string, array<array{ mixed[], class-string, non-empty-string }>> $targetProperties
21+
* where `0` is the serialized attribute arguments, `1` is a target class, and `2` is the target method.
22+
* @param array<class-string, array<array{ string, class-string, non-empty-string }>> $targetProperties
2323
* Where _key_ is an attribute class and _value_ an array of arrays
24-
* where 0 are the attribute arguments, 1 is a target class, and 2 is the target property.
25-
* @param array<class-string, array<array{ mixed[], class-string, non-empty-string, non-empty-string }>> $targetParameters
26-
* Where _key_ is an attribute class and _value_ an array of arrays where 0 are the
27-
* attribute arguments, 1 is a target class, 2 is the target method, and 3 is the target parameter.
24+
* where `0` is the serialized attribute arguments, `1` is a target class, and `2` is the target property.
25+
* @param array<class-string, array<array{ string, class-string, non-empty-string, non-empty-string }>> $targetParameters
26+
* Where _key_ is an attribute class and _value_ an array of arrays
27+
* where 0 are the serialized attribute arguments, `1` is a target class, `2` is the target method, and `3` is the target parameter.
2828
*/
2929
public function __construct(
3030
private array $targetClasses,
@@ -53,15 +53,15 @@ public function findTargetClasses(string $attribute): array
5353
* @template T of object
5454
*
5555
* @param class-string<T> $attribute
56-
* @param array<mixed> $arguments
56+
* @param string $arguments The serialized arguments
5757
* @param class-string $class
5858
*
5959
* @return TargetClass<T>
6060
*/
61-
private static function createClassAttribute(string $attribute, array $arguments, string $class): object
61+
private static function createClassAttribute(string $attribute, string $arguments, string $class): object
6262
{
6363
try {
64-
$a = new $attribute(...$arguments);
64+
$a = new $attribute(...unserialize($arguments));
6565
return new TargetClass($a, $class);
6666
} catch (Throwable $e) {
6767
throw new RuntimeException(
@@ -90,20 +90,20 @@ public function findTargetMethods(string $attribute): array
9090
* @template T of object
9191
*
9292
* @param class-string<T> $attribute
93-
* @param array<mixed> $arguments
93+
* @param string $arguments The serialized arguments
9494
* @param class-string $class
9595
* @param non-empty-string $method
9696
*
9797
* @return TargetMethod<T>
9898
*/
9999
private static function createMethodAttribute(
100100
string $attribute,
101-
array $arguments,
101+
string $arguments,
102102
string $class,
103103
string $method,
104104
): object {
105105
try {
106-
$a = new $attribute(...$arguments);
106+
$a = new $attribute(...unserialize($arguments));
107107
return new TargetMethod($a, $class, $method);
108108
} catch (Throwable $e) {
109109
throw new RuntimeException(
@@ -132,7 +132,7 @@ public function findTargetParameters(string $attribute): array
132132
* @template T of object
133133
*
134134
* @param class-string<T> $attribute
135-
* @param array<mixed> $arguments
135+
* @param string $arguments The serialized arguments
136136
* @param class-string $class
137137
* @param non-empty-string $method
138138
* @param non-empty-string $parameter
@@ -141,13 +141,13 @@ public function findTargetParameters(string $attribute): array
141141
*/
142142
private static function createParameterAttribute(
143143
string $attribute,
144-
array $arguments,
144+
string $arguments,
145145
string $class,
146146
string $method,
147147
string $parameter,
148148
): object {
149149
try {
150-
$a = new $attribute(...$arguments);
150+
$a = new $attribute(...unserialize($arguments));
151151
return new TargetParameter($a, $class, $method, $parameter);
152152
} catch (Throwable $e) {
153153
throw new RuntimeException(
@@ -176,20 +176,20 @@ public function findTargetProperties(string $attribute): array
176176
* @template T of object
177177
*
178178
* @param class-string<T> $attribute
179-
* @param array<mixed> $arguments
179+
* @param string $arguments The serialized arguments
180180
* @param class-string $class
181181
* @param non-empty-string $property
182182
*
183183
* @return TargetProperty<T>
184184
*/
185185
private static function createPropertyAttribute(
186186
string $attribute,
187-
array $arguments,
187+
string $arguments,
188188
string $class,
189189
string $property,
190190
): object {
191191
try {
192-
$a = new $attribute(...$arguments);
192+
$a = new $attribute(...unserialize($arguments));
193193
return new TargetProperty($a, $class, $property);
194194
} catch (Throwable $e) {
195195
throw new RuntimeException(

src/TransientCollectionRenderer.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,19 +52,20 @@ private static function targetsToCode(iterable $targetByClass): string
5252
* @param iterable<class-string, iterable<TransientTargetClass|TransientTargetMethod|TransientTargetParameter|TransientTargetProperty>> $targetByClass
5353
*
5454
* @return array<class-string, array<array{
55-
* array<int|string, mixed>,
55+
* string,
5656
* class-string,
5757
* 2?:non-empty-string,
5858
* 3?:non-empty-string
59-
* }>>
59+
* }>> Where _key_ is an attribute class and _value_ is an array of parameters,
60+
* where `1` is the serialized arguments and `2` is the target class.
6061
*/
6162
private static function targetsToArray(iterable $targetByClass): array
6263
{
6364
$by = [];
6465

6566
foreach ($targetByClass as $class => $targets) {
6667
foreach ($targets as $t) {
67-
$a = [ $t->arguments, $class ];
68+
$a = [ serialize($t->arguments), $class ];
6869

6970
if ($t instanceof TransientTargetParameter) {
7071
$a[] = $t->method;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace Acme81\Attribute;
4+
5+
use Attribute;
6+
7+
#[Attribute(Attribute::TARGET_CLASS)]
8+
class SampleNested
9+
{
10+
public function __construct(
11+
public SampleNestedValue $value,
12+
) {
13+
}
14+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Acme81\Attribute;
4+
5+
class SampleNestedValue
6+
{
7+
public function __construct(
8+
public int $value
9+
) {
10+
}
11+
}

tests/Acme81/PSR4/Presentation/ArticleController.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66
use Acme81\Attribute\Method;
77
use Acme81\Attribute\Post;
88
use Acme81\Attribute\Route;
9+
use Acme81\Attribute\SampleNested;
10+
use Acme81\Attribute\SampleNestedValue;
911

1012
#[Route('/articles')]
13+
#[SampleNested(new SampleNestedValue(1))]
1114
class ArticleController
1215
{
1316
#[Route('/:id', method: Method::GET)]

tests/CollectionTest.php

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,17 @@ public function testInstantiationErrorIsDecorated(string $expectedMessage, Closu
4343
$collection = new Collection(
4444
targetClasses: [
4545
Permission::class => [
46-
[ [ 'Permission' => 'is_admin' ], DeleteMenu::class ],
46+
[ serialize([ 'Permission' => 'is_admin' ]), DeleteMenu::class ],
4747
]
4848
],
4949
targetMethods: [
5050
Route::class => [
51-
[ [ 'Method' => 'GET' ], ArticleController::class, 'list' ],
51+
[ serialize([ 'Method' => 'GET' ]), ArticleController::class, 'list' ],
5252
]
5353
],
5454
targetProperties: [
5555
Serial::class => [
56-
[ [ 'Primary' => true ], Article::class, 'id' ],
56+
[ serialize([ 'Primary' => true ]), Article::class, 'id' ],
5757
]
5858
],
5959
targetParameters: [
@@ -101,9 +101,9 @@ public function testFilterTargetClasses(): void
101101
$collection = new Collection(
102102
targetClasses: [
103103
Route::class => [
104-
[ [ 'pattern' => '/articles' ], ArticleController::class ],
105-
[ [ 'pattern' => '/images' ], ImageController::class ],
106-
[ [ 'pattern' => '/files' ], FileController::class ],
104+
[ serialize([ 'pattern' => '/articles' ]), ArticleController::class ],
105+
[ serialize([ 'pattern' => '/images' ]), ImageController::class ],
106+
[ serialize([ 'pattern' => '/files' ]), FileController::class ],
107107
],
108108
],
109109
targetMethods: [
@@ -131,13 +131,13 @@ public function testFilterTargetMethods(): void
131131
],
132132
targetMethods: [
133133
Route::class => [
134-
[ [ 'pattern' => '/recent' ], ArticleController::class, 'recent' ],
134+
[ serialize([ 'pattern' => '/recent' ]), ArticleController::class, 'recent' ],
135135
],
136136
Get::class => [
137-
[ [ ], ArticleController::class, 'show' ],
137+
[ serialize([ ]), ArticleController::class, 'show' ],
138138
],
139139
Post::class => [
140-
[ [ ], ArticleController::class, 'create' ],
140+
[ serialize([ ]), ArticleController::class, 'create' ],
141141
],
142142
],
143143
targetProperties: [
@@ -166,12 +166,12 @@ public function testFilterTargetParameters(): void
166166
],
167167
targetParameters: [
168168
ParameterA::class => [
169-
[ [ 'a' ], ArticleController::class, 'myMethod', 'myParamA', ],
170-
[ [ 'a2' ], ArticleController::class, 'myMethod', 'myParamA2' ],
171-
[ [ 'a3' ], ArticleController::class, 'myFoo', 'fooParam' ],
169+
[ serialize([ 'a' ]), ArticleController::class, 'myMethod', 'myParamA', ],
170+
[ serialize([ 'a2' ]), ArticleController::class, 'myMethod', 'myParamA2' ],
171+
[ serialize([ 'a3' ]), ArticleController::class, 'myFoo', 'fooParam' ],
172172
],
173173
ParameterB::class => [
174-
[ [ 'b', 'more data'], ArticleController::class, 'myMethod', 'myParamB' ],
174+
[ serialize([ 'b', 'more data']), ArticleController::class, 'myMethod', 'myParamB' ],
175175
],
176176
]
177177
);
@@ -192,27 +192,27 @@ public function testFilterTargetProperties(): void
192192
],
193193
targetMethods: [
194194
Route::class => [
195-
[ [ 'pattern' => '/recent' ], ArticleController::class, 'recent' ],
195+
[ serialize([ 'pattern' => '/recent' ]), ArticleController::class, 'recent' ],
196196
],
197197
Get::class => [
198-
[ [ ], ArticleController::class, 'show' ],
198+
[ serialize([ ]), ArticleController::class, 'show' ],
199199
],
200200
Post::class => [
201-
[ [ ], ArticleController::class, 'create' ],
201+
[ serialize([ ]), ArticleController::class, 'create' ],
202202
],
203203
],
204204
targetProperties: [
205205
Id::class => [
206-
[ [ ], Article::class, 'id' ],
206+
[ serialize([ ]), Article::class, 'id' ],
207207
],
208208
Serial::class => [
209-
[ [ ], Article::class, 'id' ],
209+
[ serialize([ ]), Article::class, 'id' ],
210210
],
211211
Varchar::class => [
212-
[ [ 'size' => 80 ], Article::class, 'title' ],
212+
[ serialize([ 'size' => 80 ]), Article::class, 'title' ],
213213
],
214214
Text::class => [
215-
[ [ ], Article::class, 'body' ],
215+
[ serialize([ ]), Article::class, 'body' ],
216216
]
217217
],
218218
targetParameters: [
@@ -236,30 +236,30 @@ public function testForClass(): void
236236
$collection = new Collection(
237237
targetClasses: [
238238
Index::class => [
239-
[ [ 'slug', 'unique' => true ], Article::class ],
239+
[ serialize([ 'slug', 'unique' => true ]), Article::class ],
240240
],
241241
Route::class => [ // trap
242-
[ [ 'pattern' => '/articles' ], ArticleController::class ],
242+
[ serialize([ 'pattern' => '/articles' ]), ArticleController::class ],
243243
],
244244
],
245245
targetMethods: [
246246
Route::class => [ // trap
247-
[ [ 'pattern' => '/recent' ], ArticleController::class, 'recent' ],
247+
[ serialize([ 'pattern' => '/recent' ]), ArticleController::class, 'recent' ],
248248
],
249249
],
250250
targetProperties: [
251251
Id::class => [
252-
[ [ ], Article::class, 'id' ],
252+
[ serialize([ ]), Article::class, 'id' ],
253253
],
254254
Serial::class => [
255-
[ [ ], Article::class, 'id' ],
255+
[ serialize([ ]), Article::class, 'id' ],
256256
],
257257
Varchar::class => [
258-
[ [ 'size' => 80 ], Article::class, 'title' ],
259-
[ [ 'size' => 80 ], Article::class, 'slug' ],
258+
[ serialize([ 'size' => 80 ]), Article::class, 'title' ],
259+
[ serialize([ 'size' => 80 ]), Article::class, 'slug' ],
260260
],
261261
Text::class => [
262-
[ [ ], Article::class, 'body' ],
262+
[ serialize([ ]), Article::class, 'body' ],
263263
]
264264
],
265265
targetParameters: [

tests/CollectorTestAbstract.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,24 @@ public function testFilterTargetMethods(): void
347347
], $this->collectMethods($actual));
348348
}
349349

350+
/**
351+
* @requires PHP >= 8.1
352+
*/
353+
public function testNestedAttributes81(): void
354+
{
355+
$expected = [
356+
new TargetClass(
357+
new \Acme81\Attribute\SampleNested(new \Acme81\Attribute\SampleNestedValue(1)),
358+
\Acme81\PSR4\Presentation\ArticleController::class,
359+
)
360+
];
361+
$actual = Attributes::filterTargetClasses(
362+
Attributes::predicateForAttributeInstanceOf(\Acme81\Attribute\SampleNested::class)
363+
);
364+
365+
$this->assertEquals($expected, $actual);
366+
}
367+
350368
/**
351369
* @requires PHP >= 8.1
352370
*/

0 commit comments

Comments
 (0)