Completed
Push — master ( 0d5de4...97668b )
by Jacob
6s
created

Store::findQuery()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 10
rs 9.4285
cc 1
eloc 6
nc 1
nop 6
1
<?php
2
3
namespace As3\Modlr\Store;
4
5
use As3\Modlr\DataTypes\TypeFactory;
6
use As3\Modlr\Events\EventDispatcher;
7
use As3\Modlr\Metadata\EntityMetadata;
8
use As3\Modlr\Metadata\MetadataFactory;
9
use As3\Modlr\Metadata\RelationshipMetadata;
10
use As3\Modlr\Models\AbstractCollection;
11
use As3\Modlr\Models\Collection;
12
use As3\Modlr\Models\InverseCollection;
13
use As3\Modlr\Models\Model;
14
use As3\Modlr\Persister\PersisterInterface;
15
use As3\Modlr\Persister\Record;
16
use As3\Modlr\StorageLayerManager;
17
use As3\Modlr\Store\Events\ModelLifecycleArguments;
18
19
/**
20
 * Manages models and their persistence.
21
 *
22
 * @author Jacob Bare <[email protected]>
23
 */
24
class Store
25
{
26
    /**
27
     * @var MetadataFactory
28
     */
29
    private $mf;
30
31
    /**
32
     * @var TypeFactory
33
     */
34
    private $typeFactory;
35
36
    /**
37
     * The storage layer  manager.
38
     * Retrieves the appropriate persister and search client for handling records.
39
     *
40
     * @var  StorageLayerManager
41
     */
42
    private $storageManager;
43
44
    /**
45
     * Contains all models currently loaded in memory.
46
     *
47
     * @var Cache
48
     */
49
    private $cache;
50
51
    /**
52
     * The event dispatcher for firing model lifecycle events.
53
     *
54
     * @var EventDispatcher
55
     */
56
    private $dispatcher;
57
58
    /**
59
     * Constructor.
60
     *
61
     * @param   MetadataFactory         $mf
62
     * @param   StorageLayerManager     $storageManager
63
     * @param   TypeFactory             $typeFactory
64
     * @param   EventDispatcher         $dispatcher
65
     */
66
    public function __construct(MetadataFactory $mf, StorageLayerManager $storageManager, TypeFactory $typeFactory, EventDispatcher $dispatcher)
67
    {
68
        $this->mf = $mf;
69
        $this->storageManager = $storageManager;
70
        $this->typeFactory = $typeFactory;
71
        $this->dispatcher = $dispatcher;
72
        $this->cache = new Cache();
73
    }
74
75
    /**
76
     * Finds a single record from the persistence layer, by type and id.
77
     * Will return a Model object if found, or throw an exception if not.
78
     *
79
     * @api
80
     * @param   string  $typeKey    The model type.
81
     * @param   string  $identifier The model id.
82
     * @return  Model
83
     */
84
    public function find($typeKey, $identifier)
85
    {
86
        if (true === $this->cache->has($typeKey, $identifier)) {
87
            return $this->cache->get($typeKey, $identifier);
88
        }
89
        $record = $this->retrieveRecord($typeKey, $identifier);
90
        return $this->loadModel($typeKey, $record);
91
    }
92
93
    /**
94
     * Finds all records (or filtered by specific identifiers) for a type.
95
     *
96
     * @todo    Add sorting and pagination (limit/skip).
97
     * @todo    Handle find all with identifiers.
98
     * @param   string  $typeKey        The model type.
99
     * @param   array   $idenitifiers   The model identifiers (optional).
0 ignored issues
show
Documentation introduced by
There is no parameter named $idenitifiers. Did you maybe mean $identifiers?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
100
     * @return  Collection
101
     */
102
    public function findAll($typeKey, array $identifiers = [])
103
    {
104
        $metadata = $this->getMetadataForType($typeKey);
105
        if (!empty($identifiers)) {
106
            throw StoreException::nyi('Finding multiple records with specified identifiers is not yet supported.');
107
        }
108
        $records = $this->retrieveRecords($typeKey, $identifiers);
109
        $models = $this->loadModels($typeKey, $records);
110
        return new Collection($metadata, $this, $models);
0 ignored issues
show
Documentation introduced by
$models is of type array<integer,object<As3\Modlr\Models\Model>>, but the function expects a array<integer,object<As3\Modlr\Models\Models>>.

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...
111
    }
112
113
    /**
114
     * Queries records based on a provided set of criteria.
115
     *
116
     * @param   string      $typeKey    The model type.
117
     * @param   array       $criteria   The query criteria.
118
     * @param   array       $fields     Fields to include/exclude.
119
     * @param   array       $sort       The sort criteria.
120
     * @param   int         $offset     The starting offset, aka the number of Models to skip.
121
     * @param   int         $limit      The number of Models to limit.
122
     * @return  Collection
123
     */
124
    public function findQuery($typeKey, array $criteria, array $fields = [], array $sort = [], $offset = 0, $limit = 0)
125
    {
126
        $metadata = $this->getMetadataForType($typeKey);
127
128
        $persister = $this->getPersisterFor($typeKey);
129
        $records = $persister->query($metadata, $this, $criteria, $fields, $sort, $offset, $limit);
130
131
        $models = $this->loadModels($typeKey, $records);
132
        return new Collection($metadata, $this, $models);
0 ignored issues
show
Documentation introduced by
$models is of type array<integer,object<As3\Modlr\Models\Model>>, but the function expects a array<integer,object<As3\Modlr\Models\Models>>.

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...
133
    }
134
135
    /**
136
     * Searches for records (via the search layer) for a specific type, attribute, and value.
137
     * Uses the autocomplete logic to fullfill the request.
138
     *
139
     * @todo    Determine if full models should be return, or only specific fields.
140
     *          Autocompleters needs to be fast. If only specific fields are returned, do we need to exclude nulls in serialization?
141
     * @todo    Is search enabled for all models, by default, where everything is stored?
142
     *
143
     * @param   string  $typeKey
144
     * @param   string  $attributeKey
145
     * @param   string  $searchValue
146
     * @return  Collection
147
     */
148
    public function searchAutocomplete($typeKey, $attributeKey, $searchValue)
0 ignored issues
show
Unused Code introduced by
The parameter $attributeKey is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $searchValue is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
149
    {
150
        $metadata = $this->getMetadataForType($typeKey);
151
        if (false === $metadata->isSearchEnabled()) {
152
            throw StoreException::badRequest(sprintf('Search is not enabled for model type "%s"', $metadata->type));
153
        }
154
        return new Collection($metadata, $this, []);
155
    }
156
157
    /**
158
     * Creates a new record.
159
     * The model will not be comitted to the persistence layer until $model->save() is called.
160
     *
161
     * @api
162
     * @param   string      $typeKey    The model type.
163
     * @param   string|null $identifier The model identifier. Generally should be null unless client-side id generation is in place.
164
     * @return  Model
165
     */
166
    public function create($typeKey, $identifier = null)
167
    {
168
        if (empty($identifier)) {
169
            $identifier = $this->generateIdentifier($typeKey);
170
        }
171
        return $this->createModel($typeKey, $identifier);
172
    }
173
174
    /**
175
     * Deletes a model.
176
     * The moel will be immediately deleted once retrieved.
177
     *
178
     * @api
179
     * @param   string      $typeKey    The model type.
180
     * @param   string|null $identifier The model identifier.
181
     * @return  Model
182
     */
183
    public function delete($typeKey, $identifier)
184
    {
185
        $model = $this->find($typeKey, $identifier);
186
        return $model->delete()->save();
187
    }
188
189
    /**
190
     * Retrieves a Record object from the persistence layer.
191
     *
192
     * @param   string  $typeKey    The model type.
193
     * @param   string  $identifier The model identifier.
194
     * @return  Record
195
     * @throws  StoreException  If the record cannot be found.
196
     */
197
    public function retrieveRecord($typeKey, $identifier)
198
    {
199
        $persister = $this->getPersisterFor($typeKey);
200
        $record = $persister->retrieve($this->getMetadataForType($typeKey), $identifier, $this);
201
        if (null === $record) {
202
            throw StoreException::recordNotFound($typeKey, $identifier);
203
        }
204
        return $record;
205
    }
206
207
    /**
208
     * Retrieves multiple Record objects from the persistence layer.
209
     *
210
     * @todo    Implement sorting and pagination (limit/skip).
211
     * @param   string  $typeKey        The model type.
212
     * @param   array   $identifiers    The model identifier.
213
     * @return  Record[]
214
     */
215
    public function retrieveRecords($typeKey, array $identifiers)
216
    {
217
        $persister = $this->getPersisterFor($typeKey);
218
        return $persister->all($this->getMetadataForType($typeKey), $this, $identifiers);
219
    }
220
221
    /**
222
     * Retrieves multiple Record objects from the persistence layer for an inverse relationship.
223
     *
224
     * @todo    Need to find a way to query all inverse at the same time for a findAll query, as it's queried multiple times.
225
     * @param   string  $ownerTypeKey
226
     * @param   string  $relTypeKey
227
     * @param   array   $identifiers
228
     * @param   string  $inverseField
229
     * @return  Record[]
230
     */
231
    public function retrieveInverseRecords($ownerTypeKey, $relTypeKey, array $identifiers, $inverseField)
232
    {
233
        $persister = $this->getPersisterFor($relTypeKey);
234
        return $persister->inverse(
235
            $this->getMetadataForType($ownerTypeKey),
236
            $this->getMetadataForType($relTypeKey),
237
            $this,
238
            $identifiers,
239
            $inverseField
240
        );
241
    }
242
243
    /**
244
     * Loads/creates a model from a persistence layer Record.
245
     *
246
     * @param   string  $typeKey    The model type.
247
     * @param   Record  $record     The persistence layer record.
248
     * @return  Model
249
     */
250
    protected function loadModel($typeKey, Record $record)
251
    {
252
        $this->mf->validateResourceTypes($typeKey, $record->getType());
253
        // Must use the type from the record to cover polymorphic models.
254
        $metadata = $this->getMetadataForType($record->getType());
255
256
        $model = new Model($metadata, $record->getId(), $this, $record);
257
        $model->getState()->setLoaded();
258
259
        $this->dispatchLifecycleEvent(Events::postLoad, $model);
260
261
        $this->cache->push($model);
262
        return $model;
263
    }
264
265
    /**
266
     * Loads/creates multiple models from persistence layer Records.
267
     *
268
     * @param   string      $typeKey    The model type.
269
     * @param   Record[]    $record     The persistence layer records.
0 ignored issues
show
Documentation introduced by
There is no parameter named $record. Did you maybe mean $records?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
270
     * @return  Model[]
271
     */
272
    protected function loadModels($typeKey, array $records)
273
    {
274
        $models = [];
275
        foreach ($records as $record) {
276
            $models[] = $this->loadModel($typeKey, $record);
277
        }
278
        return $models;
279
    }
280
281
    /**
282
     * Dispatches a model lifecycle event via the event dispatcher.
283
     *
284
     * @param   string  $eventName
285
     * @param   Model   $model
286
     * @return  self
287
     */
288
    protected function dispatchLifecycleEvent($eventName, Model $model)
289
    {
290
        $args = new ModelLifecycleArguments($model);
291
        $this->dispatcher->dispatch($eventName, $args);
292
        return $this;
293
    }
294
295
    /**
296
     * Creates a new Model instance.
297
     * Will not be persisted until $model->save() is called.
298
     *
299
     * @param   string  $typeKey    The model type.
300
     * @param   string  $identifier The model identifier.
301
     * @return  Model
302
     */
303
    protected function createModel($typeKey, $identifier)
304
    {
305
        if (true === $this->cache->has($typeKey, $identifier)) {
306
            throw new \RuntimeException(sprintf('A model is already loaded for type "%s" using identifier "%s"', $typeKey, $identifier));
307
        }
308
        $metadata = $this->getMetadataForType($typeKey);
309
        if (true === $metadata->isAbstract()) {
310
            throw StoreException::badRequest('Abstract models cannot be created directly. You must instantiate a child class');
311
        }
312
        $model = new Model($metadata, $identifier, $this);
313
        $model->getState()->setNew();
314
        $this->cache->push($model);
315
        return $model;
316
    }
317
318
    /**
319
     * Loads a has-one model proxy.
320
     *
321
     * @param   string  $relatedTypeKey
322
     * @param   string  $identifier
323
     * @return  Model
324
     */
325
    public function loadProxyModel($relatedTypeKey, $identifier)
326
    {
327
        $identifier = $this->convertId($identifier);
328
        if (true === $this->cache->has($relatedTypeKey, $identifier)) {
329
            return $this->cache->get($relatedTypeKey, $identifier);
330
        }
331
332
        $metadata = $this->getMetadataForType($relatedTypeKey);
333
        $model = new Model($metadata, $identifier, $this);
334
        $this->cache->push($model);
335
        return $model;
336
    }
337
338
    /**
339
     * Loads a has-many inverse model collection.
340
     *
341
     * @param   RelationshipMetadata    $relMeta
342
     * @param   Model                   $owner
343
     * @return  InverseCollection
344
     */
345
    public function createInverseCollection(RelationshipMetadata $relMeta, Model $owner)
346
    {
347
        $metadata = $this->getMetadataForType($relMeta->getEntityType());
348
        return new InverseCollection($metadata, $this, $owner, $relMeta->inverseField);
0 ignored issues
show
Documentation introduced by
$relMeta->inverseField 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...
349
    }
350
351
    /**
352
     * Loads a has-many model collection.
353
     *
354
     * @param   RelationshipMetadata    $relMeta
355
     * @param   array|null              $references
356
     * @return  Collection
357
     */
358
    public function createCollection(RelationshipMetadata $relMeta, array $references = null)
359
    {
360
        $metadata = $this->getMetadataForType($relMeta->getEntityType());
361
        if (empty($references)) {
362
            $references = [];
363
        }
364
        if (false === $this->isSequentialArray($references)) {
365
            throw StoreException::badRequest(sprintf('Improper has-many data detected for relationship "%s" - a sequential array is required.', $relatedTypeKey));
366
        }
367
        $models = [];
368
        foreach ($references as $reference) {
369
            $models[] = $this->loadProxyModel($reference['type'], $reference['id']);
370
        }
371
        return new Collection($metadata, $this, $models);
372
    }
373
374
    /**
375
     * Loads/fills a collection of empty (unloaded) models with data from the persistence layer.
376
     *
377
     * @param   AbstractCollection  $collection
378
     * @return  Model[]
379
     */
380
    public function loadCollection(AbstractCollection $collection)
381
    {
382
        $identifiers = $collection->getIdentifiers();
383
        if (empty($identifiers)) {
384
            // Nothing to query.
385
            return $collection;
386
        }
387
        if ($collection instanceof InverseCollection) {
388
            $records = $this->retrieveInverseRecords($collection->getOwner()->getType(), $collection->getType(), $collection->getIdentifiers(), $collection->getQueryField());
389
        } else {
390
            $records = $this->retrieveRecords($collection->getType(), $collection->getIdentifiers());
391
        }
392
393
        $models = [];
394
        foreach ($records as $record) {
395
            if (true === $this->cache->has($record->getType(), $record->getId())) {
396
                $models[] = $this->cache->get($record->getType(), $record->getId());
397
                continue;
398
            }
399
            $models[] = $this->loadModel($collection->getType(), $record);
400
        }
401
        return $models;
402
    }
403
404
    /**
405
     * Commits a model by persisting it to the database.
406
     *
407
     * @todo    Eventually we'll want to schedule models and allow for mutiple commits, flushes, etc.
408
     * @todo    Will need to handle cascade saving of new or modified relationships??
409
     * @param   Model   $model  The model to commit.
410
     * @return  Model
411
     */
412
    public function commit(Model $model)
413
    {
414
        $this->dispatchLifecycleEvent(Events::preCommit, $model);
415
416
        if (false === $this->shouldCommit($model)) {
417
            return $model;
418
        }
419
420
        if (true === $model->getState()->is('new')) {
421
            $this->doCommitCreate($model);
422
423
        } elseif (true === $model->getState()->is('deleting')) {
424
            // Deletes must execute before updates to prevent an update then a delete.
425
            $this->doCommitDelete($model);
426
427
        } elseif (true === $model->getState()->is('dirty')) {
428
            $this->doCommitUpdate($model);
429
430
        } else {
431
            throw new \RuntimeException('Unable to commit model.');
432
        }
433
434
        $this->dispatchLifecycleEvent(Events::postCommit, $model);
435
436
        return $model;
437
    }
438
439
    /**
440
     * Performs a Model creation commit and persists to the database.
441
     *
442
     * @param   Model   $model
443
     * @return  Model
444
     */
445 View Code Duplication
    private function doCommitCreate(Model $model)
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...
446
    {
447
        $this->dispatchLifecycleEvent(Events::preCreate, $model);
448
449
        $this->getPersisterFor($model->getType())->create($model);
450
        $model->getState()->setNew(false);
451
        // Should the model always reload? Or should the commit be assumed correct and just clear the new/dirty state?
452
        $model->reload();
453
454
        $this->dispatchLifecycleEvent(Events::postCreate, $model);
455
        return $model;
456
    }
457
458
    /**
459
     * Performs a Model delete commit and persists to the database.
460
     *
461
     * @param   Model   $model
462
     * @return  Model
463
     */
464 View Code Duplication
    private function doCommitDelete(Model $model)
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...
465
    {
466
        $this->dispatchLifecycleEvent(Events::preDelete, $model);
467
468
        $this->getPersisterFor($model->getType())->delete($model);
469
        $model->getState()->setDeleted();
470
471
        $this->dispatchLifecycleEvent(Events::postDelete, $model);
472
        return $model;
473
    }
474
475
    /**
476
     * Performs a Model update commit and persists to the database.
477
     *
478
     * @param   Model   $model
479
     * @return  Model
480
     */
481 View Code Duplication
    private function doCommitUpdate(Model $model)
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...
482
    {
483
        $this->dispatchLifecycleEvent(Events::preUpdate, $model);
484
485
        $this->getPersisterFor($model->getType())->update($model);
486
        // Should the model always reload? Or should the commit be assumed correct and just clear the new/dirty state?
487
        $model->reload();
488
489
        $this->dispatchLifecycleEvent(Events::postUpdate, $model);
490
        return $model;
491
    }
492
493
    /**
494
     * Validates that a model type can be set to an owning metadata type.
495
     *
496
     * @param   EntityMetadata  $owningMeta The metadata the type will be added to.
497
     * @param   string          $typeToAdd  The type to add.
498
     * @return  self
499
     * @throws  StoreException  If the type to add is not supported.
500
     */
501
    public function validateRelationshipSet(EntityMetadata $owningMeta, $typeToAdd)
502
    {
503
        if (true === $owningMeta->isPolymorphic()) {
504
            $canSet = in_array($typeToAdd, $owningMeta->ownedTypes);
505
        } else {
506
            $canSet = $owningMeta->type === $typeToAdd;
507
        }
508
        if (false === $canSet) {
509
            throw StoreException::badRequest(sprintf('The model type "%s" cannot be added to "%s", as it is not supported.', $typeToAdd, $owningMeta->type));
510
        }
511
        return $this;
512
    }
513
514
    /**
515
     * Converts an attribute value to the proper Modlr data type.
516
     *
517
     * @param   string  $dataType   The data type, such as string, integer, boolean, etc.
518
     * @param   mixed   $value      The value to convert.
519
     * @return  mixed
520
     */
521
    public function convertAttributeValue($dataType, $value)
522
    {
523
        return $this->typeFactory->convertToModlrValue($dataType, $value);
524
    }
525
526
    /**
527
     * Determines if a model is eligible for commit.
528
     *
529
     * @todo    Does delete need to be here?
530
     * @param   Model   $model
531
     * @return  bool
532
     */
533
    protected function shouldCommit(Model $model)
534
    {
535
        $state = $model->getState();
536
        return true === $state->is('dirty') || $state->is('new') || $state->is('deleting');
537
    }
538
539
    /**
540
     * Determines the persister to use for the provided model key.
541
     *
542
     * @param   string  $typeKey    The model type key.
543
     * @return  PersisterInterface
544
     */
545
    protected function getPersisterFor($typeKey)
546
    {
547
        $metadata = $this->getMetadataForType($typeKey);
548
        return $this->storageManager->getPersister($metadata->persistence->getKey());
549
    }
550
551
    /**
552
     * Generates a new identifier value for a model type.
553
     *
554
     * @param   string  $typeKey    The model type.
555
     * @return  string
556
     */
557
    protected function generateIdentifier($typeKey)
558
    {
559
        return $this->convertId($this->getPersisterFor($typeKey)->generateId());
560
    }
561
562
    /**
563
     * Converts the id value to a normalized string.
564
     *
565
     * @param   mixed   $identenfier    The identifier to convert.
0 ignored issues
show
Bug introduced by
There is no parameter named $identenfier. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
566
     * @return  string
567
     */
568
    protected function convertId($identifier)
569
    {
570
        return (String) $identifier;
571
    }
572
573
    /**
574
     * Gets the metadata for a model type.
575
     *
576
     * @param   string  $typeKey    The model type.
577
     * @return  EntityMetadata
578
     */
579
    public function getMetadataForType($typeKey)
580
    {
581
        return $this->mf->getMetadataForType($typeKey);
582
    }
583
584
    /**
585
     * Gets the metadata for a relationship.
586
     *
587
     * @param   RelationshipMetadata    $relMeta    The relationship metadata.
588
     * @return  EntityMetadata
589
     */
590
    public function getMetadataForRelationship(RelationshipMetadata $relMeta)
591
    {
592
        return $this->getMetadataForType($relMeta->getEntityType());
593
    }
594
595
    /**
596
     * Determines if an array is sequential.
597
     *
598
     * @param   array   $arr
599
     * @return  bool
600
     */
601
    protected function isSequentialArray(array $arr)
602
    {
603
        if (empty($arr)) {
604
            return true;
605
        }
606
        return (range(0, count($arr) - 1) === array_keys($arr));
607
    }
608
}
609