Skip to content

Commit 3da4803

Browse files
authored
Add filterTargetClasses and filterTargetMethods (#12)
1 parent 91e614e commit 3da4803

File tree

11 files changed

+383
-67
lines changed

11 files changed

+383
-67
lines changed

MIGRATION.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,31 @@
11
# Migration
22

3+
## v1.1 to v1.2
4+
5+
### New Requirements
6+
7+
None
8+
9+
### New features
10+
11+
- [#11](https://github.com/olvlvl/composer-attribute-collector/pull/11) Attribute instantiation errors are decorated to help find origin (@withinboredom @olvlvl)
12+
- [#12](https://github.com/olvlvl/composer-attribute-collector/pull/12) `Attributes::filterTargetClasses()` can filter target classes using a predicate (@olvlvl)
13+
- [#12](https://github.com/olvlvl/composer-attribute-collector/pull/12) `Attributes::filterTargetMethods()` can filter target methods using a predicate (@olvlvl)
14+
15+
### Backward Incompatible Changes
16+
17+
None
18+
19+
### Deprecated Features
20+
21+
None
22+
23+
### Other Changes
24+
25+
None
26+
27+
28+
329
## v1.0 to v1.1
430

531
### New Requirements

README.md

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,22 @@ require_once 'vendor/attributes.php'; // <-- the file created by the plugin
3939
foreach (Attributes::findTargetClasses(AsMessageHandler::class) as $target) {
4040
// $target->attribute is an instance of the specified attribute
4141
// with the actual data.
42-
var_dump($target->name, $target->attribute);
42+
var_dump($target->attribute, $target->name);
4343
}
4444

4545
// Find the target methods of the Route attribute.
4646
foreach (Attributes::findTargetMethods(Route::class) as $target) {
47-
var_dump($target->class, $target->name, $target->attribute);
47+
var_dump($target->attribute, $target->class, $target->name);
4848
}
4949

50-
// Find attributes for the ArticleController class.
50+
// Filter target methods using a predicate.
51+
foreach (Attributes::filterTargetMethods(
52+
fn($attribute) => is_a($attribute, Route::class, true)
53+
) as $target) {
54+
var_dump($target->attribute, $target->class, $target->name);
55+
}
56+
57+
// Find class and method attributes for the ArticleController class.
5158
$attributes = Attributes::forClass(ArticleController::class);
5259

5360
var_dump($attributes->classAttributes);
@@ -289,6 +296,41 @@ final class IsAdmin implements Voter
289296
}
290297
```
291298

299+
## Using Attributes
300+
301+
### Filtering target methods
302+
303+
`filterTargetMethods()` can filter target methods using a predicate. This can be helpful when a number of attributes extend another one, and you are interested in collecting any instance of that attribute.
304+
305+
Let's say we have a `Route` attribute extended by `Get`, `Post`, `Put`
306+
307+
```php
308+
<?php
309+
310+
use olvlvl\ComposerAttributeCollector\Attributes;
311+
312+
/** @var TargetMethod<Route>[] $target_methods */
313+
$target_methods = [
314+
...Attributes::findTargetMethods(Get::class),
315+
...Attributes::findTargetMethods(Head::class),
316+
...Attributes::findTargetMethods(Post::class),
317+
...Attributes::findTargetMethods(Put::class),
318+
...Attributes::findTargetMethods(Delete::class),
319+
...Attributes::findTargetMethods(Connect::class),
320+
...Attributes::findTargetMethods(Options::class),
321+
...Attributes::findTargetMethods(Trace::class),
322+
...Attributes::findTargetMethods(Patch::class),
323+
...Attributes::findTargetMethods(Route::class),
324+
];
325+
326+
// Can be replaced by:
327+
328+
/** @var TargetMethod<Route>[] $target_methods */
329+
$target_methods = Attributes::filterTargetMethods(
330+
fn($attribute) => is_a($attribute, Route::class, true)
331+
);
332+
```
333+
292334

293335

294336
----------

composer.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@
2020
"psr-4": {
2121
"tests\\olvlvl\\ComposerAttributeCollector\\": "tests",
2222
"Acme\\": "tests/Acme"
23-
}
23+
},
24+
"classmap": [
25+
"tests/Acme/ClassMap"
26+
]
2427
},
2528
"config": {
2629
"sort-packages": true

src/Attributes.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,26 @@ public static function findTargetMethods(string $attribute): array
5454
return self::getCollection()->findTargetMethods($attribute);
5555
}
5656

57+
/**
58+
* @param callable(class-string $attribute, class-string $class):bool $predicate
59+
*
60+
* @return array<TargetClass<object>>
61+
*/
62+
public static function filterTargetClasses(callable $predicate): array
63+
{
64+
return self::getCollection()->filterTargetClasses($predicate);
65+
}
66+
67+
/**
68+
* @param callable(class-string $attribute, class-string $class, string $method):bool $predicate
69+
*
70+
* @return array<TargetMethod<object>>
71+
*/
72+
public static function filterTargetMethods(callable $predicate): array
73+
{
74+
return self::getCollection()->filterTargetMethods($predicate);
75+
}
76+
5777
/**
5878
* @var array<class-string, ForClass>
5979
*/

src/Collection.php

Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public function __construct(
3838
*
3939
* @param class-string<T> $attribute
4040
*
41-
* @return TargetClass<T>[]
41+
* @return array<TargetClass<T>>
4242
*/
4343
public function findTargetClasses(string $attribute): array
4444
{
@@ -74,7 +74,7 @@ private static function createClassAttribute(string $attribute, array $arguments
7474
*
7575
* @param class-string<T> $attribute
7676
*
77-
* @return TargetMethod<T>[]
77+
* @return array<TargetMethod<T>>
7878
*/
7979
public function findTargetMethods(string $attribute): array
8080
{
@@ -84,6 +84,51 @@ public function findTargetMethods(string $attribute): array
8484
);
8585
}
8686

87+
/**
88+
* @param callable(class-string $attribute, class-string $class):bool $predicate
89+
*
90+
* @return array<TargetClass<object>>
91+
*/
92+
public function filterTargetClasses(callable $predicate): array
93+
{
94+
$ar = [];
95+
96+
foreach ($this->targetClasses as $attribute => $references) {
97+
foreach ($references as [ $arguments, $class ]) {
98+
if ($predicate($attribute, $class)) {
99+
$ar[] = new TargetClass(self::createClassAttribute($attribute, $arguments, $class), $class);
100+
}
101+
}
102+
}
103+
104+
return $ar;
105+
}
106+
107+
/**
108+
* @param callable(class-string $attribute, class-string $class, string $method):bool $predicate
109+
*
110+
* @return array<TargetMethod<object>>
111+
*/
112+
public function filterTargetMethods(callable $predicate): array
113+
{
114+
$ar = [];
115+
116+
foreach ($this->targetMethods as $attribute => $references) {
117+
foreach ($references as [ $arguments, $class, $method ]) {
118+
if ($predicate($attribute, $class, $method)) {
119+
$ar[] = new TargetMethod(self::createMethodAttribute(
120+
$attribute,
121+
$arguments,
122+
$class,
123+
$method
124+
), $class, $method);
125+
}
126+
}
127+
}
128+
129+
return $ar;
130+
}
131+
87132
/**
88133
* @template T of object
89134
*
@@ -111,38 +156,19 @@ private static function createMethodAttribute(
111156

112157
/**
113158
* @param class-string $class
114-
*
115-
* @return ForClass
116159
*/
117160
public function forClass(string $class): ForClass
118161
{
119162
$classAttributes = [];
120163

121-
foreach ($this->targetClasses as $attribute => $references) {
122-
foreach ($references as [ $arguments, $targetClass ]) {
123-
if ($targetClass != $class) {
124-
continue;
125-
}
126-
127-
$classAttributes[] = self::createClassAttribute($attribute, $arguments, $class);
128-
}
164+
foreach ($this->filterTargetClasses(fn($a, $c): bool => $c === $class) as $targetClass) {
165+
$classAttributes[] = $targetClass->attribute;
129166
}
130167

131168
$methodAttributes = [];
132169

133-
foreach ($this->targetMethods as $attribute => $references) {
134-
foreach ($references as [ $arguments, $targetClass, $targetMethod ]) {
135-
if ($targetClass != $class) {
136-
continue;
137-
}
138-
139-
$methodAttributes[$targetMethod][] = self::createMethodAttribute(
140-
$attribute,
141-
$arguments,
142-
$class,
143-
$targetMethod
144-
);
145-
}
170+
foreach ($this->filterTargetMethods(fn($a, $c): bool => $c === $class) as $targetMethod) {
171+
$methodAttributes[$targetMethod->name][] = $targetMethod->attribute;
146172
}
147173

148174
return new ForClass($classAttributes, $methodAttributes);

tests/Acme/Attribute/Get.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Acme\Attribute;
4+
5+
use Attribute;
6+
7+
#[Attribute(Attribute::TARGET_METHOD)]
8+
class Get extends Route
9+
{
10+
public function __construct(
11+
string $pattern = '',
12+
?string $id = null
13+
) {
14+
parent::__construct($pattern, 'GET', $id);
15+
}
16+
}

tests/Acme/Attribute/Post.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Acme\Attribute;
4+
5+
use Attribute;
6+
7+
#[Attribute(Attribute::TARGET_METHOD)]
8+
class Post extends Route
9+
{
10+
public function __construct(
11+
string $pattern = '',
12+
?string $id = null
13+
) {
14+
parent::__construct($pattern, 'POST', $id);
15+
}
16+
}

tests/Acme/Attribute/Route.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111

1212
use Attribute;
1313

14-
#[Attribute(Attribute::TARGET_METHOD)]
15-
final class Route
14+
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
15+
class Route
1616
{
1717
/**
1818
* @param string|string[] $method

tests/Acme/ClassMap/controllers.php

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,29 +9,32 @@
99

1010
namespace Acme\Presentation;
1111

12+
use Acme\Attribute\Get;
1213
use Acme\Attribute\Route;
1314

15+
#[Route('/images')]
1416
final class ImageController
1517
{
16-
#[Route("/images")]
18+
#[Get]
1719
protected function list(): void
1820
{
1921
}
2022

21-
#[Route("/images/{id}")]
23+
#[Get("/{id}")]
2224
private function show(int $id): void
2325
{
2426
}
2527
}
2628

29+
#[Route('/files')]
2730
final class FileController
2831
{
29-
#[Route("/files")]
32+
#[Get]
3033
public function list(): void
3134
{
3235
}
3336

34-
#[Route("/files/{id}")]
37+
#[Get('/{id}')]
3538
public function show(int $id): void
3639
{
3740
}

0 commit comments

Comments
 (0)