Completed
Push — master ( 79d9b9...58badd )
by Jacob
10s
created

MetadataFactory::mergeMetadata()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 8
rs 9.4285
cc 2
eloc 5
nc 2
nop 2
1
<?php
2
3
namespace As3\Modlr\Metadata;
4
5
use As3\Modlr\Exception\MetadataException;
6
use As3\Modlr\Metadata\Driver\DriverInterface;
7
use As3\Modlr\Metadata\Cache\CacheInterface;
8
use As3\Modlr\Util\EntityUtility;
9
use As3\Modlr\Exception\InvalidArgumentException;
10
11
/**
12
 * The primary MetadataFactory service.
13
 * Returns EntityMetadata instances for supplied entity types.
14
 * Can also write and retrieve these instances from cache, if supplied.
15
 *
16
 * @author Jacob Bare <[email protected]>
17
 */
18
class MetadataFactory implements MetadataFactoryInterface
19
{
20
    /**
21
     * The Metadata driver.
22
     *
23
     * @var DriverInterface
24
     */
25
    private $driver;
26
27
    /**
28
     * Entity utility. Used for formatting and validation.
29
     *
30
     * @var EntityUtility
31
     */
32
    private $entityUtil;
33
34
    /**
35
     * The Metadata cache instance.
36
     * Is optional, if defined using the setter.
37
     *
38
     * @var CacheInterface
39
     */
40
    private $cache;
41
42
    /**
43
     * Flags whether metadata caching is enabled.
44
     *
45
     * @var bool
46
     */
47
    private $cacheEnabled = true;
48
49
    /**
50
     * In-memory loaded Metadata instances.
51
     *
52
     * @var EntityMetadata[]
53
     */
54
    private $loaded;
55
56
    /**
57
     * Constructor.
58
     *
59
     * @param   DriverInterface $driver
60
     */
61
    public function __construct(DriverInterface $driver, EntityUtility $entityUtil)
62
    {
63
        $this->driver = $driver;
64
        $this->entityUtil = $entityUtil;
65
    }
66
67
    /**
68
     * Sets the cache instance to use for reading/writing Metadata objects.
69
     *
70
     * @param   CacheInterface  $cache
71
     * @return  self
72
     */
73
    public function setCache(CacheInterface $cache)
74
    {
75
        $this->cache = $cache;
76
        return $this;
77
    }
78
79
    /**
80
     * Gets the cache instance.
81
     *
82
     * @return  CacheInterface|null
83
     */
84
    public function getCache()
85
    {
86
        return $this->cache;
87
    }
88
89
    /**
90
     * Enables or disables the cache.
91
     *
92
     * @param   bool    $bit
93
     * @return  self
94
     */
95
    public function enableCache($bit = true)
96
    {
97
        $this->cacheEnabled = (Boolean) $bit;
98
        return $this;
99
    }
100
101
    /**
102
     * Determines if cache is enbled.
103
     *
104
     * @return  bool
105
     */
106
    public function hasCache()
107
    {
108
        return null !== $this->getCache() && true === $this->cacheEnabled;
109
    }
110
111
    /**
112
     * {@inheritDoc}
113
     */
114
    public function getMetadataForType($type)
115
    {
116
        if (null !== $metadata = $this->doLoadMetadata($type)) {
117
            // Found in memory or from cache implementation
118
            return $metadata;
119
        }
120
121
        // Loop through the type hierarchy (extension) and merge metadata objects.
122
        foreach ($this->driver->getTypeHierarchy($type) as $hierType) {
123
124
            if (null !== $loaded = $this->doLoadMetadata($hierType)) {
125
                // Found in memory or from cache implementation
126
                $this->mergeMetadata($metadata, $loaded);
127
                continue;
128
            }
129
130
            // Load from driver source
131
            $loaded = $this->driver->loadMetadataForType($hierType);
132
133
            if (null === $loaded) {
134
                throw MetadataException::mappingNotFound($type);
135
            }
136
137
            // Validate the metadata object.
138
            $this->entityUtil->validateMetadata($hierType, $loaded, $this);
139
140
            // Handle persistence specific loading and validation.
141
            $this->loadPersistenceMetadata($loaded);
142
143
            // Handle search specific loading and validation.
144
            $this->loadSearchMetadata($loaded);
145
146
            $this->mergeMetadata($metadata, $loaded);
147
            $this->doPutMetadata($loaded);
148
        }
149
150
        if (null === $metadata) {
151
            throw MetadataException::mappingNotFound($type);
152
        }
153
154
        $this->doPutMetadata($metadata);
155
        return $metadata;
156
    }
157
158
    /**
159
     * {@inheritDoc}
160
     */
161
    public function getAllTypeNames()
162
    {
163
        return $this->driver->getAllTypeNames();
164
    }
165
166
    /**
167
     * {@inheritDoc}
168
     */
169
    public function getAllMetadata()
170
    {
171
        $metadatas = [];
172
        foreach ($this->getAllTypeNames() as $type) {
173
            $metadatas[] = $this->getMetadataForType($type);
174
        }
175
        return $metadatas;
176
    }
177
178
    /**
179
     * {@inheritDoc}
180
     */
181
    public function metadataExists($type)
182
    {
183
        if (null !== $metadata = $this->doLoadMetadata($type)) {
184
            // Found in memory or from cache implementation
185
            return true;
186
        }
187
        return null !== $this->driver->loadMetadataForType($type);
188
    }
189
190
    /**
191
     * Determines if a type is direct child of another type.
192
     *
193
     * @param   string  $child
194
     * @param   string  $parent
195
     * @return  bool
196
     */
197
    public function isChildOf($child, $parent)
198
    {
199
        $childMeta = $this->getMetadataForType($child);
200
        if (false === $childMeta->isChildEntity()) {
201
            return false;
202
        }
203
        return $childMeta->getParentEntityType() === $parent;
204
    }
205
206
    /**
207
     * Determines if a type is an ancestor of another type.
208
     *
209
     * @param   string  $parent
210
     * @param   string  $child
211
     * @return  bool
212
     */
213
    public function isAncestorOf($parent, $child)
214
    {
215
        return $this->isDescendantOf($child, $parent);
216
    }
217
218
    /**
219
     * Determines if a type is a descendant of another type.
220
     *
221
     * @param   string  $child
222
     * @param   string  $parent
223
     * @return  bool
224
     */
225
    public function isDescendantOf($child, $parent)
226
    {
227
        $childMeta = $this->getMetadataForType($child);
228
        if (false === $childMeta->isChildEntity()) {
229
            return false;
230
        }
231
        if ($childMeta->getParentEntityType() === $parent) {
232
            return true;
233
        }
234
        return $this->isDescendantOf($childMeta->getParentEntityType(), $parent);
0 ignored issues
show
Documentation introduced by
$childMeta->getParentEntityType() is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
235
    }
236
237
    public function validateResourceTypes($parentType, $childType)
238
    {
239
        $meta = $this->getMetadataForType($parentType);
240
        if (true === $meta->isPolymorphic()) {
241
            if (true === $meta->isAbstract() && false === $this->isDescendantOf($childType, $parentType)) {
242
                throw new InvalidArgumentException(sprintf('The resource type "%s" is polymorphic and abstract. Resource "%s" must be a descendent of "%s"', $parentType, $childType, $parentType));
243
            }
244
        }
245
246
        if (false === $meta->isPolymorphic() && $parentType !== $childType) {
247
            throw new InvalidArgumentException(sprintf('This resource only supports resources of type "%s" - resource type "%s" was provided', $parentType, $childType));
248
        }
249
        return true;
250
    }
251
252
    /**
253
     * Handles persistence specific metadata loading.
254
     *
255
     * @param   EntityMetadata  $metadata
256
     * @return  EntityMetadata
257
     */
258
    private function loadPersistenceMetadata(EntityMetadata $metadata)
259
    {
260
        $persisterKey = $metadata->persistence->getKey();
261
        $persistenceFactory = $this->driver->getPersistenceMetadataFactory($persisterKey);
262
263
        $persistenceFactory->handleLoad($metadata);
264
        $persistenceFactory->handleValidate($metadata);
265
        return $metadata;
266
    }
267
268
    /**
269
     * Handles search specific metadata loading.
270
     *
271
     * @param   EntityMetadata  $metadata
272
     * @return  EntityMetadata
273
     */
274
    private function loadSearchMetadata(EntityMetadata $metadata)
275
    {
276
        if (false === $metadata->isSearchEnabled()) {
277
            return $metadata;
278
        }
279
        $clientKey = $metadata->search->getKey();
280
        $searchFactory = $this->driver->getSearchMetadataFactory($clientKey);
281
282
        $searchFactory->handleLoad($metadata);
283
        $searchFactory->handleValidate($metadata);
284
        return $metadata;
285
    }
286
287
    /**
288
     * Merges two sets of EntityMetadata.
289
     * Is used for applying inheritance information.
290
     *
291
     * @param   EntityMetadata  &$metadata
292
     * @param   EntityMetadata  $toAdd
293
     */
294
    private function mergeMetadata(EntityMetadata &$metadata = null, EntityMetadata $toAdd)
295
    {
296
        if (null === $metadata) {
297
            $metadata = clone $toAdd;
298
        } else {
299
            $metadata->merge($toAdd);
300
        }
301
    }
302
303
    /**
304
     * Attempts to load a Metadata instance from a memory or cache source.
305
     *
306
     * @param   string  $type
307
     * @return  EntityMetadata|null
308
     */
309
    private function doLoadMetadata($type)
310
    {
311
        if (null !== $meta = $this->getFromMemory($type)) {
312
            // Found in memory.
313
            return $meta;
314
        }
315
316
        if (null !== $meta = $this->getFromCache($type)) {
317
            // Found in cache.
318
            $this->setToMemory($meta);
319
            return $meta;
320
        }
321
        return null;
322
    }
323
324
    /**
325
     * Puts the Metadata instance into a cache source (if set) and memory.
326
     *
327
     * @param   EntityMetadata  $metadata
328
     * @return  self
329
     */
330
    private function doPutMetadata(EntityMetadata $metadata)
331
    {
332
        if (true === $this->hasCache()) {
333
            $this->cache->putMetadataInCache($metadata);
334
        }
335
        $this->setToMemory($metadata);
336
        return $this;
337
    }
338
339
    /**
340
     * Clears any loaded metadata objects from memory.
341
     *
342
     * @return  self
343
     */
344
    public function clearMemory()
345
    {
346
        $this->loaded = [];
347
        return $this;
348
    }
349
350
    /**
351
     * Gets a Metadata instance for a type from memory.
352
     *
353
     * @return  EntityMetadata|null
354
     */
355
    private function getFromMemory($type)
356
    {
357
        if (isset($this->loaded[$type])) {
358
            return $this->loaded[$type];
359
        }
360
        return null;
361
    }
362
363
    /**
364
     * Sets a Metadata instance to the memory cache.
365
     *
366
     * @param   EntityMetadata  $metadata
367
     * @return  self
368
     */
369
    private function setToMemory(EntityMetadata $metadata)
370
    {
371
        $this->loaded[$metadata->type] = $metadata;
372
        return $this;
373
    }
374
375
    /**
376
     * Retrieves a Metadata instance for a type from cache.
377
     *
378
     * @return  EntityMetadata|null
379
     */
380
    private function getFromCache($type)
381
    {
382
        if (false === $this->hasCache()) {
383
            return null;
384
        }
385
        return $this->cache->loadMetadataFromCache($type);
386
    }
387
}
388