Skip to content

Commit 4d0d31d

Browse files
committed
New mechanism to get reflector in attribute constructor.
1 parent 2eac328 commit 4d0d31d

6 files changed

Lines changed: 155 additions & 1 deletion

File tree

README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,44 @@ Provides mechanisms for attribute objects to know about the symbol they are atta
44

55
## Usage
66

7+
### Using AttributeConstructor::getReflector().
8+
9+
Create an attribute class that calls `AttributeConstructor::getReflector()` in the constructor.
10+
11+
```php
12+
use Ock\ReflectorAwareAttributes\AttributeConstructor;
13+
use Ock\ReflectorAwareAttributes\ReflectorAwareAttributeInterface;
14+
15+
#[\Attribute(\Attribute::TARGET_ALL)]
16+
class MyAttribute {
17+
18+
public readonly \Reflector $reflector;
19+
20+
public function __construct(): void {
21+
$this->reflector = AttributeConstructor::getReflector();
22+
}
23+
24+
}
25+
```
26+
27+
Attach the attribute to a class or other symbol.
28+
29+
```php
30+
#[MyAttribute]
31+
class MyClass {}
32+
```
33+
34+
Call `get_attributes()` to extract attributes instances from the class.
35+
36+
```php
37+
use function Ock\ReflectorAwareAttributes\get_attributes;
38+
39+
$reflection_class = new \ReflectionClass(MyClass::class);
40+
$attribute_instances = get_attributes($reflection_class, MyAttribute::class);
41+
assert($attribute_instances[0] instanceof MyAttribute);
42+
assert($attribute_instances[0]->reflector === $reflection_function);
43+
```
44+
745
### Using the interface with ->setReflector().
846

947
Create an attribute class that implements `ReflectorAwareAttributeInterface`.

functions/functions.attributes.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ function get_attributes(
3232
string $name,
3333
int $flags = \ReflectionAttribute::IS_INSTANCEOF,
3434
): array {
35-
$instances = get_raw_attributes($reflector, $name, $flags);
35+
$instances = AttributeConstructor::callWithReflector(
36+
fn () => get_raw_attributes($reflector, $name, $flags),
37+
$reflector,
38+
);
3639
foreach ($instances as $instance) {
3740
if ($instance instanceof ReflectorAwareAttributeInterface) {
3841
$instance->setReflector($reflector);

src/AttributeConstructor.php

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace Ock\ReflectorAwareAttributes;
6+
7+
/**
8+
* Static methods to be called from attribute constructors.
9+
*/
10+
class AttributeConstructor {
11+
12+
/**
13+
* Reflector that an attribute is attached to.
14+
*/
15+
private static \ReflectionClass|\ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty|\ReflectionClassConstant|null $reflector = null;
16+
17+
/**
18+
* @template TReturn
19+
*
20+
* @param \Closure(): TReturn $callback
21+
* @param \ReflectionClass|\ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty|\ReflectionClassConstant $reflector
22+
*
23+
* @return TReturn
24+
*/
25+
public static function callWithReflector(
26+
\Closure $callback,
27+
\ReflectionClass|\ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty|\ReflectionClassConstant $reflector,
28+
): mixed {
29+
try {
30+
self::$reflector = $reflector;
31+
return $callback();
32+
}
33+
finally {
34+
self::$reflector = null;
35+
}
36+
}
37+
38+
/**
39+
* Gets the reflector the current attribute is attached to.
40+
*
41+
* This should only be called from an attribute constructor that was invoked
42+
* with ::newAttributeInstance() above.
43+
*
44+
* @throws \LogicException
45+
* The method is called at the wrong time.
46+
*/
47+
public static function getReflector(): \ReflectionClass|\ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty|\ReflectionClassConstant {
48+
if (self::$reflector === null) {
49+
throw new \LogicException("This method can only be called from an attribute constructor, and only when the attribute is instantiated using the 'ock/reflector-aware-attributes' package.");
50+
}
51+
return self::$reflector;
52+
}
53+
54+
/**
55+
* Gets the reflector the current attribute is attached to, or NULL.
56+
*
57+
* This should only be called from an attribute constructor that was invoked
58+
* with ::newAttributeInstance() above.
59+
*/
60+
public static function getReflectorIfSet(): \ReflectionClass|\ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty|\ReflectionClassConstant|null {
61+
return self::$reflector;
62+
}
63+
64+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace Ock\ReflectorAwareAttributes\Tests\Fixtures\Attribute;
6+
7+
use Ock\ReflectorAwareAttributes\AttributeConstructor;
8+
9+
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_ALL)]
10+
class ReflectorAwareConstructorTestAttribute {
11+
12+
public readonly ?\Reflector $reflectorIfSet;
13+
14+
public readonly ?\Reflector $reflector;
15+
16+
public readonly ?\LogicException $exception;
17+
18+
public function __construct(
19+
public readonly string $name,
20+
) {
21+
$this->reflectorIfSet = AttributeConstructor::getReflectorIfSet();
22+
try {
23+
$this->reflector = AttributeConstructor::getReflector();
24+
$this->exception = NULL;
25+
}
26+
catch (\LogicException $e) {
27+
$this->reflector = NULL;
28+
$this->exception = $e;
29+
}
30+
}
31+
32+
}

tests/src/Fixtures/TestClassWithAttributes.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
namespace Ock\ReflectorAwareAttributes\Tests\Fixtures;
66

77
use Ock\ReflectorAwareAttributes\Tests\Fixtures\Attribute\OtherTestAttribute;
8+
use Ock\ReflectorAwareAttributes\Tests\Fixtures\Attribute\ReflectorAwareConstructorTestAttribute;
89
use Ock\ReflectorAwareAttributes\Tests\Fixtures\Attribute\ReflectorAwareTestAttribute;
910
use Ock\ReflectorAwareAttributes\Tests\Fixtures\Attribute\TestAttribute;
1011

1112
#[TestAttribute('on a class')]
1213
#[OtherTestAttribute('on a class')]
1314
#[ReflectorAwareTestAttribute]
15+
#[ReflectorAwareConstructorTestAttribute('on a class')]
1416
class TestClassWithAttributes {
1517

1618
#[TestAttribute('on a class constant')]

tests/src/GetAttributesTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace Ock\ReflectorAwareAttributes\Tests;
66

77
use Ock\ReflectorAwareAttributes\Tests\Fixtures\Attribute\OtherTestAttribute;
8+
use Ock\ReflectorAwareAttributes\Tests\Fixtures\Attribute\ReflectorAwareConstructorTestAttribute;
89
use Ock\ReflectorAwareAttributes\Tests\Fixtures\Attribute\ReflectorAwareTestAttribute;
910
use Ock\ReflectorAwareAttributes\Tests\Fixtures\Attribute\TestAttribute;
1011
use Ock\ReflectorAwareAttributes\Tests\Fixtures\Attribute\TestAttributeInterface;
@@ -39,6 +40,20 @@ public function testSetReflector(): void {
3940
$this->assertNull($attributes[0]->reflector);
4041
}
4142

43+
public function testGetReflectorFromConstructor(): void {
44+
$reflector = new \ReflectionClass(TestClassWithAttributes::class);
45+
[$attribute] = get_attributes($reflector, ReflectorAwareConstructorTestAttribute::class);
46+
$this->assertSame($reflector, $attribute->reflectorIfSet);
47+
$this->assertSame($reflector, $attribute->reflector);
48+
$this->assertNull($attribute->exception ?? NULL);
49+
50+
[$raw_attribute] = get_raw_attributes($reflector, ReflectorAwareConstructorTestAttribute::class);
51+
$this->assertNull($raw_attribute->reflectorIfSet);
52+
$this->assertNull($raw_attribute->reflector ?? NULL);
53+
$this->assertNotNull($raw_attribute->exception);
54+
$this->assertSame(\LogicException::class, get_class($raw_attribute->exception));
55+
}
56+
4257
/**
4358
* @param \Closure(\ReflectionClass|\ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty|\ReflectionClassConstant, class-string, int=): list<object> $get_attributes
4459
*/

0 commit comments

Comments
 (0)