1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Created by gerk on 29.09.16 10:21 |
4
|
|
|
*/ |
5
|
|
|
|
6
|
|
|
namespace PeekAndPoke\Component\MetaCore; |
7
|
|
|
|
8
|
|
|
use Doctrine\Common\Annotations\AnnotationReader; |
9
|
|
|
use Doctrine\Common\Annotations\AnnotationRegistry; |
10
|
|
|
use Doctrine\Common\Annotations\CachedReader; |
11
|
|
|
use Doctrine\Common\Annotations\Reader; |
12
|
|
|
use Doctrine\Common\Cache\ArrayCache; |
13
|
|
|
use PeekAndPoke\Component\MetaCore\DomainModel\Property; |
14
|
|
|
use PeekAndPoke\Component\MetaCore\DomainModel\Type; |
15
|
|
|
use PeekAndPoke\Component\MetaCore\DomainModel\Type\ObjectType; |
16
|
|
|
use PeekAndPoke\Component\MetaCore\DomainModel\TypeRef; |
17
|
|
|
use PeekAndPoke\Component\MetaCore\DomainModel\TypeRegistry; |
18
|
|
|
use PeekAndPoke\Component\MetaCore\Exception\MetaCoreRuntimeException; |
19
|
|
|
use PeekAndPoke\Component\Psi\Psi; |
20
|
|
|
use PeekAndPoke\Component\Slumber\Annotation\Slumber\Alias; |
21
|
|
|
use PeekAndPoke\Types\Enumerated; |
22
|
|
|
|
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* Builder |
26
|
|
|
* |
27
|
|
|
* @author Karsten J. Gerber <[email protected]> |
28
|
|
|
*/ |
29
|
|
|
class Builder |
30
|
|
|
{ |
31
|
|
|
/** @var Reader */ |
32
|
|
|
private $reader; |
33
|
|
|
/** @var PropertyFilter */ |
34
|
|
|
private $propertyFilter; |
35
|
|
|
/** @var PropertyMapper */ |
36
|
|
|
private $propertyMapper; |
37
|
|
|
|
38
|
|
|
/** @var TypeRegistry */ |
39
|
|
|
private $typeRegistry; |
40
|
|
|
/** @var array|Type[] Built in type, key being the type name, value a blueprint instance */ |
41
|
|
|
private $builtInTypes; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* @return Builder |
45
|
|
|
*/ |
46
|
43 |
|
public static function createDefault() |
47
|
|
|
{ |
48
|
43 |
|
return new static( |
49
|
43 |
|
new CachedReader(new AnnotationReader(), new ArrayCache()), |
50
|
43 |
|
new DefaultPropertyFilter(), |
51
|
43 |
|
new DefaultPropertyMapper() |
52
|
|
|
); |
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* C'tor |
57
|
|
|
* |
58
|
|
|
* @param Reader $reader |
59
|
|
|
* @param PropertyFilter $propertyFilter |
60
|
|
|
* @param PropertyMapper $propertyMapper |
61
|
|
|
*/ |
62
|
44 |
|
public function __construct(Reader $reader, PropertyFilter $propertyFilter, PropertyMapper $propertyMapper) |
63
|
|
|
{ |
64
|
44 |
|
static $autoload = false; |
65
|
|
|
|
66
|
44 |
|
if (! $autoload) { |
67
|
1 |
|
$autoload = true; |
68
|
|
|
/** @noinspection ExceptionsAnnotatingAndHandlingInspection */ |
69
|
1 |
|
AnnotationRegistry::registerLoader('class_exists'); |
|
|
|
|
70
|
|
|
} |
71
|
|
|
|
72
|
44 |
|
$this->reader = $reader; |
73
|
44 |
|
$this->propertyFilter = $propertyFilter; |
74
|
44 |
|
$this->propertyMapper = $propertyMapper; |
75
|
|
|
|
76
|
44 |
|
$this->typeRegistry = new TypeRegistry(); |
77
|
|
|
|
78
|
44 |
|
$this->builtInTypes = [ |
79
|
44 |
|
DomainModel\Type\AnyType::TYPE => new DomainModel\Type\AnyType(), |
80
|
44 |
|
DomainModel\Type\BooleanType::TYPE => new DomainModel\Type\BooleanType(), |
81
|
44 |
|
DomainModel\Type\DateTimeType::TYPE => new DomainModel\Type\DateTimeType(), |
82
|
44 |
|
DomainModel\Type\DoubleType::TYPE => new DomainModel\Type\DoubleType(), |
83
|
44 |
|
DomainModel\Type\FloatType::TYPE => new DomainModel\Type\FloatType(), |
84
|
44 |
|
DomainModel\Type\IntType::TYPE => new DomainModel\Type\IntType(), |
85
|
44 |
|
DomainModel\Type\LocalDateTimeType::TYPE => new DomainModel\Type\LocalDateTimeType(), |
86
|
44 |
|
DomainModel\Type\StringType::TYPE => new DomainModel\Type\StringType(), |
87
|
|
|
]; |
88
|
44 |
|
} |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* @return PropertyFilter |
92
|
|
|
*/ |
93
|
1 |
|
public function getPropertyFilter() |
94
|
|
|
{ |
95
|
1 |
|
return $this->propertyFilter; |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* @return PropertyMapper |
100
|
|
|
*/ |
101
|
1 |
|
public function getPropertyMapper() |
102
|
|
|
{ |
103
|
1 |
|
return $this->propertyMapper; |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* @return TypeRegistry |
108
|
|
|
*/ |
109
|
1 |
|
public function getTypeRegistry() |
110
|
|
|
{ |
111
|
1 |
|
return $this->typeRegistry; |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
/** |
115
|
|
|
* @return Type[] |
116
|
|
|
*/ |
117
|
1 |
|
public function getBuiltInTypes() |
118
|
|
|
{ |
119
|
1 |
|
return $this->builtInTypes; |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* Build the full name for a type. |
124
|
|
|
* |
125
|
|
|
* For simple types this will return "String", "Int", etc. |
126
|
|
|
* |
127
|
|
|
* For generic type this will return e.g. Map<String,List<Int>> |
128
|
|
|
* |
129
|
|
|
* @param TypeRef $ref |
130
|
|
|
* |
131
|
|
|
* @return string |
132
|
|
|
*/ |
133
|
62 |
|
public function buildFullName(TypeRef $ref) |
134
|
|
|
{ |
135
|
62 |
|
if (count($ref->getParams()) === 0) { |
136
|
62 |
|
return $ref->getId(); |
137
|
|
|
} |
138
|
|
|
|
139
|
12 |
|
$params = []; |
140
|
|
|
|
141
|
12 |
|
foreach ($ref->getParams() as $param) { |
142
|
12 |
|
$params[] = $this->buildFullName( |
143
|
12 |
|
$param |
144
|
|
|
); |
145
|
|
|
} |
146
|
|
|
|
147
|
12 |
|
return $ref->getId() . '<' . implode(',', $params) . '>'; |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
/** |
151
|
|
|
* Build a type from a given TypeRef. |
152
|
|
|
* |
153
|
|
|
* This method is omnipotent, meaning that when ever we request a type that is already |
154
|
|
|
* known, we will return the same instance of the Type. |
155
|
|
|
* |
156
|
|
|
* A type is considered to be known when the fullName |
157
|
|
|
* a) matches a built in type like "String", "DateTime", "Int" |
158
|
|
|
* b) is already present in the type registry |
159
|
|
|
* |
160
|
|
|
* @param TypeRef $ref |
161
|
|
|
* |
162
|
|
|
* @return Type |
163
|
|
|
*/ |
164
|
34 |
|
public function buildForRef(TypeRef $ref) |
165
|
|
|
{ |
166
|
34 |
|
$fullName = $this->buildFullName($ref); |
167
|
|
|
|
168
|
34 |
|
if (isset($this->builtInTypes[$fullName])) { |
169
|
21 |
|
return $this->builtInTypes[$fullName]; |
170
|
|
|
} |
171
|
|
|
|
172
|
13 |
|
if ($this->typeRegistry->hasById($fullName)) { |
173
|
9 |
|
return $this->typeRegistry->getById($fullName); |
174
|
|
|
} |
175
|
|
|
|
176
|
11 |
|
if ($ref->getId() === Type\MapType::type()) { |
177
|
6 |
|
$type = Type::map( |
178
|
6 |
|
$ref->getParamAt(0), |
|
|
|
|
179
|
6 |
|
$ref->getParamAt(1) |
|
|
|
|
180
|
|
|
); |
181
|
|
|
|
182
|
6 |
|
$this->typeRegistry->add($fullName, $type); |
183
|
|
|
|
184
|
6 |
|
return $type; |
185
|
|
|
} |
186
|
|
|
|
187
|
5 |
|
if ($ref->getId() === Type\ListType::type()) { |
188
|
2 |
|
$type = Type::list_($ref->getParamAt(0)); |
|
|
|
|
189
|
|
|
|
190
|
2 |
|
$this->typeRegistry->add($fullName, $type); |
191
|
|
|
|
192
|
2 |
|
return $type; |
193
|
|
|
} |
194
|
|
|
|
195
|
3 |
|
return $this->buildForClass(new \ReflectionClass($ref->getId())); |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
/** |
199
|
|
|
* Build the type from a given reflection class. |
200
|
|
|
* |
201
|
|
|
* @param \ReflectionClass $class |
202
|
|
|
* |
203
|
|
|
* @return Type |
204
|
|
|
*/ |
205
|
15 |
|
public function buildForClass(\ReflectionClass $class) |
206
|
|
|
{ |
207
|
15 |
|
$id = $class->name; |
208
|
|
|
|
209
|
|
|
// is there an alias defined on the class using the Slumber\Alias annotation |
210
|
15 |
|
if (class_exists(Alias::class)) { |
211
|
|
|
/** @var Alias|null $aliasAnnotation */ |
212
|
15 |
|
$aliasAnnotation = $this->reader->getClassAnnotation($class, Alias::class); |
213
|
|
|
// do we have an alias ? |
214
|
15 |
|
$id = $aliasAnnotation ? $aliasAnnotation->value : $id; |
215
|
|
|
} |
216
|
|
|
|
217
|
15 |
|
if ($this->typeRegistry->hasById($id)) { |
218
|
|
|
return $this->typeRegistry->getById($id); |
219
|
|
|
} |
220
|
|
|
|
221
|
15 |
|
if (is_a($class->name, Enumerated::class, true)) { |
222
|
|
|
|
223
|
|
|
/** @var Enumerated $enum */ |
224
|
4 |
|
$enum = $class->name; |
225
|
4 |
|
$type = Type::enum($enum::void()); |
226
|
|
|
|
227
|
4 |
|
$this->typeRegistry->add($this->buildFullName($type->ref()), $type); |
228
|
|
|
|
229
|
|
|
} else { |
230
|
|
|
// IMPORTANT: we initiate and register an "empty" type in order to break cyclic references. |
231
|
11 |
|
$type = new ObjectType($id); |
232
|
11 |
|
$this->typeRegistry->add($this->buildFullName($type->ref()), $type); |
233
|
|
|
|
234
|
|
|
// populate the type |
235
|
11 |
|
$this->populateObjectType($type, $class); |
236
|
|
|
} |
237
|
|
|
|
238
|
12 |
|
return $type; |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
/** |
242
|
|
|
* @param ObjectType $type |
243
|
|
|
* |
244
|
|
|
* @return Property[] |
245
|
|
|
*/ |
246
|
1 |
|
public function getAllProperties(ObjectType $type) |
247
|
|
|
{ |
248
|
1 |
|
$result = $type->getProperties(); |
249
|
|
|
|
250
|
1 |
|
foreach ($type->getExtends() as $extend) { |
251
|
|
|
|
252
|
1 |
|
$extendType = $this->buildForRef($extend); |
253
|
|
|
|
254
|
1 |
|
if ($extendType instanceof ObjectType) { |
255
|
|
|
/** @noinspection SlowArrayOperationsInLoopInspection */ |
256
|
1 |
|
$result = array_merge($result, $this->getAllProperties($extendType)); |
257
|
|
|
} |
258
|
|
|
} |
259
|
|
|
|
260
|
1 |
|
return $result; |
261
|
|
|
} |
262
|
|
|
|
263
|
|
|
/** |
264
|
|
|
* @param ObjectType $type |
265
|
|
|
* @param \ReflectionClass $class |
266
|
|
|
*/ |
267
|
11 |
|
private function populateObjectType(ObjectType $type, \ReflectionClass $class) |
268
|
|
|
{ |
269
|
|
|
/** @var Property[] $properties */ |
270
|
11 |
|
$properties = Psi::it($class->getProperties()) |
271
|
|
|
// is it a non-static property |
272
|
11 |
|
->filter( |
273
|
|
|
function (\ReflectionProperty $p) { return $p->isStatic() === false; } |
274
|
|
|
) |
275
|
|
|
// is the property declared by the Class or one of the Traits it uses ? |
276
|
11 |
|
->filter( |
277
|
11 |
|
function (\ReflectionProperty $p) use ($class) { |
278
|
10 |
|
return self::getDeclaringClassInInheritanceChain($class, $p) === $class; |
279
|
11 |
|
} |
280
|
|
|
) |
281
|
|
|
// filter properties |
282
|
11 |
|
->filter( |
283
|
|
|
function (\ReflectionProperty $p) { return $this->propertyFilter->filterProperty($p); } |
284
|
|
|
) |
285
|
|
|
// map properties |
286
|
11 |
|
->map( |
287
|
|
|
function (\ReflectionProperty $p) { return $this->buildProperty($p); } |
288
|
|
|
) |
289
|
|
|
// collect |
290
|
11 |
|
->toArray(); |
291
|
|
|
|
292
|
|
|
// add all the found properties |
293
|
8 |
|
foreach ($properties as $property) { |
294
|
7 |
|
$type->addProperty($property); |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
// do we inherit ? |
298
|
8 |
|
if ($class->getParentClass()) { |
299
|
3 |
|
$type->addExtends( |
300
|
3 |
|
$this->buildForClass($class->getParentClass())->ref() |
301
|
|
|
); |
302
|
|
|
} |
303
|
8 |
|
} |
304
|
|
|
|
305
|
|
|
/** |
306
|
|
|
* @param \ReflectionProperty $property |
307
|
|
|
* |
308
|
|
|
* @return Property |
309
|
|
|
*/ |
310
|
10 |
|
private function buildProperty(\ReflectionProperty $property) |
311
|
|
|
{ |
312
|
|
|
try { |
313
|
10 |
|
return $this->propertyMapper->mapProperty($this, $property); |
314
|
|
|
|
315
|
3 |
|
} catch (\Exception $e) { |
316
|
3 |
|
throw new MetaCoreRuntimeException( |
317
|
3 |
|
'Error in context of ' . self::getRealDeclaringClass($property)->name . '::' . $property->getName(), 0, $e |
318
|
|
|
); |
319
|
|
|
} |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
/** |
323
|
|
|
* TODO: should we extract extended reflection functionality into the Mirror ? |
324
|
|
|
* |
325
|
|
|
* @param \ReflectionProperty $prop |
326
|
|
|
* |
327
|
|
|
* @return \ReflectionClass |
328
|
|
|
*/ |
329
|
10 |
|
public static function getRealDeclaringClass(\ReflectionProperty $prop) |
330
|
|
|
{ |
331
|
10 |
|
return self::getRealDeclaringClassInternal($prop->getDeclaringClass(), $prop); |
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
/** |
335
|
|
|
* Get the exact class or trait that defines a property. |
336
|
|
|
* |
337
|
|
|
* TODO: should we extract extended reflection functionality into the Mirror ? |
338
|
|
|
* |
339
|
|
|
* @param \ReflectionClass $class |
340
|
|
|
* @param \ReflectionProperty $prop |
341
|
|
|
* |
342
|
|
|
* @return \ReflectionClass |
343
|
|
|
*/ |
344
|
10 |
|
private static function getRealDeclaringClassInternal(\ReflectionClass $class, \ReflectionProperty $prop) |
345
|
|
|
{ |
346
|
10 |
|
$declaringTrait = Psi::it($class->getTraits()) |
347
|
|
|
->filter(function (\ReflectionClass $r) use ($prop) { return $r->hasProperty($prop->getName()); }) |
348
|
10 |
|
->getFirst(); |
349
|
|
|
|
350
|
|
|
// We found it on the traits. No we need to recurse on traits to find exactly the one that was defining it |
351
|
10 |
|
if ($declaringTrait !== null) { |
352
|
1 |
|
return self::getRealDeclaringClassInternal($declaringTrait, $prop); |
353
|
|
|
} |
354
|
|
|
|
355
|
10 |
|
return self::getDeclaringClassInInheritanceChain($class, $prop); |
356
|
|
|
} |
357
|
|
|
|
358
|
|
|
/** |
359
|
|
|
* Get the class declaring the property after traits are resolved. |
360
|
|
|
* |
361
|
|
|
* TODO: should we extract extended reflection functionality into the Mirror ? |
362
|
|
|
* |
363
|
|
|
* @param \ReflectionClass $class |
364
|
|
|
* @param \ReflectionProperty $prop |
365
|
|
|
* |
366
|
|
|
* @return \ReflectionClass |
367
|
|
|
*/ |
368
|
10 |
|
public static function getDeclaringClassInInheritanceChain(\ReflectionClass $class, \ReflectionProperty $prop) |
369
|
|
|
{ |
370
|
|
|
// climb up the inheritance tree |
371
|
10 |
|
while ($class->getParentClass() && $class->getParentClass()->hasProperty($prop->getName())) { |
372
|
3 |
|
$class = $class->getParentClass(); |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
// this is what is left |
376
|
10 |
|
return $class; |
377
|
|
|
} |
378
|
|
|
} |
379
|
|
|
|
This method has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.