Skip to content

Commit a925621

Browse files
committed
Serialize attribute arguments
1 parent ca67a62 commit a925621

File tree

9 files changed

+117
-52
lines changed

9 files changed

+117
-52
lines changed

CHANGELOG.md

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# CHANGELOG
22

3-
## v2.0.2
3+
## v2.X.0
44

55
### New Requirements
66

@@ -10,14 +10,38 @@ None
1010

1111
None
1212

13+
### Backward Incompatible Changes
14+
15+
None
16+
1317
### Deprecated Features
1418

1519
None
1620

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+
27+
## v2.0.2
28+
29+
### New Requirements
30+
31+
None
32+
33+
### New features
34+
35+
None
36+
1737
### Backward Incompatible Changes
1838

1939
None
2040

41+
### Deprecated Features
42+
43+
None
44+
2145
### Other Changes
2246

2347
- Fix PHP 8.4 deprecation notice "Implicitly marking parameter * as nullable is deprecated."
@@ -48,7 +72,7 @@ None
4872
- #26 Fix enum support on PHP < 8.2.0 – @mnavarrocarter
4973

5074

51-
## v1.2 to v2.0
75+
## v2.0.0
5276

5377
### New Requirements
5478

src/Collection.php

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@
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.
24+
* where `0` is the serialized attribute arguments, `1` is a target class, and `2` is the target property.
2525
*/
2626
public function __construct(
2727
private array $targetClasses,
@@ -49,15 +49,15 @@ public function findTargetClasses(string $attribute): array
4949
* @template T of object
5050
*
5151
* @param class-string<T> $attribute
52-
* @param array<mixed> $arguments
52+
* @param string $arguments The serialized arguments
5353
* @param class-string $class
5454
*
5555
* @return TargetClass<T>
5656
*/
57-
private static function createClassAttribute(string $attribute, array $arguments, string $class): object
57+
private static function createClassAttribute(string $attribute, string $arguments, string $class): object
5858
{
5959
try {
60-
$a = new $attribute(...$arguments);
60+
$a = new $attribute(...unserialize($arguments));
6161
return new TargetClass($a, $class);
6262
} catch (Throwable $e) {
6363
throw new RuntimeException(
@@ -86,20 +86,20 @@ public function findTargetMethods(string $attribute): array
8686
* @template T of object
8787
*
8888
* @param class-string<T> $attribute
89-
* @param array<mixed> $arguments
90-
* @param class-string $class
89+
* @param string $arguments The serialized arguments
90+
* @param class-string $class
9191
* @param non-empty-string $method
9292
*
9393
* @return TargetMethod<T>
9494
*/
9595
private static function createMethodAttribute(
9696
string $attribute,
97-
array $arguments,
97+
string $arguments,
9898
string $class,
9999
string $method,
100100
): object {
101101
try {
102-
$a = new $attribute(...$arguments);
102+
$a = new $attribute(...unserialize($arguments));
103103
return new TargetMethod($a, $class, $method);
104104
} catch (Throwable $e) {
105105
throw new RuntimeException(
@@ -128,20 +128,20 @@ public function findTargetProperties(string $attribute): array
128128
* @template T of object
129129
*
130130
* @param class-string<T> $attribute
131-
* @param array<mixed> $arguments
131+
* @param string $arguments The serialized arguments
132132
* @param class-string $class
133133
* @param non-empty-string $property
134134
*
135135
* @return TargetProperty<T>
136136
*/
137137
private static function createPropertyAttribute(
138138
string $attribute,
139-
array $arguments,
139+
string $arguments,
140140
string $class,
141141
string $property,
142142
): object {
143143
try {
144-
$a = new $attribute(...$arguments);
144+
$a = new $attribute(...unserialize($arguments));
145145
return new TargetProperty($a, $class, $property);
146146
} catch (Throwable $e) {
147147
throw new RuntimeException(

src/Plugin.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ final class Plugin implements PluginInterface, EventSubscriberInterface
2828
{
2929
public const CACHE_DIR = '.composer-attribute-collector';
3030
public const VERSION_MAJOR = 2;
31-
public const VERSION_MINOR = 0;
31+
public const VERSION_MINOR = 1;
3232

3333
/**
3434
* @uses onPostAutoloadDump

src/TransientCollectionRenderer.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,17 @@ private static function targetsToCode(iterable $targetByClass): string
4747
* //phpcs:disable Generic.Files.LineLength.TooLong
4848
* @param iterable<class-string, iterable<TransientTargetClass|TransientTargetMethod|TransientTargetProperty>> $targetByClass
4949
*
50-
* @return array<class-string, array<array{ array<int|string, mixed>, class-string, 2?:non-empty-string }>>
50+
* @return array<class-string, array<array{ string, class-string, 2?:non-empty-string }>>
51+
* Where _key_ is an attribute class and _value_ the attribute targets,
52+
* where `0` is the attribute serialized arguments, `1` the target class, and `2` the method or property name.
5153
*/
5254
private static function targetsToArray(iterable $targetByClass): array
5355
{
5456
$by = [];
5557

5658
foreach ($targetByClass as $class => $targets) {
5759
foreach ($targets as $t) {
58-
$a = [ $t->arguments, $class ];
60+
$a = [ serialize($t->arguments), $class ];
5961

6062
if ($t instanceof TransientTargetMethod || $t instanceof TransientTargetProperty) {
6163
$a[] = $t->name;
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 SampleComplex
9+
{
10+
public function __construct(
11+
public SampleComplexValue $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 SampleComplexValue
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\SampleComplex;
10+
use Acme81\Attribute\SampleComplexValue;
911

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

tests/CollectionTest.php

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,17 @@ public function testInstantiationErrorIsDecorated(string $expectedMessage, Closu
4040
$collection = new Collection(
4141
targetClasses: [
4242
Permission::class => [
43-
[ [ 'Permission' => 'is_admin' ], DeleteMenu::class ],
43+
[ serialize([ 'Permission' => 'is_admin' ]), DeleteMenu::class ],
4444
]
4545
],
4646
targetMethods: [
4747
Route::class => [
48-
[ [ 'Method' => 'GET' ], ArticleController::class, 'list' ],
48+
[ serialize([ 'Method' => 'GET' ]), ArticleController::class, 'list' ],
4949
]
5050
],
5151
targetProperties: [
5252
Serial::class => [
53-
[ [ 'Primary' => true ], Article::class, 'id' ],
53+
[ serialize([ 'Primary' => true ]), Article::class, 'id' ],
5454
]
5555
]
5656
);
@@ -96,9 +96,9 @@ public function testFilterTargetClasses(): void
9696
$collection = new Collection(
9797
targetClasses: [
9898
Route::class => [
99-
[ [ 'pattern' => '/articles' ], ArticleController::class ],
100-
[ [ 'pattern' => '/images' ], ImageController::class ],
101-
[ [ 'pattern' => '/files' ], FileController::class ],
99+
[ serialize([ 'pattern' => '/articles' ]), ArticleController::class ],
100+
[ serialize([ 'pattern' => '/images' ]), ImageController::class ],
101+
[ serialize([ 'pattern' => '/files' ]), FileController::class ],
102102
],
103103
],
104104
targetMethods: [
@@ -124,13 +124,13 @@ public function testFilterTargetMethods(): void
124124
],
125125
targetMethods: [
126126
Route::class => [
127-
[ [ 'pattern' => '/recent' ], ArticleController::class, 'recent' ],
127+
[ serialize([ 'pattern' => '/recent' ]), ArticleController::class, 'recent' ],
128128
],
129129
Get::class => [
130-
[ [ ], ArticleController::class, 'show' ],
130+
[ serialize([ ]), ArticleController::class, 'show' ],
131131
],
132132
Post::class => [
133-
[ [ ], ArticleController::class, 'create' ],
133+
[ serialize([ ]), ArticleController::class, 'create' ],
134134
],
135135
],
136136
targetProperties: [
@@ -153,27 +153,27 @@ public function testFilterTargetProperties(): void
153153
],
154154
targetMethods: [
155155
Route::class => [
156-
[ [ 'pattern' => '/recent' ], ArticleController::class, 'recent' ],
156+
[ serialize([ 'pattern' => '/recent' ]), ArticleController::class, 'recent' ],
157157
],
158158
Get::class => [
159-
[ [ ], ArticleController::class, 'show' ],
159+
[ serialize([ ]), ArticleController::class, 'show' ],
160160
],
161161
Post::class => [
162-
[ [ ], ArticleController::class, 'create' ],
162+
[ serialize([ ]), ArticleController::class, 'create' ],
163163
],
164164
],
165165
targetProperties: [
166166
Id::class => [
167-
[ [ ], Article::class, 'id' ],
167+
[ serialize([ ]), Article::class, 'id' ],
168168
],
169169
Serial::class => [
170-
[ [ ], Article::class, 'id' ],
170+
[ serialize([ ]), Article::class, 'id' ],
171171
],
172172
Varchar::class => [
173-
[ [ 'size' => 80 ], Article::class, 'title' ],
173+
[ serialize([ 'size' => 80 ]), Article::class, 'title' ],
174174
],
175175
Text::class => [
176-
[ [ ], Article::class, 'body' ],
176+
[ serialize([ ]), Article::class, 'body' ],
177177
]
178178
]
179179
);
@@ -195,30 +195,30 @@ public function testForClass(): void
195195
$collection = new Collection(
196196
targetClasses: [
197197
Index::class => [
198-
[ [ 'slug', 'unique' => true ], Article::class ],
198+
[ serialize([ 'slug', 'unique' => true ]), Article::class ],
199199
],
200200
Route::class => [ // trap
201-
[ [ 'pattern' => '/articles' ], ArticleController::class ],
201+
[ serialize([ 'pattern' => '/articles' ]), ArticleController::class ],
202202
],
203203
],
204204
targetMethods: [
205205
Route::class => [ // trap
206-
[ [ 'pattern' => '/recent' ], ArticleController::class, 'recent' ],
206+
[ serialize([ 'pattern' => '/recent' ]), ArticleController::class, 'recent' ],
207207
],
208208
],
209209
targetProperties: [
210210
Id::class => [
211-
[ [ ], Article::class, 'id' ],
211+
[ serialize([ ]), Article::class, 'id' ],
212212
],
213213
Serial::class => [
214-
[ [ ], Article::class, 'id' ],
214+
[ serialize([ ]), Article::class, 'id' ],
215215
],
216216
Varchar::class => [
217-
[ [ 'size' => 80 ], Article::class, 'title' ],
218-
[ [ 'size' => 80 ], Article::class, 'slug' ],
217+
[ serialize([ 'size' => 80 ]), Article::class, 'title' ],
218+
[ serialize([ 'size' => 80 ]), Article::class, 'slug' ],
219219
],
220220
Text::class => [
221-
[ [ ], Article::class, 'body' ],
221+
[ serialize([ ]), Article::class, 'body' ],
222222
]
223223
]
224224
);

tests/PluginTest.php

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
11
<?php
22

3-
/*
4-
* (c) Olivier Laviale <olivier.laviale@gmail.com>
5-
*
6-
* For the full copyright and license information, please view the LICENSE
7-
* file that was distributed with this source code.
8-
*/
9-
103
namespace tests\olvlvl\ComposerAttributeCollector;
114

125
use Acme\Attribute\ActiveRecord\Boolean;
@@ -264,6 +257,24 @@ public function testFilterTargetMethods(): void
264257
], $this->collectMethods($actual));
265258
}
266259

260+
/**
261+
* @requires PHP >= 8.1
262+
*/
263+
public function testComplexTypes81(): void
264+
{
265+
$expected = [
266+
new TargetClass(
267+
new \Acme81\Attribute\SampleComplex(new \Acme81\Attribute\SampleComplexValue(1)),
268+
\Acme81\PSR4\Presentation\ArticleController::class,
269+
)
270+
];
271+
$actual = Attributes::filterTargetClasses(
272+
Attributes::predicateForAttributeInstanceOf(\Acme81\Attribute\SampleComplex::class)
273+
);
274+
275+
$this->assertEquals($expected, $actual);
276+
}
277+
267278
/**
268279
* @requires PHP >= 8.1
269280
*/

0 commit comments

Comments
 (0)