MetadataFactory::loadPersistenceMetadata()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 9
rs 9.6666
cc 1
eloc 6
nc 1
nop 1
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
use As3\Modlr\Events\EventDispatcher;
11
12
/**
13
 * The primary MetadataFactory service.
14
 * Returns EntityMetadata instances for supplied entity types.
15
 * Can also write and retrieve these instances from cache, if supplied.
16
 *
17
 * @author Jacob Bare <[email protected]>
18
 */
19
class MetadataFactory implements MetadataFactoryInterface
20
{
21
    /**
22
     * The Metadata driver.
23
     *
24
     * @var DriverInterface
25
     */
26
    private $driver;
27
28
    /**
29
     * Entity utility. Used for formatting and validation.
30
     *
31
     * @var EntityUtility
32
     */
33
    private $entityUtil;
34
35
    /**
36
     * The event dispatcher
37
     *
38
     * @var EventDispatcher
39
     */
40
    private $dispatcher;
41
42
    /**
43
     * The Metadata cache instance.
44
     * Is optional, if defined using the setter.
45
     *
46
     * @var CacheInterface|null
47
     */
48
    private $cache;
49
50
    /**
51
     * Flags whether metadata caching is enabled.
52
     *
53
     * @var bool
54
     */
55
    private $cacheEnabled = true;
56
57
    /**
58
     * In-memory loaded Metadata instances.
59
     *
60
     * @var EntityMetadata[]
61
     */
62
    private $loaded;
63
64
    /**
65
     * Constructor.
66
     *
67
     * @param   DriverInterface $driver
68
     * @param   EntityUtility   $entityUtil
69
     * @param   EventDispatcher $dispatcher
70
     */
71
    public function __construct(DriverInterface $driver, EntityUtility $entityUtil, EventDispatcher $dispatcher)
72
    {
73
        $this->driver = $driver;
74
        $this->entityUtil = $entityUtil;
75
        $this->dispatcher = $dispatcher;
76
    }
77
78
    /**
79
     * Sets the cache instance to use for reading/writing Metadata objects.
80
     *
81
     * @param   CacheInterface  $cache
82
     * @return  self
83
     */
84
    public function setCache(CacheInterface $cache)
85
    {
86
        $this->cache = $cache;
87
        return $this;
88
    }
89
90
    /**
91
     * Gets the cache instance.
92
     *
93
     * @return  CacheInterface|null
94
     */
95
    public function getCache()
96
    {
97
        return $this->cache;
98
    }
99
100
    /**
101
     * Enables or disables the cache.
102
     *
103
     * @param   bool    $bit
104
     * @return  self
105
     */
106
    public function enableCache($bit = true)
107
    {
108
        $this->cacheEnabled = (Boolean) $bit;
109
        return $this;
110
    }
111
112
    /**
113
     * Determines if cache is enbled.
114
     *
115
     * @return  bool
116
     */
117
    public function hasCache()
118
    {
119
        return null !== $this->getCache() && true === $this->cacheEnabled;
120
    }
121
122
    /**
123
     * {@inheritDoc}
124
     */
125
    public function getMetadataForType($type)
126
    {
127
        if (null !== $metadata = $this->doLoadMetadata($type)) {
128
            // Found in memory or from cache implementation
129
            return $metadata;
130
        }
131
132
        // Loop through the type hierarchy (extension) and merge metadata objects.
133
        foreach ($this->driver->getTypeHierarchy($type) as $hierType) {
134
135
            if (null !== $loaded = $this->doLoadMetadata($hierType)) {
136
                // Found in memory or from cache implementation
137
                $this->mergeMetadata($metadata, $loaded);
138
                continue;
139
            }
140
141
            // Load from driver source
142
            $loaded = $this->driver->loadMetadataForType($hierType);
143
144
            if (null === $loaded) {
145
                throw MetadataException::mappingNotFound($type);
146
            }
147
148
            // Validate the metadata object.
149
            $this->entityUtil->validateMetadata($hierType, $loaded, $this);
150
151
            // Handle persistence specific loading and validation.
152
            $this->loadPersistenceMetadata($loaded);
153
154
            // Handle search specific loading and validation.
155
            $this->loadSearchMetadata($loaded);
156
157
            $this->mergeMetadata($metadata, $loaded);
158
            $this->dispatchMetadataEvent(Events::onMetadataLoad, $loaded);
159
            $this->doPutMetadata($loaded);
160
        }
161
162
        if (null === $metadata) {
163
            throw MetadataException::mappingNotFound($type);
164
        }
165
166
        $this->doPutMetadata($metadata);
167
        return $metadata;
168
    }
169
170
    /**
171
     * Dispatches a Metadata event
172
     *
173
     * @param   string          $eventName
174
     * @param   EntityMetadata  $metadata
175
     */
176
    private function dispatchMetadataEvent($eventName, EntityMetadata $metadata)
177
    {
178
        $metadataArgs = new Events\MetadataArguments($metadata);
179
        $this->dispatcher->dispatch($eventName, $metadataArgs);
180
    }
181
182
    /**
183
     * {@inheritDoc}
184
     */
185
    public function getAllTypeNames()
186
    {
187
        return $this->driver->getAllTypeNames();
188
    }
189
190
    /**
191
     * {@inheritDoc}
192
     */
193
    public function getAllMetadata()
194
    {
195
        $metadatas = [];
196
        foreach ($this->getAllTypeNames() as $type) {
197
            $metadatas[] = $this->getMetadataForType($type);
198
        }
199
        return $metadatas;
200
    }
201
202
    /**
203
     * {@inheritDoc}
204
     */
205
    public function metadataExists($type)
206
    {
207
        if (null !== $metadata = $this->doLoadMetadata($type)) {
208
            // Found in memory or from cache implementation
209
            return true;
210
        }
211
        return null !== $this->driver->loadMetadataForType($type);
212
    }
213
214
    /**
215
     * Determines if a type is direct child of another type.
216
     *
217
     * @param   string  $child
218
     * @param   string  $parent
219
     * @return  bool
220
     */
221
    public function isChildOf($child, $parent)
222
    {
223
        $childMeta = $this->getMetadataForType($child);
224
        if (false === $childMeta->isChildEntity()) {
225
            return false;
226
        }
227
        return $childMeta->getParentEntityType() === $parent;
228
    }
229
230
    /**
231
     * Determines if a type is an ancestor of another type.
232
     *
233
     * @param   string  $parent
234
     * @param   string  $child
235
     * @return  bool
236
     */
237
    public function isAncestorOf($parent, $child)
238
    {
239
        return $this->isDescendantOf($child, $parent);
240
    }
241
242
    /**
243
     * Determines if a type is a descendant of another type.
244
     *
245
     * @param   string  $child
246
     * @param   string  $parent
247
     * @return  bool
248
     */
249
    public function isDescendantOf($child, $parent)
250
    {
251
        $childMeta = $this->getMetadataForType($child);
252
        if (false === $childMeta->isChildEntity()) {
253
            return false;
254
        }
255
        if ($childMeta->getParentEntityType() === $parent) {
256
            return true;
257
        }
258
        return $this->isDescendantOf($childMeta->getParentEntityType(), $parent);
259
    }
260
261
    /**
262
     * @param   string  $parentType
263
     * @param   string  $childType
264
     * @return  bool
265
     * @throws  InvalidArgumentException
266
     */
267
    public function validateResourceTypes($parentType, $childType)
268
    {
269
        $meta = $this->getMetadataForType($parentType);
270
        if (true === $meta->isPolymorphic()) {
271
            if (true === $meta->isAbstract() && false === $this->isDescendantOf($childType, $parentType)) {
272
                throw new InvalidArgumentException(sprintf('The resource type "%s" is polymorphic and abstract. Resource "%s" must be a descendent of "%s"', $parentType, $childType, $parentType));
273
            }
274
        }
275
276
        if (false === $meta->isPolymorphic() && $parentType !== $childType) {
277
            throw new InvalidArgumentException(sprintf('This resource only supports resources of type "%s" - resource type "%s" was provided', $parentType, $childType));
278
        }
279
        return true;
280
    }
281
282
    /**
283
     * Handles persistence specific metadata loading.
284
     *
285
     * @param   EntityMetadata  $metadata
286
     * @return  EntityMetadata
287
     */
288
    private function loadPersistenceMetadata(EntityMetadata $metadata)
289
    {
290
        $persisterKey = $metadata->persistence->getKey();
291
        $persistenceFactory = $this->driver->getPersistenceMetadataFactory($persisterKey);
292
293
        $persistenceFactory->handleLoad($metadata);
294
        $persistenceFactory->handleValidate($metadata);
295
        return $metadata;
296
    }
297
298
    /**
299
     * Handles search specific metadata loading.
300
     *
301
     * @param   EntityMetadata  $metadata
302
     * @return  EntityMetadata
303
     */
304
    private function loadSearchMetadata(EntityMetadata $metadata)
305
    {
306
        if (false === $metadata->isSearchEnabled()) {
307
            return $metadata;
308
        }
309
        $clientKey = $metadata->search->getKey();
310
        $searchFactory = $this->driver->getSearchMetadataFactory($clientKey);
311
312
        $searchFactory->handleLoad($metadata);
313
        $searchFactory->handleValidate($metadata);
314
        return $metadata;
315
    }
316
317
    /**
318
     * Merges two sets of EntityMetadata.
319
     * Is used for applying inheritance information.
320
     *
321
     * @param   EntityMetadata  &$metadata
322
     * @param   EntityMetadata  $toAdd
323
     */
324
    private function mergeMetadata(EntityMetadata &$metadata = null, EntityMetadata $toAdd)
325
    {
326
        if (null === $metadata) {
327
            $metadata = clone $toAdd;
328
        } else {
329
            $metadata->merge($toAdd);
330
        }
331
    }
332
333
    /**
334
     * Attempts to load a Metadata instance from a memory or cache source.
335
     *
336
     * @param   string  $type
337
     * @return  EntityMetadata|null
338
     */
339
    private function doLoadMetadata($type)
340
    {
341
        if (null !== $meta = $this->getFromMemory($type)) {
342
            $this->dispatchMetadataEvent(Events::onMetadataCacheLoad, $meta);
343
            // Found in memory.
344
            return $meta;
345
        }
346
347
        if (null !== $meta = $this->getFromCache($type)) {
348
            // Found in cache.
349
            $this->dispatchMetadataEvent(Events::onMetadataCacheLoad, $meta);
350
            $this->setToMemory($meta);
351
            return $meta;
352
        }
353
        return null;
354
    }
355
356
    /**
357
     * Puts the Metadata instance into a cache source (if set) and memory.
358
     *
359
     * @param   EntityMetadata  $metadata
360
     * @return  self
361
     */
362
    private function doPutMetadata(EntityMetadata $metadata)
363
    {
364
        if (true === $this->hasCache()) {
365
            $this->cache->putMetadataInCache($metadata);
366
        }
367
        $this->setToMemory($metadata);
368
        return $this;
369
    }
370
371
    /**
372
     * Clears any loaded metadata objects from memory.
373
     *
374
     * @return  self
375
     */
376
    public function clearMemory()
377
    {
378
        $this->loaded = [];
379
        return $this;
380
    }
381
382
    /**
383
     * Gets a Metadata instance for a type from memory.
384
     *
385
     * @param   string  $type
386
     * @return  EntityMetadata|null
387
     */
388
    private function getFromMemory($type)
389
    {
390
        if (isset($this->loaded[$type])) {
391
            return $this->loaded[$type];
392
        }
393
        return null;
394
    }
395
396
    /**
397
     * Sets a Metadata instance to the memory cache.
398
     *
399
     * @param   EntityMetadata  $metadata
400
     * @return  self
401
     */
402
    private function setToMemory(EntityMetadata $metadata)
403
    {
404
        $this->loaded[$metadata->type] = $metadata;
405
        return $this;
406
    }
407
408
    /**
409
     * Retrieves a Metadata instance for a type from cache.
410
     *
411
     * @param   string  $type
412
     * @return  EntityMetadata|null
413
     */
414
    private function getFromCache($type)
415
    {
416
        if (false === $this->hasCache()) {
417
            return null;
418
        }
419
        return $this->cache->loadMetadataFromCache($type);
420
    }
421
}
422