8000 [12.x] Introduce Reflector methods for accessing class attributes (#5… · laravel/framework@186debc · GitHub
[go: up one dir, main page]

Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
10000

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

+41
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
*
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

+57
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)
0