Skip to content

Commit 186debc

Browse files
daniserSergey Danilchenkotaylorotwell
authored
[12.x] Introduce Reflector methods for accessing class attributes (#55568)
* [12.x] Introduce helper functions for accessing class attributes * fix tests * move functions to Reflector class * rename to getClassAttribute(s) * formatting --------- Co-authored-by: Sergey Danilchenko <s.danilchenko@ttbooking.ru> Co-authored-by: Taylor Otwell <taylor@laravel.com>
1 parent 1deaa36 commit 186debc

File tree

3 files changed

+139
-0
lines changed

3 files changed

+139
-0
lines changed

src/Illuminate/Support/Reflector.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Illuminate\Support;
44

5+
use ReflectionAttribute;
56
use ReflectionClass;
67
use ReflectionEnum;
78
use ReflectionMethod;
@@ -56,6 +57,46 @@ public static function isCallable($var, $syntaxOnly = false)
5657
return false;
5758
}
5859

60+
/**
61+
* Get the specified class attribute, optionally following an inheritance chain.
62+
*
63+
* @template TAttribute of object
64+
*
65+
* @param object|class-string $objectOrClass
66+
* @param class-string<TAttribute> $attribute
67+
* @return TAttribute|null
68+
*/
69+
public static function getClassAttribute($objectOrClass, $attribute, $ascend = false)
70+
{
71+
return static::getClassAttributes($objectOrClass, $attribute, $ascend)->flatten()->first();
72+
}
73+
74+
/**
75+
* Get the specified class attribute(s), optionally following an inheritance chain.
76+
*
77+
* @template TTarget of object
78+
* @template TAttribute of object
79+
*
80+
* @param TTarget|class-string<TTarget> $objectOrClass
81+
* @param class-string<TAttribute> $attribute
82+
* @return ($includeParents is true ? Collection<class-string<contravariant TTarget>, Collection<int, TAttribute>> : Collection<int, TAttribute>)
83+
*/
84+
public static function getClassAttributes($objectOrClass, $attribute, $includeParents = false)
85+
{
86+
$reflectionClass = new ReflectionClass($objectOrClass);
87+
88+
$attributes = [];
89+
90+
do {
91+
$attributes[$reflectionClass->name] = new Collection(array_map(
92+
fn (ReflectionAttribute $reflectionAttribute) => $reflectionAttribute->newInstance(),
93+
$reflectionClass->getAttributes($attribute)
94+
));
95+
} while ($includeParents && false !== $reflectionClass = $reflectionClass->getParentClass());
96+
97+
return $includeParents ? new Collection($attributes) : reset($attributes);
98+
}
99+
59100
/**
60101
* Get the class name of the given parameter's type, if possible.
61102
*
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace Illuminate\Tests\Support\Fixtures;
4+
5+
use Attribute;
6+
7+
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
8+
class UnusedAttr
9+
{
10+
}
11+
12+
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
13+
class ParentOnlyAttr
14+
{
15+
}
16+
17+
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
18+
class StrAttr
19+
{
20+
public function __construct(public string $string)
21+
{
22+
}
23+
}
24+
25+
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
26+
class NumAttr
27+
{
28+
public function __construct(public int $number)
29+
{
30+
}
31+
}
32+
33+
#[StrAttr('lazy'), StrAttr('dog'), NumAttr(2), NumAttr(3), ParentOnlyAttr]
34+
class ParentClass
35+
{
36+
}
37+
38+
#[StrAttr('quick'), StrAttr('brown'), StrAttr('fox'), NumAttr(7)]
39+
class ChildClass extends ParentClass
40+
{
41+
}

tests/Support/SupportReflectorTest.php

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,63 @@ public function testIsCallable()
7575
$this->assertFalse(Reflector::isCallable(['TotallyMissingClass', 'foo']));
7676
$this->assertTrue(Reflector::isCallable(['TotallyMissingClass', 'foo'], true));
7777
}
78+
79+
public function testGetClassAttributes()
80+
{
81+
require_once __DIR__.'/Fixtures/ClassesWithAttributes.php';
82+
83+
$this->assertSame([], Reflector::getClassAttributes(Fixtures\ChildClass::class, Fixtures\UnusedAttr::class)->toArray());
84+
85+
$this->assertSame(
86+
[Fixtures\ChildClass::class => [], Fixtures\ParentClass::class => []],
87+
Reflector::getClassAttributes(Fixtures\ChildClass::class, Fixtures\UnusedAttr::class, true)->toArray()
88+
);
89+
90+
$this->assertSame(
91+
['quick', 'brown', 'fox'],
92+
Reflector::getClassAttributes(Fixtures\ChildClass::class, Fixtures\StrAttr::class)->map->string->all()
93+
);
94+
95+
$this->assertSame(
96+
['quick', 'brown', 'fox', 'lazy', 'dog'],
97+
Reflector::getClassAttributes(Fixtures\ChildClass::class, Fixtures\StrAttr::class, true)->flatten()->map->string->all()
98+
);
99+
100+
$this->assertSame(7, Reflector::getClassAttributes(Fixtures\ChildClass::class, Fixtures\NumAttr::class)->sum->number);
101+
$this->assertSame(12, Reflector::getClassAttributes(Fixtures\ChildClass::class, Fixtures\NumAttr::class, true)->flatten()->sum->number);
102+
$this->assertSame(5, Reflector::getClassAttributes(Fixtures\ParentClass::class, Fixtures\NumAttr::class)->sum->number);
103+
$this->assertSame(5, Reflector::getClassAttributes(Fixtures\ParentClass::class, Fixtures\NumAttr::class, true)->flatten()->sum->number);
104+
105+
$this->assertSame(
106+
[Fixtures\ChildClass::class, Fixtures\ParentClass::class],
107+
Reflector::getClassAttributes(Fixtures\ChildClass::class, Fixtures\StrAttr::class, true)->keys()->all()
108+
);
109+
110+
$this->assertContainsOnlyInstancesOf(
111+
Fixtures\StrAttr::class,
112+
Reflector::getClassAttributes(Fixtures\ChildClass::class, Fixtures\StrAttr::class)->all()
113+
);
114+
115+
$this->assertContainsOnlyInstancesOf(
116+
Fixtures\StrAttr::class,
117+
Reflector::getClassAttributes(Fixtures\ChildClass::class, Fixtures\StrAttr::class, true)->flatten()->all()
118+
);
119+
}
120+
121+
public function testGetClassAttribute()
122+
{
123+
require_once __DIR__.'/Fixtures/ClassesWithAttributes.php';
124+
125+
$this->assertNull(Reflector::getClassAttribute(Fixtures\ChildClass::class, Fixtures\UnusedAttr::class));
126+
$this->assertNull(Reflector::getClassAttribute(Fixtures\ChildClass::class, Fixtures\UnusedAttr::class, true));
127+
$this->assertNull(Reflector::getClassAttribute(Fixtures\ChildClass::class, Fixtures\ParentOnlyAttr::class));
128+
$this->assertInstanceOf(Fixtures\ParentOnlyAttr::class, Reflector::getClassAttribute(Fixtures\ChildClass::class, Fixtures\ParentOnlyAttr::class, true));
129+
$this->assertInstanceOf(Fixtures\StrAttr::class, Reflector::getClassAttribute(Fixtures\ChildClass::class, Fixtures\StrAttr::class));
130+
$this->assertInstanceOf(Fixtures\StrAttr::class, Reflector::getClassAttribute(Fixtures\ChildClass::class, Fixtures\StrAttr::class, true));
131+
$this->assertSame('quick', Reflector::getClassAttribute(Fixtures\ChildClass::class, Fixtures\StrAttr::class)->string);
132+
$this->assertSame('quick', Reflector::getClassAttribute(Fixtures\ChildClass::class, Fixtures\StrAttr::class, true)->string);
133+
$this->assertSame('lazy', Reflector::getClassAttribute(Fixtures\ParentClass::class, Fixtures\StrAttr::class)->string);
134+
}
78135
}
79136

80137
class A

0 commit comments

Comments
 (0)