Completed
Push — master ( e24a08...649c71 )
by Joshua
9s
created

YamlFileDriver::setEmbeds()   B

Complexity

Conditions 6
Paths 17

Size

Total Lines 24
Code Lines 14

Duplication

Lines 3
Ratio 12.5 %

Importance

Changes 2
Bugs 1 Features 1
Metric Value
c 2
b 1
f 1
dl 3
loc 24
rs 8.5125
cc 6
eloc 14
nc 17
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
            $metadata->addAttribute($attribute);
252
        }
253
        return $metadata;
254
    }
255
256
    /**
257
     * Sets the entity embed metadata from the metadata mapping.
258
     *
259
     * @param   Metadata\Interfaces\EmbedInterface  $metadata
260
     * @param   array                               $embedMapping
261
     * @return  Metadata\EntityMetadata
262
     */
263
    protected function setEmbeds(Metadata\Interfaces\EmbedInterface $metadata, array $embedMapping)
264
    {
265
        foreach ($embedMapping as $key => $mapping) {
266 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...
267
                $mapping = ['type' => null, 'entity' => null];
268
            }
269
270
            if (!isset($mapping['type'])) {
271
                $mapping['type'] = null;
272
            }
273
274
            if (!isset($mapping['entity'])) {
275
                $mapping['entity'] = null;
276
            }
277
278
            $embedMeta = $this->loadMetadataForEmbed($mapping['entity']);
279
            if (null === $embedMeta) {
280
                continue;
281
            }
282
            $property = new Metadata\EmbeddedPropMetadata($key, $mapping['type'], $embedMeta, $this->isMixin($metadata));
283
            $metadata->addEmbed($property);
284
        }
285
        return $metadata;
286
    }
287
288
    /**
289
     * Sets creates mixin metadata instances from a set of mixin mappings ands sets them to the entity metadata instance.
290
     *
291
     * @param   Metadata\Interfaces\MixinInterface  $metadata
292
     * @param   array                   $mixins
293
     * @return  Metadata\Interfaces\MixinInterface
294
     */
295
    protected function setMixins(Metadata\Interfaces\MixinInterface $metadata, array $mixins)
296
    {
297
        foreach ($mixins as $mixinName) {
298
            $mixinMeta = $this->loadMetadataForMixin($mixinName);
299
            if (null === $mixinMeta) {
300
                continue;
301
            }
302
            $metadata->addMixin($mixinMeta);
303
        }
304
        return $metadata;
305
    }
306
307
    /**
308
     * Sets the entity relationship metadata from the metadata mapping.
309
     *
310
     * @param   Metadata\Interfaces\RelationshipInterface   $metadata
311
     * @param   array                                       $relMapping
312
     * @return  Metadata\Interfaces\RelationshipInterface
313
     * @throws  RuntimeException If the related entity type was not found.
314
     */
315
    protected function setRelationships(Metadata\Interfaces\RelationshipInterface $metadata, array $relMapping)
316
    {
317
        foreach ($relMapping as $key => $mapping) {
318 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...
319
                $mapping = ['type' => null, 'entity' => null];
320
            }
321
322
            if (!isset($mapping['type'])) {
323
                $mapping['type'] = null;
324
            }
325
326
            if (!isset($mapping['entity'])) {
327
                $mapping['entity'] = null;
328
            }
329
330
            if (!isset($mapping['search'])) {
331
                $mapping['search'] = [];
332
            }
333
334
            $relationship = new Metadata\RelationshipMetadata($key, $mapping['type'], $mapping['entity'], $this->isMixin($metadata));
335
336
            if (isset($mapping['description'])) {
337
                $relationship->description = $mapping['description'];
338
            }
339
340
            if (isset($mapping['inverse'])) {
341
                $relationship->isInverse = true;
342
                if (isset($mapping['field'])) {
343
                    $relationship->inverseField = $mapping['field'];
344
                }
345
            }
346
347
            $path = $this->getFilePath('model', $mapping['entity']);
348
            $relatedEntityMapping = $this->getMapping('model', $mapping['entity'], $path);
349
350 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...
351
                $relationship->setPolymorphic(true);
352
                $relationship->ownedTypes = $this->getOwnedTypes($mapping['entity']);
353
            }
354
355
            if (isset($mapping['search']['store'])) {
356
                $relationship->setSearchProperty(true);
357
            }
358
359
            if (isset($mapping['save'])) {
360
                $relationship->enableSave($mapping['save']);
361
            }
362
363
            $metadata->addRelationship($relationship);
364
        }
365
        return $metadata;
366
    }
367
368
    /**
369
     * Determines if a metadata instance is a mixin.
370
     *
371
     * @param   Metadata\Interfaces\PropertyInterface   $metadata
372
     * @return  bool
373
     */
374
    protected function isMixin(Metadata\Interfaces\PropertyInterface $metadata)
375
    {
376
        return $metadata instanceof Metadata\MixinMetadata;
377
    }
378
379
    /**
380
     * Sets default values to the metadata mapping array.
381
     *
382
     * @param   string  $metaType   The metadata type, either model or mixin.
383
     * @param   mixed   $mapping    The parsed mapping data.
384
     * @return  array
385
     */
386
    protected function setDefaults($metaType, $mapping)
387
    {
388
        if (!is_array($mapping)) {
389
            $mapping = [];
390
        }
391
392
        $mapping = $this->setRootDefault('attributes', $mapping);
393
        $mapping = $this->setRootDefault('embeds', $mapping);
394
        $mapping = $this->setRootDefault('mixins', $mapping);
395
        if ('embed' === $metaType) {
396
            return $mapping;
397
        }
398
399
        $mapping = $this->setRootDefault('relationships', $mapping);
400
        if ('mixin' === $metaType) {
401
            return $mapping;
402
        }
403
        $this->setRootDefault('entity', $mapping);
404
405 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...
406
            $mapping['entity']['persistence'] = [];
407
        }
408
409 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...
410
            $mapping['entity']['search'] = [];
411
        }
412
        return $mapping;
413
    }
414
415
    /**
416
     * Sets a root level default value to a metadata mapping array.
417
     *
418
     * @param   string  $key
419
     * @param   array   $mapping
420
     * @return  array
421
     */
422
    private function setRootDefault($key, array $mapping)
423
    {
424
        if (!isset($mapping[$key]) || !is_array($mapping[$key])) {
425
            $mapping[$key] = [];
426
        }
427
        return $mapping;
428
    }
429
430
    /**
431
     * {@inheritDoc}
432
     */
433
    protected function getExtension()
434
    {
435
        return 'yml';
436
    }
437
}
438