-
-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathReflectionDefinitionBuilder.php
More file actions
197 lines (173 loc) · 7.42 KB
/
Copy pathReflectionDefinitionBuilder.php
File metadata and controls
197 lines (173 loc) · 7.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
<?php
declare(strict_types=1);
namespace Crell\AttributeUtils;
use function Crell\fp\afilter;
use function Crell\fp\amap;
use function Crell\fp\indexBy;
use function Crell\fp\method;
use function Crell\fp\pipe;
/**
* Derives the definition for a component's attributes.
*/
class ReflectionDefinitionBuilder
{
public function __construct(
protected readonly AttributeParser $parser,
protected readonly ?Analyzer $analyzer = null,
) {}
/**
* Gets all applicable attribute definitions of a given class element type.
*
* Eg, gets all property attributes, or all method attributes.
*
* @param \Reflector[] $reflections
* The reflection objects to turn into attributes.
* @param callable $deriver
* Callback for turning a reflection object into the corresponding attribute.
* It must already have closed over the attribute type to retrieve.
* @return array<string, object>
* An array of attributes across all items of the applicable type.
*/
public function getDefinitions(array $reflections, callable $deriver): array
{
return pipe($reflections,
// The Reflector interface is insufficient, but getName() is defined
// on all types we care about. This is a reflection API limitation.
indexBy(method('getName')),
amap($deriver),
afilter(static fn (?object $attr): bool => $attr && !($attr instanceof Excludable && $attr->exclude())),
);
}
/**
* Returns the attribute definition for a class component.
*/
public function getComponentDefinition(\Reflector $reflection, string $attributeType, bool $includeByDefault, string $reflectionInterface, object $classDef): ?object
{
// @todo This is a problem. IF an attribute supports scopes, and is excluded,
// then we do NOT want to have a default empty added, regardless of $includeByDefault.
// I think? But we don't know about scopes at this point, which means we don't know
// what we should do here. I don't know how to solve this.
$def = $this->parser->getInheritedAttribute($reflection, $attributeType)
?? ($includeByDefault ? new $attributeType() : null);
if ($def instanceof $reflectionInterface) {
// This is just too dynamic for PHPstan to handle.
// @phpstan-ignore-next-line
$def->fromReflection($reflection);
}
$this->loadSubAttributes($def, $reflection);
if ($def instanceof CustomAnalysis && $this->analyzer) {
$def->customAnalysis($this->analyzer);
}
if ($def instanceof ReadsClass) {
$def->fromClassAttribute($classDef);
}
if ($def instanceof Finalizable) {
$def->finalize();
}
return $def;
}
/**
* Returns the attribute definition for a method.
*
* Methods can't just reuse getComponentDefinition() because they
* also have parameters of their own to parse.
*/
public function getMethodDefinition(\ReflectionMethod $reflection, string $attributeType, bool $includeByDefault, object $classDef): ?object
{
$def = $this->parser->getInheritedAttribute($reflection, $attributeType)
?? ($includeByDefault ? new $attributeType() : null);
if ($def instanceof FromReflectionMethod) {
$def->fromReflection($reflection);
}
$this->loadSubAttributes($def, $reflection);
if ($def instanceof ParseParameters) {
$parameters = $this->getDefinitions(
$reflection->getParameters(),
fn (\ReflectionParameter $p)
=> $this->getComponentDefinition($p, $def->parameterAttribute(), $def->includeParametersByDefault(), FromReflectionParameter::class, $classDef)
);
$def->setParameters($parameters);
}
if ($def instanceof CustomAnalysis && $this->analyzer) {
$def->customAnalysis($this->analyzer);
}
if ($def instanceof ReadsClass) {
$def->fromClassAttribute($classDef);
}
if ($def instanceof Finalizable) {
$def->finalize();
}
return $def;
}
/**
* Loads sub-attributes onto an attribute, if appropriate.
*/
public function loadSubAttributes(?object $attribute, \Reflector $reflection): void
{
if ($attribute instanceof HasSubAttributes) {
foreach ($attribute->subAttributes() as $type => $callback) {
if ($this->isMultivalueAttribute($type)) {
$subs = $this->parser->getInheritedAttributes($reflection, $type);
foreach ($subs as $sub) {
$this->applySubattributeFeatures($sub, $reflection);
}
if ($callback instanceof \Closure) {
$callback($subs);
} else {
$attribute->$callback($subs);
}
} else {
$sub = $this->parser->getInheritedAttribute($reflection, $type);
if ($sub) {
$this->applySubattributeFeatures($sub, $reflection);
}
if ($callback instanceof \Closure) {
$callback($sub);
} else {
$attribute->$callback($sub);
}
}
}
}
}
protected function applySubattributeFeatures(object $attribute, \Reflector $reflection): void
{
// For each possible type, check for a FromReflection interface.
if ($reflection instanceof \ReflectionClass && ! $reflection instanceof \ReflectionEnum && $attribute instanceof FromReflectionClass) {
/** @var \ReflectionClass<object> $reflection */
$attribute->fromReflection($reflection);
}
if ($reflection instanceof \ReflectionEnum && $attribute instanceof FromReflectionEnum) {
$attribute->fromReflection($reflection);
}
if ($reflection instanceof \ReflectionFunction && $attribute instanceof FromReflectionFunction) {
$attribute->fromReflection($reflection);
}
if ($reflection instanceof \ReflectionProperty && $attribute instanceof FromReflectionProperty) {
$attribute->fromReflection($reflection);
}
if ($reflection instanceof \ReflectionMethod && $attribute instanceof FromReflectionMethod) {
$attribute->fromReflection($reflection);
}
if ($reflection instanceof \ReflectionClassConstant && $attribute instanceof FromReflectionClassConstant) {
$attribute->fromReflection($reflection);
}
if ($reflection instanceof \ReflectionParameter && $attribute instanceof FromReflectionParameter) {
$attribute->fromReflection($reflection);
}
if ($attribute instanceof Finalizable) {
$attribute->finalize();
}
// Call recursively to allow sub-attributes on sub-attributes. (Yo Dawg.)
$this->loadSubAttributes($attribute, $reflection);
}
/**
* Determines if a given attribute class allows repeating.
*
* This is only meaningful for attributes used as sub-attributes.
*/
protected function isMultivalueAttribute(string $attributeType): bool
{
return is_a($attributeType, Multivalue::class, true);
}
}