Completed
Push — master ( f2cba1...1a17ec )
by Jacob
04:37 queued 01:33
created

EntityMetadata::merge()   B

Complexity

Conditions 4
Paths 3

Size

Total Lines 26
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 26
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 17
nc 3
nop 1
1
<?php
2
3
namespace As3\Modlr\Metadata;
4
5
use As3\Modlr\Exception\MetadataException;
6
7
/**
8
 * Defines the metadata for an entity (e.g. a database object).
9
 * Should be loaded using the MetadataFactory, not instantiated directly.
10
 *
11
 * @author  Jacob Bare <[email protected]>
12
 * @todo    This should be renamed to ModelMetadata.
13
 */
14
class EntityMetadata implements Interfaces\AttributeInterface, Interfaces\EmbedInterface, Interfaces\MergeableInterface, Interfaces\MixinInterface, Interfaces\RelationshipInterface
15
{
16
    /**
17
     * Uses attributes.
18
     */
19
    use Traits\AttributesTrait;
20
21
    /**
22
     * Uses embeds.
23
     */
24
    use Traits\EmbedsTrait;
25
26
    /**
27
     * Uses mixins.
28
     */
29
    use Traits\MixinsTrait;
30
31
    /**
32
     * Uses merged properties.
33
     */
34
    use Traits\PropertiesTrait;
35
36
    /**
37
     * Uses relationships.
38
     */
39
    use Traits\RelationshipsTrait;
40
41
    /**
42
     * The id key name and type.
43
     */
44
    const ID_KEY  = 'id';
45
    const ID_TYPE = 'string';
46
47
    /**
48
     * The model type key.
49
     */
50
    const TYPE_KEY = 'type';
51
52
    /**
53
     * Whether this class is abstract.
54
     *
55
     * @var bool
56
     */
57
    public $abstract = false;
58
59
    /**
60
     * An array of attribute default values for this model.
61
     * Keyed by field name.
62
     *
63
     * @var array
64
     */
65
    public $defaultValues = [];
66
67
    /**
68
     * The entity type this entity extends.
69
     *
70
     * @var string|null
71
     */
72
    public $extends;
73
74
    /**
75
     * Child entity types this entity owns.
76
     * Only used for polymorphic entities.
77
     *
78
     * @var array
79
     */
80
    public $ownedTypes = [];
81
82
    /**
83
     * The persistence metadata for this entity.
84
     *
85
     * @var Interfaces\StorageLayerInterface
86
     */
87
    public $persistence;
88
89
    /**
90
     * Whether this class is considered polymorphic.
91
     *
92
     * @var bool
93
     */
94
    public $polymorphic = false;
95
96
    /**
97
     * The search metadata for this entity.
98
     *
99
     * @var Interfaces\StorageLayerInterface
100
     */
101
    public $search;
102
103
    /**
104
     * Uniquely defines the type of entity.
105
     *
106
     * @var string
107
     */
108
    public $type;
109
110
    /**
111
     * Constructor.
112
     *
113
     * @param   string  $type   The resource identifier type.
114
     */
115
    public function __construct($type)
116
    {
117
        $this->setType($type);
118
    }
119
120
    /**
121
     * Gets the parent entity type.
122
     * For entities that are extended.
123
     *
124
     * @return  string|null
125
     */
126
    public function getParentEntityType()
127
    {
128
        return $this->extends;
129
    }
130
131
    /**
132
     * {@inheritdoc}
133
     */
134
    public function getProperties()
135
    {
136
        return array_merge($this->getAttributes(), $this->getRelationships(), $this->getEmbeds());
137
    }
138
139
    /**
140
     * Whether this metadata represents an abstract class.
141
     *
142
     * @return  bool
143
     */
144
    public function isAbstract()
145
    {
146
        return (Boolean) $this->abstract;
147
    }
148
149
    /**
150
     * Whether this metadata represents a polymorphic class.
151
     *
152
     * @return  bool
153
     */
154
    public function isPolymorphic()
155
    {
156
        return (Boolean) $this->polymorphic;
157
    }
158
159
    /**
160
     * Deteremines whether search is enabled for this model.
161
     *
162
     * @return  bool
163
     */
164
    public function isSearchEnabled()
165
    {
166
        return null !== $this->search;
167
    }
168
169
    /**
170
     * Determines if this is a child entity of another entity.
171
     *
172
     * @return  bool
173
     */
174
    public function isChildEntity()
175
    {
176
        return null !== $this->getParentEntityType();
177
    }
178
179
    /**
180
     * {@inheritDoc}
181
     */
182
    public function merge(Interfaces\MergeableInterface $metadata)
183
    {
184
        if (!$metadata instanceof EntityMetadata) {
185
            throw new MetadataException('Unable to merge metadata. The provided metadata instance is not compatible.');
186
        }
187
188
        $this->setType($metadata->type);
189
        $this->setPolymorphic($metadata->isPolymorphic());
190
        $this->setAbstract($metadata->isAbstract());
191
        $this->extends = $metadata->extends;
192
        $this->ownedTypes = $metadata->ownedTypes;
193
        $this->defaultValues = array_merge($this->defaultValues, $metadata->defaultValues);
194
195
        $this->persistence->merge($metadata->persistence);
196
197
        if ($this->search instanceof Interfaces\StorageLayerInterface && $metadata->search instanceof Interfaces\StorageLayerInterface) {
198
            $this->search->merge($metadata->search);
199
        }
200
201
        $this->mergeAttributes($metadata->getAttributes());
202
        $this->mergeRelationships($metadata->getRelationships());
203
        $this->mergeEmbeds($metadata->getEmbeds());
204
        $this->mergeMixins($metadata->getMixins());
205
206
        return $this;
207
    }
208
209
    /**
210
     * Sets this metadata as representing an abstract class.
211
     *
212
     * @param   bool    $bit
213
     * @return  self
214
     */
215
    public function setAbstract($bit = true)
216
    {
217
        $this->abstract = (Boolean) $bit;
218
        return $this;
219
    }
220
221
    /**
222
     * Sets the persistence metadata for this entity.
223
     *
224
     * @param   Interfaces\StorageLayerInterface    $persistence
225
     * @return  self
226
     */
227
    public function setPersistence(Interfaces\StorageLayerInterface $persistence)
228
    {
229
        $this->persistence = $persistence;
230
        return $this;
231
    }
232
233
    /**
234
     * Sets this metadata as representing a polymorphic class.
235
     *
236
     * @param   bool    $bit
237
     * @return  self
238
     */
239
    public function setPolymorphic($bit = true)
240
    {
241
        $this->polymorphic = (Boolean) $bit;
242
        return $this;
243
    }
244
245
    /**
246
     * Sets the search metadata for this entity.
247
     *
248
     * @param   Interfaces\StorageLayerInterface    $search
249
     * @return  self
250
     */
251
    public function setSearch(Interfaces\StorageLayerInterface $search)
252
    {
253
        $this->search = $search;
254
        return $this;
255
    }
256
257
    /**
258
     * Sets the entity type.
259
     *
260
     * @param   string  $type
261
     * @return  self
262
     * @throws  MetadataException   If the type is not a string or is empty.
263
     */
264
    public function setType($type)
265
    {
266
        if (!is_string($type) || empty($type)) {
267
            throw MetadataException::invalidEntityType($type);
268
        }
269
        $this->type = $type;
270
        return $this;
271
    }
272
273
    /**
274
     * {@inheritdoc}
275
     */
276 View Code Duplication
    protected function applyMixinProperties(MixinMetadata $mixin)
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...
277
    {
278
        foreach ($mixin->getAttributes() as $attribute) {
279
            if (true === $this->hasAttribute($attribute->key)) {
280
                throw MetadataException::mixinPropertyExists($this->type, $mixin->name, 'attribute', $attribute->key);
281
            }
282
            $this->addAttribute($attribute);
283
        }
284
        foreach ($mixin->getRelationships() as $relationship) {
285
            if (true === $this->hasRelationship($relationship->key)) {
286
                throw MetadataException::mixinPropertyExists($this->type, $mixin->name, 'relationship', $relationship->key);
287
            }
288
            $this->addRelationship($relationship);
289
        }
290
        foreach ($mixin->getEmbeds() as $embed) {
291
            if (true === $this->hasEmbed($embed->key)) {
292
                throw MetadataException::mixinPropertyExists($this->type, $mixin->name, 'embed', $embed->key);
293
            }
294
            $this->addEmbed($embed);
295
        }
296
    }
297
298
    /**
299
     * {@inheritdoc}
300
     */
301 View Code Duplication
    protected function validateAttribute(AttributeMetadata $attribute)
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...
302
    {
303
        if (true === $this->hasRelationship($attribute->getKey())) {
304
            throw MetadataException::fieldKeyInUse('attribute', 'relationship', $attribute->getKey(), $this->type);
305
        }
306
        if (true === $this->hasEmbed($attribute->getKey())) {
307
            throw MetadataException::fieldKeyInUse('attribute', 'embed', $attribute->getKey(), $this->type);
308
        }
309
    }
310
311
    /**
312
     * {@inheritdoc}
313
     */
314 View Code Duplication
    protected function validateEmbed(EmbeddedPropMetadata $embed)
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...
315
    {
316
        if (true === $this->hasAttribute($embed->getKey())) {
317
            throw MetadataException::fieldKeyInUse('embed', 'attribute', $embed->getKey(), $this->type);
318
        }
319
        if (true === $this->hasRelationship($embed->getKey())) {
320
            throw MetadataException::fieldKeyInUse('embed', 'relationship', $embed->getKey(), $this->type);
321
        }
322
    }
323
324
    /**
325
     * {@inheritdoc}
326
     */
327 View Code Duplication
    protected function validateRelationship(RelationshipMetadata $relationship)
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...
328
    {
329
        if (true === $this->hasAttribute($relationship->getKey())) {
330
            throw MetadataException::fieldKeyInUse('relationship', 'attribute', $relationship->getKey(), $this->type);
331
        }
332
        if (true === $this->hasEmbed($relationship->getKey())) {
333
            throw MetadataException::fieldKeyInUse('relationship', 'embed', $relationship->getKey(), $this->type);
334
        }
335
    }
336
337
    /**
338
     * Merges attributes with this instance's attributes.
339
     *
340
     * @param   AttributeMetadata[]     $toAdd
341
     * @return  self
342
     */
343
    private function mergeAttributes(array $toAdd)
344
    {
345
        foreach ($toAdd as $attribute) {
346
            $this->addAttribute($attribute);
347
        }
348
        return $this;
349
    }
350
351
    /**
352
     * Merges embeds with this instance's embeds.
353
     *
354
     * @param   EmbeddedPropMetadata[]  $toAdd
355
     * @return  self
356
     */
357
    private function mergeEmbeds(array $toAdd)
358
    {
359
        foreach ($toAdd as $embed) {
360
            $this->addEmbed($embed);
361
        }
362
        return $this;
363
    }
364
365
    /**
366
     * Merges mixins with this instance's mixins.
367
     *
368
     * @param   MixinMetadata[]     $toAdd
369
     * @return  self
370
     */
371
    private function mergeMixins(array $toAdd)
372
    {
373
        foreach ($toAdd as $mixin) {
374
            if (!isset($this->mixins[$mixin->name])) {
375
                $this->mixins[$mixin->name] = $mixin;
376
            }
377
        }
378
        return $this;
379
    }
380
381
    /**
382
     * Merges relationships with this instance's relationships.
383
     *
384
     * @param   RelationshipMetadata[]  $toAdd
385
     * @return  self
386
     */
387
    private function mergeRelationships(array $toAdd)
388
    {
389
        foreach ($toAdd as $relationship) {
390
            $this->addRelationship($relationship);
391
        }
392
        return $this;
393
    }
394
}
395