Completed
Pull Request — master (#74)
by Joshua
02:57
created

MetadataFactory::dispatchMetadataEvent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 5
rs 9.4285
cc 1
eloc 3
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
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($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   EntityMetadata  $metadata
174
     */
175
    private function dispatchMetadataEvent(EntityMetadata $metadata)
176
    {
177
        $metadataArgs = new Events\MetadataArguments($metadata);
178
        $this->dispatcher->dispatch(Events::postLoad, $metadataArgs);
179
    }
180
181
    /**
182
     * {@inheritDoc}
183
     */
184
    public function getAllTypeNames()
185
    {
186
        return $this->driver->getAllTypeNames();
187
    }
188
189
    /**
190
     * {@inheritDoc}
191
     */
192
    public function getAllMetadata()
193
    {
194
        $metadatas = [];
195
        foreach ($this->getAllTypeNames() as $type) {
196
            $metadatas[] = $this->getMetadataForType($type);
197
        }
198
        return $metadatas;
199
    }
200
201
    /**
202
     * {@inheritDoc}
203
     */
204
    public function metadataExists($type)
205
    {
206
        if (null !== $metadata = $this->doLoadMetadata($type)) {
207
            // Found in memory or from cache implementation
208
            return true;
209
        }
210
        return null !== $this->driver->loadMetadataForType($type);
211
    }
212
213
    /**
214
     * Determines if a type is direct child of another type.
215
     *
216
     * @param   string  $child
217
     * @param   string  $parent
218
     * @return  bool
219
     */
220
    public function isChildOf($child, $parent)
221
    {
222
        $childMeta = $this->getMetadataForType($child);
223
        if (false === $childMeta->isChildEntity()) {
224
            return false;
225
        }
226
        return $childMeta->getParentEntityType() === $parent;
227
    }
228
229
    /**
230
     * Determines if a type is an ancestor of another type.
231
     *
232
     * @param   string  $parent
233
     * @param   string  $child
234
     * @return  bool
235
     */
236
    public function isAncestorOf($parent, $child)
237
    {
238
        return $this->isDescendantOf($child, $parent);
239
    }
240
241
    /**
242
     * Determines if a type is a descendant of another type.
243
     *
244
     * @param   string  $child
245
     * @param   string  $parent
246
     * @return  bool
247
     */
248
    public function isDescendantOf($child, $parent)
249
    {
250
        $childMeta = $this->getMetadataForType($child);
251
        if (false === $childMeta->isChildEntity()) {
252
            return false;
253
        }
254
        if ($childMeta->getParentEntityType() === $parent) {
255
            return true;
256
        }
257
        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...
258
    }
259
260
    public function validateResourceTypes($parentType, $childType)
261
    {
262
        $meta = $this->getMetadataForType($parentType);
263
        if (true === $meta->isPolymorphic()) {
264
            if (true === $meta->isAbstract() && false === $this->isDescendantOf($childType, $parentType)) {
265
                throw new InvalidArgumentException(sprintf('The resource type "%s" is polymorphic and abstract. Resource "%s" must be a descendent of "%s"', $parentType, $childType, $parentType));
266
            }
267
        }
268
269
        if (false === $meta->isPolymorphic() && $parentType !== $childType) {
270
            throw new InvalidArgumentException(sprintf('This resource only supports resources of type "%s" - resource type "%s" was provided', $parentType, $childType));
271
        }
272
        return true;
273
    }
274
275
    /**
276
     * Handles persistence specific metadata loading.
277
     *
278
     * @param   EntityMetadata  $metadata
279
     * @return  EntityMetadata
280
     */
281
    private function loadPersistenceMetadata(EntityMetadata $metadata)
282
    {
283
        $persisterKey = $metadata->persistence->getKey();
284
        $persistenceFactory = $this->driver->getPersistenceMetadataFactory($persisterKey);
285
286
        $persistenceFactory->handleLoad($metadata);
287
        $persistenceFactory->handleValidate($metadata);
288
        return $metadata;
289
    }
290
291
    /**
292
     * Handles search specific metadata loading.
293
     *
294
     * @param   EntityMetadata  $metadata
295
     * @return  EntityMetadata
296
     */
297
    private function loadSearchMetadata(EntityMetadata $metadata)
298
    {
299
        if (false === $metadata->isSearchEnabled()) {
300
            return $metadata;
301
        }
302
        $clientKey = $metadata->search->getKey();
303
        $searchFactory = $this->driver->getSearchMetadataFactory($clientKey);
304
305
        $searchFactory->handleLoad($metadata);
306
        $searchFactory->handleValidate($metadata);
307
        return $metadata;
308
    }
309
310
    /**
311
     * Merges two sets of EntityMetadata.
312
     * Is used for applying inheritance information.
313
     *
314
     * @param   EntityMetadata  &$metadata
315
     * @param   EntityMetadata  $toAdd
316
     */
317
    private function mergeMetadata(EntityMetadata &$metadata = null, EntityMetadata $toAdd)
318
    {
319
        if (null === $metadata) {
320
            $metadata = clone $toAdd;
321
        } else {
322
            $metadata->merge($toAdd);
323
        }
324
    }
325
326
    /**
327
     * Attempts to load a Metadata instance from a memory or cache source.
328
     *
329
     * @param   string  $type
330
     * @return  EntityMetadata|null
331
     */
332
    private function doLoadMetadata($type)
333
    {
334
        if (null !== $meta = $this->getFromMemory($type)) {
335
            // Found in memory.
336
            return $meta;
337
        }
338
339
        if (null !== $meta = $this->getFromCache($type)) {
340
            // Found in cache.
341
            $this->setToMemory($meta);
342
            return $meta;
343
        }
344
        return null;
345
    }
346
347
    /**
348
     * Puts the Metadata instance into a cache source (if set) and memory.
349
     *
350
     * @param   EntityMetadata  $metadata
351
     * @return  self
352
     */
353
    private function doPutMetadata(EntityMetadata $metadata)
354
    {
355
        if (true === $this->hasCache()) {
356
            $this->cache->putMetadataInCache($metadata);
357
        }
358
        $this->setToMemory($metadata);
359
        return $this;
360
    }
361
362
    /**
363
     * Clears any loaded metadata objects from memory.
364
     *
365
     * @return  self
366
     */
367
    public function clearMemory()
368
    {
369
        $this->loaded = [];
370
        return $this;
371
    }
372
373
    /**
374
     * Gets a Metadata instance for a type from memory.
375
     *
376
     * @return  EntityMetadata|null
377
     */
378
    private function getFromMemory($type)
379
    {
380
        if (isset($this->loaded[$type])) {
381
            return $this->loaded[$type];
382
        }
383
        return null;
384
    }
385
386
    /**
387
     * Sets a Metadata instance to the memory cache.
388
     *
389
     * @param   EntityMetadata  $metadata
390
     * @return  self
391
     */
392
    private function setToMemory(EntityMetadata $metadata)
393
    {
394
        $this->loaded[$metadata->type] = $metadata;
395
        return $this;
396
    }
397
398
    /**
399
     * Retrieves a Metadata instance for a type from cache.
400
     *
401
     * @return  EntityMetadata|null
402
     */
403
    private function getFromCache($type)
404
    {
405
        if (false === $this->hasCache()) {
406
            return null;
407
        }
408
        return $this->cache->loadMetadataFromCache($type);
409
    }
410
}
411