1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace As3\Modlr\Metadata\Driver; |
4
|
|
|
|
5
|
|
|
use As3\Modlr\Metadata; |
6
|
|
|
use As3\Modlr\Exception\RuntimeException; |
7
|
|
|
use As3\Modlr\Exception\MetadataException; |
8
|
|
|
use Symfony\Component\Yaml\Yaml; |
9
|
|
|
|
10
|
|
|
/** |
11
|
|
|
* The YAML metadata file driver. |
12
|
|
|
* |
13
|
|
|
* @author Jacob Bare <[email protected]> |
14
|
|
|
*/ |
15
|
|
|
final class YamlFileDriver extends AbstractFileDriver |
16
|
|
|
{ |
17
|
|
|
/** |
18
|
|
|
* An in-memory cache of parsed metadata mappings (from file). |
19
|
|
|
* |
20
|
|
|
* @var array |
21
|
|
|
*/ |
22
|
|
|
private $mappings = [ |
23
|
|
|
'model' => [], |
24
|
|
|
'mixin' => [], |
25
|
|
|
'embed' => [], |
26
|
|
|
]; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* {@inheritDoc} |
30
|
|
|
*/ |
31
|
|
|
protected function loadMetadataFromFile($type, $file) |
32
|
|
|
{ |
33
|
|
|
$mapping = $this->getMapping('model', $type, $file); |
34
|
|
|
|
35
|
|
|
$metadata = new Metadata\EntityMetadata($type); |
36
|
|
|
|
37
|
|
|
if (isset($mapping['entity']['abstract'])) { |
38
|
|
|
$metadata->setAbstract(true); |
39
|
|
|
} |
40
|
|
|
|
41
|
|
View Code Duplication |
if (isset($mapping['entity']['polymorphic'])) { |
|
|
|
|
42
|
|
|
$metadata->setPolymorphic(true); |
43
|
|
|
$metadata->ownedTypes = $this->getOwnedTypes($metadata->type); |
44
|
|
|
} |
45
|
|
|
|
46
|
|
|
if (isset($mapping['entity']['extends'])) { |
47
|
|
|
$metadata->extends = $mapping['entity']['extends']; |
48
|
|
|
} |
49
|
|
|
|
50
|
|
View Code Duplication |
if (isset($mapping['entity']['defaultValues']) && is_array($mapping['entity']['defaultValues'])) { |
|
|
|
|
51
|
|
|
$metadata->defaultValues = $mapping['entity']['defaultValues']; |
52
|
|
|
} |
53
|
|
|
|
54
|
|
|
$this->setPersistence($metadata, $mapping['entity']['persistence']); |
55
|
|
|
$this->setSearch($metadata, $mapping['entity']['search']); |
56
|
|
|
$this->setAttributes($metadata, $mapping['attributes']); |
57
|
|
|
$this->setRelationships($metadata, $mapping['relationships']); |
58
|
|
|
$this->setEmbeds($metadata, $mapping['embeds']); |
59
|
|
|
$this->setMixins($metadata, $mapping['mixins']); |
60
|
|
|
return $metadata; |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* {@inheritDoc} |
65
|
|
|
*/ |
66
|
|
View Code Duplication |
protected function loadEmbedFromFile($embedName, $file) |
|
|
|
|
67
|
|
|
{ |
68
|
|
|
$mapping = $this->getMapping('embed', $embedName, $file); |
69
|
|
|
|
70
|
|
|
$embed = new Metadata\EmbedMetadata($embedName); |
71
|
|
|
$this->setAttributes($embed, $mapping['attributes']); |
72
|
|
|
$this->setEmbeds($embed, $mapping['embeds']); |
73
|
|
|
$this->setMixins($embed, $mapping['mixins']); |
74
|
|
|
return $embed; |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* {@inheritDoc} |
79
|
|
|
*/ |
80
|
|
View Code Duplication |
protected function loadMixinFromFile($mixinName, $file) |
|
|
|
|
81
|
|
|
{ |
82
|
|
|
$mapping = $this->getMapping('mixin', $mixinName, $file); |
83
|
|
|
|
84
|
|
|
$mixin = new Metadata\MixinMetadata($mixinName); |
85
|
|
|
|
86
|
|
|
$this->setAttributes($mixin, $mapping['attributes']); |
87
|
|
|
$this->setEmbeds($mixin, $mapping['embeds']); |
88
|
|
|
$this->setRelationships($mixin, $mapping['relationships']); |
89
|
|
|
return $mixin; |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* {@inheritDoc} |
94
|
|
|
*/ |
95
|
|
|
public function getTypeHierarchy($type, array $types = []) |
96
|
|
|
{ |
97
|
|
|
$path = $this->getFilePath('model', $type); |
98
|
|
|
$mapping = $this->getMapping('model', $type, $path); |
99
|
|
|
|
100
|
|
|
$types[] = $type; |
101
|
|
|
if (isset($mapping['entity']['extends']) && $mapping['entity']['extends'] !== $type) { |
102
|
|
|
return $this->getTypeHierarchy($mapping['entity']['extends'], $types); |
103
|
|
|
} |
104
|
|
|
return array_reverse($types); |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
/** |
108
|
|
|
* {@inheritDoc} |
109
|
|
|
*/ |
110
|
|
|
public function getOwnedTypes($type, array $types = []) |
111
|
|
|
{ |
112
|
|
|
$path = $this->getFilePath('model', $type); |
113
|
|
|
$superMapping = $this->getMapping('model', $type, $path); |
114
|
|
|
|
115
|
|
|
$abstract = isset($superMapping['entity']['abstract']) && true === $superMapping['entity']['abstract']; |
116
|
|
|
|
117
|
|
|
foreach ($this->getAllTypeNames() as $searchType) { |
118
|
|
|
|
119
|
|
|
if ($type === $searchType && false === $abstract) { |
120
|
|
|
$types[] = $type; |
121
|
|
|
continue; |
122
|
|
|
} |
123
|
|
|
if (0 !== strpos($searchType, $type)) { |
124
|
|
|
continue; |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
$path = $this->getFilePath('model', $searchType); |
128
|
|
|
$mapping = $this->getMapping('model', $searchType, $path); |
129
|
|
|
|
130
|
|
|
if (!isset($mapping['entity']['extends']) || $mapping['entity']['extends'] !== $type) { |
131
|
|
|
continue; |
132
|
|
|
} |
133
|
|
|
$types[] = $searchType; |
134
|
|
|
} |
135
|
|
|
return $types; |
136
|
|
|
} |
137
|
|
|
|
138
|
|
|
/** |
139
|
|
|
* Gets the metadata mapping information from the YAML file. |
140
|
|
|
* |
141
|
|
|
* @param string $metaType The metadata type, either mixin or model. |
142
|
|
|
* @param string $key The metadata key name, either the mixin name or model type. |
143
|
|
|
* @param string $file The YAML file location. |
144
|
|
|
* @return array |
145
|
|
|
* @throws MetadataException |
146
|
|
|
*/ |
147
|
|
|
private function getMapping($metaType, $key, $file) |
148
|
|
|
{ |
149
|
|
|
if (isset($this->mappings[$metaType][$key])) { |
150
|
|
|
// Set to array cache to prevent multiple gets/parses. |
151
|
|
|
return $this->mappings[$metaType][$key]; |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
$contents = Yaml::parse(file_get_contents($file)); |
155
|
|
|
if (!isset($contents[$key])) { |
156
|
|
|
throw MetadataException::fatalDriverError($key, sprintf('No mapping key was found at the beginning of the YAML file. Expected "%s"', $key)); |
157
|
|
|
} |
158
|
|
|
return $this->mappings[$metaType][$key] = $this->setDefaults($metaType, $contents[$key]); |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* Sets the entity persistence metadata from the metadata mapping. |
163
|
|
|
* |
164
|
|
|
* @param Metadata\EntityMetadata $metadata |
165
|
|
|
* @param array $mapping |
166
|
|
|
* @return Metadata\EntityMetadata |
167
|
|
|
*/ |
168
|
|
View Code Duplication |
protected function setPersistence(Metadata\EntityMetadata $metadata, array $mapping) |
|
|
|
|
169
|
|
|
{ |
170
|
|
|
$persisterKey = isset($mapping['key']) ? $mapping['key'] : null; |
171
|
|
|
$factory = $this->getPersistenceMetadataFactory($persisterKey); |
172
|
|
|
|
173
|
|
|
$persistence = $factory->createInstance($mapping); |
174
|
|
|
|
175
|
|
|
$metadata->setPersistence($persistence); |
176
|
|
|
return $metadata; |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
/** |
180
|
|
|
* Sets the entity search metadata from the metadata mapping. |
181
|
|
|
* |
182
|
|
|
* @param Metadata\EntityMetadata $metadata |
183
|
|
|
* @param array $mapping |
184
|
|
|
* @return Metadata\EntityMetadata |
185
|
|
|
*/ |
186
|
|
View Code Duplication |
protected function setSearch(Metadata\EntityMetadata $metadata, array $mapping) |
|
|
|
|
187
|
|
|
{ |
188
|
|
|
$clientKey = isset($mapping['key']) ? $mapping['key'] : null; |
189
|
|
|
if (null === $clientKey) { |
190
|
|
|
// Search is not enabled for this model. |
191
|
|
|
return $metadata; |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
$factory = $this->getSearchMetadataFactory($clientKey); |
195
|
|
|
|
196
|
|
|
$search = $factory->createInstance($mapping); |
197
|
|
|
|
198
|
|
|
$metadata->setSearch($search); |
199
|
|
|
return $metadata; |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
/** |
203
|
|
|
* Sets the entity attribute metadata from the metadata mapping. |
204
|
|
|
* |
205
|
|
|
* @param Metadata\Interfaces\AttributeInterface $metadata |
206
|
|
|
* @param array $attrMapping |
207
|
|
|
* @return Metadata\EntityMetadata |
208
|
|
|
*/ |
209
|
|
|
protected function setAttributes(Metadata\Interfaces\AttributeInterface $metadata, array $attrMapping) |
210
|
|
|
{ |
211
|
|
|
foreach ($attrMapping as $key => $mapping) { |
212
|
|
|
if (!is_array($mapping)) { |
213
|
|
|
$mapping = ['type' => null]; |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
if (!isset($mapping['type'])) { |
217
|
|
|
$mapping['type'] = null; |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
if (!isset($mapping['search'])) { |
221
|
|
|
$mapping['search'] = []; |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
$attribute = new Metadata\AttributeMetadata($key, $mapping['type'], $this->isMixin($metadata)); |
225
|
|
|
|
226
|
|
|
if (isset($mapping['description'])) { |
227
|
|
|
$attribute->description = $mapping['description']; |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
if (isset($mapping['defaultValue'])) { |
231
|
|
|
$attribute->defaultValue = $mapping['defaultValue']; |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
if (isset($mapping['calculated']) && is_array($mapping['calculated'])) { |
235
|
|
|
$calculated = $mapping['calculated']; |
236
|
|
|
if (isset($calculated['class']) && isset($calculated['method'])) { |
237
|
|
|
$attribute->calculated['class'] = $calculated['class']; |
238
|
|
|
$attribute->calculated['method'] = $calculated['method']; |
239
|
|
|
} |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
if (isset($mapping['search']['autocomplete'])) { |
243
|
|
|
$attribute->setAutocomplete(true); |
244
|
|
|
} else if (isset($mapping['search']['store'])) { |
245
|
|
|
$attribute->setSearchProperty(true); |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
if (isset($mapping['save'])) { |
249
|
|
|
$attribute->enableSave($mapping['save']); |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
if (isset($mapping['serialize'])) { |
253
|
|
|
$attribute->enableSerialize($mapping['serialize']); |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
$metadata->addAttribute($attribute); |
257
|
|
|
} |
258
|
|
|
return $metadata; |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
/** |
262
|
|
|
* Sets the entity embed metadata from the metadata mapping. |
263
|
|
|
* |
264
|
|
|
* @param Metadata\Interfaces\EmbedInterface $metadata |
265
|
|
|
* @param array $embedMapping |
266
|
|
|
* @return Metadata\EntityMetadata |
267
|
|
|
*/ |
268
|
|
|
protected function setEmbeds(Metadata\Interfaces\EmbedInterface $metadata, array $embedMapping) |
269
|
|
|
{ |
270
|
|
|
foreach ($embedMapping as $key => $mapping) { |
271
|
|
View Code Duplication |
if (!is_array($mapping)) { |
|
|
|
|
272
|
|
|
$mapping = ['type' => null, 'entity' => null]; |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
if (!isset($mapping['type'])) { |
276
|
|
|
$mapping['type'] = null; |
277
|
|
|
} |
278
|
|
|
|
279
|
|
|
if (!isset($mapping['entity'])) { |
280
|
|
|
$mapping['entity'] = null; |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
if ($metadata instanceof Metadata\EmbedMetadata && $mapping['entity'] === $metadata->name) { |
284
|
|
|
// This embed meta is referencing itself as an embed property. As such, do not recreate. |
285
|
|
|
// This prevents infinite recursion. |
286
|
|
|
$embedMeta = $metadata; |
287
|
|
|
} else { |
288
|
|
|
$embedMeta = $this->loadMetadataForEmbed($mapping['entity']); |
289
|
|
|
} |
290
|
|
|
if (null === $embedMeta) { |
291
|
|
|
continue; |
292
|
|
|
} |
293
|
|
|
$property = new Metadata\EmbeddedPropMetadata($key, $mapping['type'], $embedMeta, $this->isMixin($metadata)); |
294
|
|
|
|
295
|
|
|
if (isset($mapping['serialize'])) { |
296
|
|
|
$property->enableSerialize($mapping['serialize']); |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
$metadata->addEmbed($property); |
300
|
|
|
} |
301
|
|
|
return $metadata; |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
/** |
305
|
|
|
* Sets creates mixin metadata instances from a set of mixin mappings ands sets them to the entity metadata instance. |
306
|
|
|
* |
307
|
|
|
* @param Metadata\Interfaces\MixinInterface $metadata |
308
|
|
|
* @param array $mixins |
309
|
|
|
* @return Metadata\Interfaces\MixinInterface |
310
|
|
|
*/ |
311
|
|
|
protected function setMixins(Metadata\Interfaces\MixinInterface $metadata, array $mixins) |
312
|
|
|
{ |
313
|
|
|
foreach ($mixins as $mixinName) { |
314
|
|
|
$mixinMeta = $this->loadMetadataForMixin($mixinName); |
315
|
|
|
if (null === $mixinMeta) { |
316
|
|
|
continue; |
317
|
|
|
} |
318
|
|
|
$metadata->addMixin($mixinMeta); |
319
|
|
|
} |
320
|
|
|
return $metadata; |
321
|
|
|
} |
322
|
|
|
|
323
|
|
|
/** |
324
|
|
|
* Sets the entity relationship metadata from the metadata mapping. |
325
|
|
|
* |
326
|
|
|
* @param Metadata\Interfaces\RelationshipInterface $metadata |
327
|
|
|
* @param array $relMapping |
328
|
|
|
* @return Metadata\Interfaces\RelationshipInterface |
329
|
|
|
* @throws RuntimeException If the related entity type was not found. |
330
|
|
|
*/ |
331
|
|
|
protected function setRelationships(Metadata\Interfaces\RelationshipInterface $metadata, array $relMapping) |
332
|
|
|
{ |
333
|
|
|
foreach ($relMapping as $key => $mapping) { |
334
|
|
View Code Duplication |
if (!is_array($mapping)) { |
|
|
|
|
335
|
|
|
$mapping = ['type' => null, 'entity' => null]; |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
if (!isset($mapping['type'])) { |
339
|
|
|
$mapping['type'] = null; |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
if (!isset($mapping['entity'])) { |
343
|
|
|
$mapping['entity'] = null; |
344
|
|
|
} |
345
|
|
|
|
346
|
|
|
if (!isset($mapping['search'])) { |
347
|
|
|
$mapping['search'] = []; |
348
|
|
|
} |
349
|
|
|
|
350
|
|
|
$relationship = new Metadata\RelationshipMetadata($key, $mapping['type'], $mapping['entity'], $this->isMixin($metadata)); |
351
|
|
|
|
352
|
|
|
if (isset($mapping['description'])) { |
353
|
|
|
$relationship->description = $mapping['description']; |
354
|
|
|
} |
355
|
|
|
|
356
|
|
|
if (isset($mapping['inverse'])) { |
357
|
|
|
$relationship->isInverse = true; |
358
|
|
|
if (isset($mapping['field'])) { |
359
|
|
|
$relationship->inverseField = $mapping['field']; |
360
|
|
|
} |
361
|
|
|
} |
362
|
|
|
|
363
|
|
|
$path = $this->getFilePath('model', $mapping['entity']); |
364
|
|
|
$relatedEntityMapping = $this->getMapping('model', $mapping['entity'], $path); |
365
|
|
|
|
366
|
|
View Code Duplication |
if (isset($relatedEntityMapping['entity']['polymorphic'])) { |
|
|
|
|
367
|
|
|
$relationship->setPolymorphic(true); |
368
|
|
|
$relationship->ownedTypes = $this->getOwnedTypes($mapping['entity']); |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
if (isset($mapping['search']['store'])) { |
372
|
|
|
$relationship->setSearchProperty(true); |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
if (isset($mapping['save'])) { |
376
|
|
|
$relationship->enableSave($mapping['save']); |
377
|
|
|
} |
378
|
|
|
|
379
|
|
|
if (isset($mapping['serialize'])) { |
380
|
|
|
$relationship->enableSerialize($mapping['serialize']); |
381
|
|
|
} |
382
|
|
|
|
383
|
|
|
$metadata->addRelationship($relationship); |
384
|
|
|
} |
385
|
|
|
return $metadata; |
386
|
|
|
} |
387
|
|
|
|
388
|
|
|
/** |
389
|
|
|
* Determines if a metadata instance is a mixin. |
390
|
|
|
* |
391
|
|
|
* @param Metadata\Interfaces\PropertyInterface $metadata |
392
|
|
|
* @return bool |
393
|
|
|
*/ |
394
|
|
|
protected function isMixin(Metadata\Interfaces\PropertyInterface $metadata) |
395
|
|
|
{ |
396
|
|
|
return $metadata instanceof Metadata\MixinMetadata; |
397
|
|
|
} |
398
|
|
|
|
399
|
|
|
/** |
400
|
|
|
* Sets default values to the metadata mapping array. |
401
|
|
|
* |
402
|
|
|
* @param string $metaType The metadata type, either model or mixin. |
403
|
|
|
* @param mixed $mapping The parsed mapping data. |
404
|
|
|
* @return array |
405
|
|
|
*/ |
406
|
|
|
protected function setDefaults($metaType, $mapping) |
407
|
|
|
{ |
408
|
|
|
if (!is_array($mapping)) { |
409
|
|
|
$mapping = []; |
410
|
|
|
} |
411
|
|
|
|
412
|
|
|
$mapping = $this->setRootDefault('attributes', $mapping); |
413
|
|
|
$mapping = $this->setRootDefault('embeds', $mapping); |
414
|
|
|
$mapping = $this->setRootDefault('mixins', $mapping); |
415
|
|
|
if ('embed' === $metaType) { |
416
|
|
|
return $mapping; |
417
|
|
|
} |
418
|
|
|
|
419
|
|
|
$mapping = $this->setRootDefault('relationships', $mapping); |
420
|
|
|
if ('mixin' === $metaType) { |
421
|
|
|
return $mapping; |
422
|
|
|
} |
423
|
|
|
$this->setRootDefault('entity', $mapping); |
424
|
|
|
|
425
|
|
View Code Duplication |
if (!isset($mapping['entity']['persistence']) || !is_array($mapping['entity']['persistence'])) { |
|
|
|
|
426
|
|
|
$mapping['entity']['persistence'] = []; |
427
|
|
|
} |
428
|
|
|
|
429
|
|
View Code Duplication |
if (!isset($mapping['entity']['search']) || !is_array($mapping['entity']['search'])) { |
|
|
|
|
430
|
|
|
$mapping['entity']['search'] = []; |
431
|
|
|
} |
432
|
|
|
return $mapping; |
433
|
|
|
} |
434
|
|
|
|
435
|
|
|
/** |
436
|
|
|
* Sets a root level default value to a metadata mapping array. |
437
|
|
|
* |
438
|
|
|
* @param string $key |
439
|
|
|
* @param array $mapping |
440
|
|
|
* @return array |
441
|
|
|
*/ |
442
|
|
|
private function setRootDefault($key, array $mapping) |
443
|
|
|
{ |
444
|
|
|
if (!isset($mapping[$key]) || !is_array($mapping[$key])) { |
445
|
|
|
$mapping[$key] = []; |
446
|
|
|
} |
447
|
|
|
return $mapping; |
448
|
|
|
} |
449
|
|
|
|
450
|
|
|
/** |
451
|
|
|
* {@inheritDoc} |
452
|
|
|
*/ |
453
|
|
|
protected function getExtension() |
454
|
|
|
{ |
455
|
|
|
return 'yml'; |
456
|
|
|
} |
457
|
|
|
} |
458
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.