Completed
Push — master ( dd6356...9040ae )
by Jacob
9s
created

YamlFileDriver::setEmbeds()   C

Complexity

Conditions 7
Paths 25

Size

Total Lines 29
Code Lines 16

Duplication

Lines 3
Ratio 10.34 %

Importance

Changes 3
Bugs 1 Features 2
Metric Value
c 3
b 1
f 2
dl 3
loc 29
rs 6.7272
cc 7
eloc 16
nc 25
nop 2
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'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
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'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
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
    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
    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->setRelationships($mixin, $mapping['relationships']);
88
        return $mixin;
89
    }
90
91
    /**
92
     * {@inheritDoc}
93
     */
94
    public function getTypeHierarchy($type, array $types = [])
95
    {
96
        $path = $this->getFilePath('model', $type);
97
        $mapping = $this->getMapping('model', $type, $path);
98
99
        $types[] = $type;
100
        if (isset($mapping['entity']['extends']) && $mapping['entity']['extends'] !== $type) {
101
            return $this->getTypeHierarchy($mapping['entity']['extends'], $types);
102
        }
103
        return array_reverse($types);
104
    }
105
106
    /**
107
     * {@inheritDoc}
108
     */
109
    public function getOwnedTypes($type, array $types = [])
110
    {
111
        $path = $this->getFilePath('model', $type);
112
        $superMapping = $this->getMapping('model', $type, $path);
113
114
        $abstract = isset($superMapping['entity']['abstract']) && true === $superMapping['entity']['abstract'];
115
116
        foreach ($this->getAllTypeNames() as $searchType) {
117
118
            if ($type === $searchType && false === $abstract) {
119
                $types[] = $type;
120
                continue;
121
            }
122
            if (0 !== strpos($searchType, $type)) {
123
                continue;
124
            }
125
126
            $path = $this->getFilePath('model', $searchType);
127
            $mapping = $this->getMapping('model', $searchType, $path);
128
129
            if (!isset($mapping['entity']['extends']) || $mapping['entity']['extends'] !== $type) {
130
                continue;
131
            }
132
            $types[] = $searchType;
133
        }
134
        return $types;
135
    }
136
137
    /**
138
     * Gets the metadata mapping information from the YAML file.
139
     *
140
     * @param   string  $metaType   The metadata type, either mixin or model.
141
     * @param   string  $key        The metadata key name, either the mixin name or model type.
142
     * @param   string  $file       The YAML file location.
143
     * @return  array
144
     * @throws  MetadataException
145
     */
146
    private function getMapping($metaType, $key, $file)
147
    {
148
        if (isset($this->mappings[$metaType][$key])) {
149
            // Set to array cache to prevent multiple gets/parses.
150
            return $this->mappings[$metaType][$key];
151
        }
152
153
        $contents = Yaml::parse(file_get_contents($file));
154
        if (!isset($contents[$key])) {
155
            throw MetadataException::fatalDriverError($key, sprintf('No mapping key was found at the beginning of the YAML file. Expected "%s"', $key));
156
        }
157
        return $this->mappings[$metaType][$key] = $this->setDefaults($metaType, $contents[$key]);
158
    }
159
160
    /**
161
     * Sets the entity persistence metadata from the metadata mapping.
162
     *
163
     * @param   Metadata\EntityMetadata     $metadata
164
     * @param   array                       $mapping
165
     * @return  Metadata\EntityMetadata
166
     */
167 View Code Duplication
    protected function setPersistence(Metadata\EntityMetadata $metadata, array $mapping)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
168
    {
169
        $persisterKey = isset($mapping['key']) ? $mapping['key'] : null;
170
        $factory = $this->getPersistenceMetadataFactory($persisterKey);
171
172
        $persistence = $factory->createInstance($mapping);
173
174
        $metadata->setPersistence($persistence);
175
        return $metadata;
176
    }
177
178
    /**
179
     * Sets the entity search metadata from the metadata mapping.
180
     *
181
     * @param   Metadata\EntityMetadata     $metadata
182
     * @param   array                       $mapping
183
     * @return  Metadata\EntityMetadata
184
     */
185 View Code Duplication
    protected function setSearch(Metadata\EntityMetadata $metadata, array $mapping)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
186
    {
187
        $clientKey = isset($mapping['key']) ? $mapping['key'] : null;
188
        if (null === $clientKey) {
189
            // Search is not enabled for this model.
190
            return $metadata;
191
        }
192
193
        $factory = $this->getSearchMetadataFactory($clientKey);
194
195
        $search = $factory->createInstance($mapping);
196
197
        $metadata->setSearch($search);
198
        return $metadata;
199
    }
200
201
    /**
202
     * Sets the entity attribute metadata from the metadata mapping.
203
     *
204
     * @param   Metadata\Interfaces\AttributeInterface  $metadata
205
     * @param   array                                   $attrMapping
206
     * @return  Metadata\EntityMetadata
207
     */
208
    protected function setAttributes(Metadata\Interfaces\AttributeInterface $metadata, array $attrMapping)
209
    {
210
        foreach ($attrMapping as $key => $mapping) {
211
            if (!is_array($mapping)) {
212
                $mapping = ['type' => null];
213
            }
214
215
            if (!isset($mapping['type'])) {
216
                $mapping['type'] = null;
217
            }
218
219
            if (!isset($mapping['search'])) {
220
                $mapping['search'] = [];
221
            }
222
223
            $attribute = new Metadata\AttributeMetadata($key, $mapping['type'], $this->isMixin($metadata));
224
225
            if (isset($mapping['description'])) {
226
                $attribute->description = $mapping['description'];
227
            }
228
229
            if (isset($mapping['defaultValue'])) {
230
                $attribute->defaultValue = $mapping['defaultValue'];
231
            }
232
233
            if (isset($mapping['calculated']) && is_array($mapping['calculated'])) {
234
                $calculated = $mapping['calculated'];
235
                if (isset($calculated['class']) && isset($calculated['method'])) {
236
                    $attribute->calculated['class']  =  $calculated['class'];
237
                    $attribute->calculated['method'] =  $calculated['method'];
238
                }
239
            }
240
241
            if (isset($mapping['search']['autocomplete'])) {
242
                $attribute->setAutocomplete(true);
243
            } else if (isset($mapping['search']['store'])) {
244
                $attribute->setSearchProperty(true);
245
            }
246
247
            if (isset($mapping['save'])) {
248
                $attribute->enableSave($mapping['save']);
249
            }
250
251
            if (isset($mapping['serialize'])) {
252
                $attribute->enableSerialize($mapping['serialize']);
253
            }
254
255
            $metadata->addAttribute($attribute);
256
        }
257
        return $metadata;
258
    }
259
260
    /**
261
     * Sets the entity embed metadata from the metadata mapping.
262
     *
263
     * @param   Metadata\Interfaces\EmbedInterface  $metadata
264
     * @param   array                               $embedMapping
265
     * @return  Metadata\EntityMetadata
266
     */
267
    protected function setEmbeds(Metadata\Interfaces\EmbedInterface $metadata, array $embedMapping)
268
    {
269
        foreach ($embedMapping as $key => $mapping) {
270 View Code Duplication
            if (!is_array($mapping)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
271
                $mapping = ['type' => null, 'entity' => null];
272
            }
273
274
            if (!isset($mapping['type'])) {
275
                $mapping['type'] = null;
276
            }
277
278
            if (!isset($mapping['entity'])) {
279
                $mapping['entity'] = null;
280
            }
281
282
            $embedMeta = $this->loadMetadataForEmbed($mapping['entity']);
283
            if (null === $embedMeta) {
284
                continue;
285
            }
286
            $property = new Metadata\EmbeddedPropMetadata($key, $mapping['type'], $embedMeta, $this->isMixin($metadata));
287
288
            if (isset($mapping['serialize'])) {
289
                $property->enableSerialize($mapping['serialize']);
290
            }
291
292
            $metadata->addEmbed($property);
293
        }
294
        return $metadata;
295
    }
296
297
    /**
298
     * Sets creates mixin metadata instances from a set of mixin mappings ands sets them to the entity metadata instance.
299
     *
300
     * @param   Metadata\Interfaces\MixinInterface  $metadata
301
     * @param   array                   $mixins
302
     * @return  Metadata\Interfaces\MixinInterface
303
     */
304
    protected function setMixins(Metadata\Interfaces\MixinInterface $metadata, array $mixins)
305
    {
306
        foreach ($mixins as $mixinName) {
307
            $mixinMeta = $this->loadMetadataForMixin($mixinName);
308
            if (null === $mixinMeta) {
309
                continue;
310
            }
311
            $metadata->addMixin($mixinMeta);
312
        }
313
        return $metadata;
314
    }
315
316
    /**
317
     * Sets the entity relationship metadata from the metadata mapping.
318
     *
319
     * @param   Metadata\Interfaces\RelationshipInterface   $metadata
320
     * @param   array                                       $relMapping
321
     * @return  Metadata\Interfaces\RelationshipInterface
322
     * @throws  RuntimeException If the related entity type was not found.
323
     */
324
    protected function setRelationships(Metadata\Interfaces\RelationshipInterface $metadata, array $relMapping)
325
    {
326
        foreach ($relMapping as $key => $mapping) {
327 View Code Duplication
            if (!is_array($mapping)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
328
                $mapping = ['type' => null, 'entity' => null];
329
            }
330
331
            if (!isset($mapping['type'])) {
332
                $mapping['type'] = null;
333
            }
334
335
            if (!isset($mapping['entity'])) {
336
                $mapping['entity'] = null;
337
            }
338
339
            if (!isset($mapping['search'])) {
340
                $mapping['search'] = [];
341
            }
342
343
            $relationship = new Metadata\RelationshipMetadata($key, $mapping['type'], $mapping['entity'], $this->isMixin($metadata));
344
345
            if (isset($mapping['description'])) {
346
                $relationship->description = $mapping['description'];
347
            }
348
349
            if (isset($mapping['inverse'])) {
350
                $relationship->isInverse = true;
351
                if (isset($mapping['field'])) {
352
                    $relationship->inverseField = $mapping['field'];
353
                }
354
            }
355
356
            $path = $this->getFilePath('model', $mapping['entity']);
357
            $relatedEntityMapping = $this->getMapping('model', $mapping['entity'], $path);
358
359 View Code Duplication
            if (isset($relatedEntityMapping['entity']['polymorphic'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
360
                $relationship->setPolymorphic(true);
361
                $relationship->ownedTypes = $this->getOwnedTypes($mapping['entity']);
362
            }
363
364
            if (isset($mapping['search']['store'])) {
365
                $relationship->setSearchProperty(true);
366
            }
367
368
            if (isset($mapping['save'])) {
369
                $relationship->enableSave($mapping['save']);
370
            }
371
372
            if (isset($mapping['serialize'])) {
373
                $relationship->enableSerialize($mapping['serialize']);
374
            }
375
376
            $metadata->addRelationship($relationship);
377
        }
378
        return $metadata;
379
    }
380
381
    /**
382
     * Determines if a metadata instance is a mixin.
383
     *
384
     * @param   Metadata\Interfaces\PropertyInterface   $metadata
385
     * @return  bool
386
     */
387
    protected function isMixin(Metadata\Interfaces\PropertyInterface $metadata)
388
    {
389
        return $metadata instanceof Metadata\MixinMetadata;
390
    }
391
392
    /**
393
     * Sets default values to the metadata mapping array.
394
     *
395
     * @param   string  $metaType   The metadata type, either model or mixin.
396
     * @param   mixed   $mapping    The parsed mapping data.
397
     * @return  array
398
     */
399
    protected function setDefaults($metaType, $mapping)
400
    {
401
        if (!is_array($mapping)) {
402
            $mapping = [];
403
        }
404
405
        $mapping = $this->setRootDefault('attributes', $mapping);
406
        $mapping = $this->setRootDefault('embeds', $mapping);
407
        $mapping = $this->setRootDefault('mixins', $mapping);
408
        if ('embed' === $metaType) {
409
            return $mapping;
410
        }
411
412
        $mapping = $this->setRootDefault('relationships', $mapping);
413
        if ('mixin' === $metaType) {
414
            return $mapping;
415
        }
416
        $this->setRootDefault('entity', $mapping);
417
418 View Code Duplication
        if (!isset($mapping['entity']['persistence']) || !is_array($mapping['entity']['persistence'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
419
            $mapping['entity']['persistence'] = [];
420
        }
421
422 View Code Duplication
        if (!isset($mapping['entity']['search']) || !is_array($mapping['entity']['search'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
423
            $mapping['entity']['search'] = [];
424
        }
425
        return $mapping;
426
    }
427
428
    /**
429
     * Sets a root level default value to a metadata mapping array.
430
     *
431
     * @param   string  $key
432
     * @param   array   $mapping
433
     * @return  array
434
     */
435
    private function setRootDefault($key, array $mapping)
436
    {
437
        if (!isset($mapping[$key]) || !is_array($mapping[$key])) {
438
            $mapping[$key] = [];
439
        }
440
        return $mapping;
441
    }
442
443
    /**
444
     * {@inheritDoc}
445
     */
446
    protected function getExtension()
447
    {
448
        return 'yml';
449
    }
450
}
451