Completed
Pull Request — master (#79)
by Jacob
03:15
created

Store::loadEmbed()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 2
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\EmbeddedPropMetadata;
8
use As3\Modlr\Metadata\EmbedMetadata;
9
use As3\Modlr\Metadata\EntityMetadata;
10
use As3\Modlr\Metadata\MetadataFactory;
11
use As3\Modlr\Metadata\RelationshipMetadata;
12
use As3\Modlr\Models\Collections;
13
use As3\Modlr\Models\Embed;
14
use As3\Modlr\Models\Model;
15
use As3\Modlr\Persister\PersisterInterface;
16
use As3\Modlr\Persister\Record;
17
use As3\Modlr\StorageLayerManager;
18
use As3\Modlr\Store\Events\ModelLifecycleArguments;
19
20
/**
21
 * Manages models and their persistence.
22
 *
23
 * @author Jacob Bare <[email protected]>
24
 */
25
class Store
26
{
27
    /**
28
     * @var MetadataFactory
29
     */
30
    private $mf;
31
32
    /**
33
     * @var TypeFactory
34
     */
35
    private $typeFactory;
36
37
    /**
38
     * The storage layer  manager.
39
     * Retrieves the appropriate persister and search client for handling records.
40
     *
41
     * @var  StorageLayerManager
42
     */
43
    private $storageManager;
44
45
    /**
46
     * Contains all models currently loaded in memory.
47
     *
48
     * @var Cache
49
     */
50
    private $cache;
51
52
    /**
53
     * The event dispatcher for firing model lifecycle events.
54
     *
55
     * @var EventDispatcher
56
     */
57
    private $dispatcher;
58
59
    /**
60
     * Constructor.
61
     *
62
     * @param   MetadataFactory         $mf
63
     * @param   StorageLayerManager     $storageManager
64
     * @param   TypeFactory             $typeFactory
65
     * @param   EventDispatcher         $dispatcher
66
     */
67
    public function __construct(MetadataFactory $mf, StorageLayerManager $storageManager, TypeFactory $typeFactory, EventDispatcher $dispatcher)
68
    {
69
        $this->mf = $mf;
70
        $this->storageManager = $storageManager;
71
        $this->typeFactory = $typeFactory;
72
        $this->dispatcher = $dispatcher;
73
        $this->cache = new Cache();
74
    }
75
76
    /**
77
     * Finds a single record from the persistence layer, by type and id.
78
     * Will return a Model object if found, or throw an exception if not.
79
     *
80
     * @api
81
     * @param   string  $typeKey    The model type.
82
     * @param   string  $identifier The model id.
83
     * @return  Model
84
     */
85
    public function find($typeKey, $identifier)
86
    {
87
        if (true === $this->cache->has($typeKey, $identifier)) {
88
            return $this->cache->get($typeKey, $identifier);
89
        }
90
        $record = $this->retrieveRecord($typeKey, $identifier);
91
        return $this->loadModel($typeKey, $record);
92
    }
93
94
    /**
95
     * Returns the available type keys from the MetadataFactory
96
     *
97
     * @return  array
98
     */
99
    public function getModelTypes()
100
    {
101
        return $this->mf->getAllTypeNames();
102
    }
103
104
    /**
105
     * Finds all records (or filtered by specific identifiers) for a type.
106
     *
107
     * @todo    Add sorting and pagination (limit/skip).
108
     * @todo    Handle find all with identifiers.
109
     * @param   string  $typeKey        The model type.
110
     * @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...
111
     * @return  Collections\Collection
112
     */
113
    public function findAll($typeKey, array $identifiers = [])
114
    {
115
        $metadata = $this->getMetadataForType($typeKey);
116
        if (!empty($identifiers)) {
117
            throw StoreException::nyi('Finding multiple records with specified identifiers is not yet supported.');
118
        }
119
        $records = $this->retrieveRecords($typeKey, $identifiers);
120
        $models = $this->loadModels($typeKey, $records);
121
        return new Collections\Collection($metadata, $this, $models);
122
    }
123
124
    /**
125
     * Queries records based on a provided set of criteria.
126
     *
127
     * @param   string      $typeKey    The model type.
128
     * @param   array       $criteria   The query criteria.
129
     * @param   array       $fields     Fields to include/exclude.
130
     * @param   array       $sort       The sort criteria.
131
     * @param   int         $offset     The starting offset, aka the number of Models to skip.
132
     * @param   int         $limit      The number of Models to limit.
133
     * @return  Collections\Collection
134
     */
135
    public function findQuery($typeKey, array $criteria, array $fields = [], array $sort = [], $offset = 0, $limit = 0)
136
    {
137
        $metadata = $this->getMetadataForType($typeKey);
138
139
        $persister = $this->getPersisterFor($typeKey);
140
        $records = $persister->query($metadata, $this, $criteria, $fields, $sort, $offset, $limit);
141
142
        $models = $this->loadModels($typeKey, $records);
143
        return new Collections\Collection($metadata, $this, $models);
144
    }
145
146
    /**
147
     * Searches for records (via the search layer) for a specific type, attribute, and value.
148
     * Uses the autocomplete logic to fullfill the request.
149
     *
150
     * @todo    Determine if full models should be return, or only specific fields.
151
     *          Autocompleters needs to be fast. If only specific fields are returned, do we need to exclude nulls in serialization?
152
     * @todo    Is search enabled for all models, by default, where everything is stored?
153
     *
154
     * @param   string  $typeKey
155
     * @param   string  $attributeKey
156
     * @param   string  $searchValue
157
     * @return  Collections\Collection
158
     */
159
    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...
160
    {
161
        $metadata = $this->getMetadataForType($typeKey);
162
        if (false === $metadata->isSearchEnabled()) {
163
            throw StoreException::badRequest(sprintf('Search is not enabled for model type "%s"', $metadata->type));
164
        }
165
        return new Collections\Collection($metadata, $this, []);
166
    }
167
168
    /**
169
     * Creates a new record.
170
     * The model will not be comitted to the persistence layer until $model->save() is called.
171
     *
172
     * @api
173
     * @param   string      $typeKey    The model type.
174
     * @param   string|null $identifier The model identifier. Generally should be null unless client-side id generation is in place.
175
     * @return  Model
176
     */
177
    public function create($typeKey, $identifier = null)
178
    {
179
        if (empty($identifier)) {
180
            $identifier = $this->generateIdentifier($typeKey);
181
        }
182
        return $this->createModel($typeKey, $identifier);
183
    }
184
185
    /**
186
     * Deletes a model.
187
     * The moel will be immediately deleted once retrieved.
188
     *
189
     * @api
190
     * @param   string      $typeKey    The model type.
191
     * @param   string|null $identifier The model identifier.
192
     * @return  Model
193
     */
194
    public function delete($typeKey, $identifier)
195
    {
196
        $model = $this->find($typeKey, $identifier);
197
        return $model->delete()->save();
198
    }
199
200
    /**
201
     * Retrieves a Record object from the persistence layer.
202
     *
203
     * @param   string  $typeKey    The model type.
204
     * @param   string  $identifier The model identifier.
205
     * @return  Record
206
     * @throws  StoreException  If the record cannot be found.
207
     */
208
    public function retrieveRecord($typeKey, $identifier)
209
    {
210
        $persister = $this->getPersisterFor($typeKey);
211
        $record = $persister->retrieve($this->getMetadataForType($typeKey), $identifier, $this);
212
        if (null === $record) {
213
            throw StoreException::recordNotFound($typeKey, $identifier);
214
        }
215
        return $record;
216
    }
217
218
    /**
219
     * Retrieves multiple Record objects from the persistence layer.
220
     *
221
     * @todo    Implement sorting and pagination (limit/skip).
222
     * @param   string  $typeKey        The model type.
223
     * @param   array   $identifiers    The model identifier.
224
     * @return  Record[]
225
     */
226
    public function retrieveRecords($typeKey, array $identifiers)
227
    {
228
        $persister = $this->getPersisterFor($typeKey);
229
        return $persister->all($this->getMetadataForType($typeKey), $this, $identifiers);
230
    }
231
232
    /**
233
     * Retrieves multiple Record objects from the persistence layer for an inverse relationship.
234
     *
235
     * @todo    Need to find a way to query all inverse at the same time for a findAll query, as it's queried multiple times.
236
     * @param   string  $ownerTypeKey
237
     * @param   string  $relTypeKey
238
     * @param   array   $identifiers
239
     * @param   string  $inverseField
240
     * @return  Record[]
241
     */
242
    public function retrieveInverseRecords($ownerTypeKey, $relTypeKey, array $identifiers, $inverseField)
243
    {
244
        $persister = $this->getPersisterFor($relTypeKey);
245
        return $persister->inverse(
246
            $this->getMetadataForType($ownerTypeKey),
247
            $this->getMetadataForType($relTypeKey),
248
            $this,
249
            $identifiers,
250
            $inverseField
251
        );
252
    }
253
254
    /**
255
     * Loads/creates a model from a persistence layer Record.
256
     *
257
     * @param   string  $typeKey    The model type.
258
     * @param   Record  $record     The persistence layer record.
259
     * @return  Model
260
     */
261
    protected function loadModel($typeKey, Record $record)
262
    {
263
        $this->mf->validateResourceTypes($typeKey, $record->getType());
264
        // Must use the type from the record to cover polymorphic models.
265
        $metadata = $this->getMetadataForType($record->getType());
266
267
        $model = new Model($metadata, $record->getId(), $this, $record->getProperties());
268
        $model->getState()->setLoaded();
269
270
        $this->dispatchLifecycleEvent(Events::postLoad, $model);
271
272
        $this->cache->push($model);
273
        return $model;
274
    }
275
276
    /**
277
     * Loads/creates multiple models from persistence layer Records.
278
     *
279
     * @param   string      $typeKey    The model type.
280
     * @param   Record[]    $records    The persistence layer records.
281
     * @return  Model[]
282
     */
283
    protected function loadModels($typeKey, array $records)
284
    {
285
        $models = [];
286
        foreach ($records as $record) {
287
            $models[] = $this->loadModel($typeKey, $record);
288
        }
289
        return $models;
290
    }
291
292
    /**
293
     * Dispatches a model lifecycle event via the event dispatcher.
294
     *
295
     * @param   string  $eventName
296
     * @param   Model   $model
297
     * @return  self
298
     */
299
    protected function dispatchLifecycleEvent($eventName, Model $model)
300
    {
301
        $args = new ModelLifecycleArguments($model);
302
        $this->dispatcher->dispatch($eventName, $args);
303
        return $this;
304
    }
305
306
    /**
307
     * Creates a new Model instance.
308
     * Will not be persisted until $model->save() is called.
309
     *
310
     * @param   string  $typeKey    The model type.
311
     * @param   string  $identifier The model identifier.
312
     * @return  Model
313
     */
314
    protected function createModel($typeKey, $identifier)
315
    {
316
        if (true === $this->cache->has($typeKey, $identifier)) {
317
            throw new \RuntimeException(sprintf('A model is already loaded for type "%s" using identifier "%s"', $typeKey, $identifier));
318
        }
319
        $metadata = $this->getMetadataForType($typeKey);
320
        if (true === $metadata->isAbstract()) {
321
            throw StoreException::badRequest('Abstract models cannot be created directly. You must instantiate a child class');
322
        }
323
        $model = new Model($metadata, $identifier, $this);
324
        $model->getState()->setNew();
325
        $this->cache->push($model);
326
        return $model;
327
    }
328
329
    /**
330
     * Loads a has-one model proxy.
331
     *
332
     * @param   string  $relatedTypeKey
333
     * @param   string  $identifier
334
     * @return  Model
335
     */
336
    public function loadProxyModel($relatedTypeKey, $identifier)
337
    {
338
        $identifier = $this->convertId($identifier);
339
        if (true === $this->cache->has($relatedTypeKey, $identifier)) {
340
            return $this->cache->get($relatedTypeKey, $identifier);
341
        }
342
343
        $metadata = $this->getMetadataForType($relatedTypeKey);
344
        $model = new Model($metadata, $identifier, $this);
345
        $this->cache->push($model);
346
        return $model;
347
    }
348
349
    /**
350
     * Loads an Embed model
351
     *
352
     * @param   EmbedMetadata   $embedMeta
353
     * @param   array           $embed
354
     * @return  Embed
355
     */
356
    public function loadEmbed(EmbedMetadata $embedMeta, array $embed)
357
    {
358
        return new Embed($embedMeta, $this, $embed);
359
    }
360
361
    /**
362
     * Loads a has-many inverse model collection.
363
     *
364
     * @param   RelationshipMetadata    $relMeta
365
     * @param   Model                   $owner
366
     * @return  Collections\InverseCollection
367
     */
368
    public function createInverseCollection(RelationshipMetadata $relMeta, Model $owner)
369
    {
370
        $metadata = $this->getMetadataForType($relMeta->getEntityType());
371
        return new Collections\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...
372
    }
373
374
    /**
375
     * Loads a has-many embed collection.
376
     *
377
     * @param   EmbeddedPropMetadata    $relMeta
0 ignored issues
show
Bug introduced by
There is no parameter named $relMeta. 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...
378
     * @param   array|null              $embedDocs
379
     * @return  Collections\EmbedCollection
380
     */
381
    public function createEmbedCollection(EmbeddedPropMetadata $embededPropMeta, array $embedDocs = null)
382
    {
383
        if (empty($embedDocs)) {
384
            $embedDocs = [];
385
        }
386
        if (false === $this->isSequentialArray($embedDocs)) {
387
            throw StoreException::badRequest(sprintf('Improper has-many data detected for embed "%s" - a sequential array is required.', $embededPropMeta->getKey()));
388
        }
389
390
        $embeds = [];
391
        foreach ($embedDocs as $embedDoc) {
392
            $embeds[] = $this->loadEmbed($embededPropMeta->embedMeta, $embedDoc);
393
        }
394
395
        return new Collections\EmbedCollection($embededPropMeta->embedMeta, $this, $embeds);
396
    }
397
398
    /**
399
     * Loads a has-many model collection.
400
     *
401
     * @param   RelationshipMetadata    $relMeta
402
     * @param   array|null              $references
403
     * @return  Collections\Collection
404
     */
405
    public function createCollection(RelationshipMetadata $relMeta, array $references = null)
406
    {
407
        $metadata = $this->getMetadataForType($relMeta->getEntityType());
408
        if (empty($references)) {
409
            $references = [];
410
        }
411
        if (false === $this->isSequentialArray($references)) {
412
            throw StoreException::badRequest(sprintf('Improper has-many data detected for relationship "%s" - a sequential array is required.', $relMeta->getKey()));
413
        }
414
        $models = [];
415
        foreach ($references as $reference) {
416
            $models[] = $this->loadProxyModel($reference['type'], $reference['id']);
417
        }
418
        return new Collections\Collection($metadata, $this, $models);
419
    }
420
421
    /**
422
     * Loads/fills a collection of empty (unloaded) models with data from the persistence layer.
423
     *
424
     * @param   Collections\AbstractCollection  $collection
425
     * @return  Model[]
426
     */
427
    public function loadCollection(Collections\AbstractCollection $collection)
428
    {
429
        $identifiers = $collection->getIdentifiers();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class As3\Modlr\Models\Collections\AbstractCollection as the method getIdentifiers() does only exist in the following sub-classes of As3\Modlr\Models\Collections\AbstractCollection: As3\Modlr\Models\Collections\Collection, As3\Modlr\Models\Collections\InverseCollection, As3\Modlr\Models\Collections\ModelCollection. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
430
        if (empty($identifiers)) {
431
            // Nothing to query.
432
            return [];
433
        }
434
        if ($collection instanceof Collections\InverseCollection) {
435
            $records = $this->retrieveInverseRecords($collection->getOwner()->getType(), $collection->getType(), $collection->getIdentifiers(), $collection->getQueryField());
436
        } else {
437
            $records = $this->retrieveRecords($collection->getType(), $collection->getIdentifiers());
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class As3\Modlr\Models\Collections\AbstractCollection as the method getIdentifiers() does only exist in the following sub-classes of As3\Modlr\Models\Collections\AbstractCollection: As3\Modlr\Models\Collections\Collection, As3\Modlr\Models\Collections\InverseCollection, As3\Modlr\Models\Collections\ModelCollection. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
438
        }
439
440
        $models = [];
441
        foreach ($records as $record) {
442
            if (true === $this->cache->has($record->getType(), $record->getId())) {
443
                $models[] = $this->cache->get($record->getType(), $record->getId());
444
                continue;
445
            }
446
            $models[] = $this->loadModel($collection->getType(), $record);
447
        }
448
        return $models;
449
    }
450
451
    /**
452
     * Commits a model by persisting it to the database.
453
     *
454
     * @todo    Eventually we'll want to schedule models and allow for mutiple commits, flushes, etc.
455
     * @todo    Will need to handle cascade saving of new or modified relationships??
456
     * @param   Model   $model  The model to commit.
457
     * @return  Model
458
     */
459
    public function commit(Model $model)
460
    {
461
        $this->dispatchLifecycleEvent(Events::preCommit, $model);
462
463
        if (false === $this->shouldCommit($model)) {
464
            return $model;
465
        }
466
467
        if (true === $model->getState()->is('new')) {
468
            $this->doCommitCreate($model);
469
470
        } elseif (true === $model->getState()->is('deleting')) {
471
            // Deletes must execute before updates to prevent an update then a delete.
472
            $this->doCommitDelete($model);
473
474
        } elseif (true === $model->getState()->is('dirty')) {
475
            $this->doCommitUpdate($model);
476
477
        } else {
478
            throw new \RuntimeException('Unable to commit model.');
479
        }
480
481
        $this->dispatchLifecycleEvent(Events::postCommit, $model);
482
483
        return $model;
484
    }
485
486
    /**
487
     * Performs a Model creation commit and persists to the database.
488
     *
489
     * @param   Model   $model
490
     * @return  Model
491
     */
492 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...
493
    {
494
        $this->dispatchLifecycleEvent(Events::preCreate, $model);
495
496
        $this->getPersisterFor($model->getType())->create($model);
497
        $model->getState()->setNew(false);
498
        // Should the model always reload? Or should the commit be assumed correct and just clear the new/dirty state?
499
        $model->reload();
500
501
        $this->dispatchLifecycleEvent(Events::postCreate, $model);
502
        return $model;
503
    }
504
505
    /**
506
     * Performs a Model delete commit and persists to the database.
507
     *
508
     * @param   Model   $model
509
     * @return  Model
510
     */
511 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...
512
    {
513
        $this->dispatchLifecycleEvent(Events::preDelete, $model);
514
515
        $this->getPersisterFor($model->getType())->delete($model);
516
        $model->getState()->setDeleted();
517
518
        $this->dispatchLifecycleEvent(Events::postDelete, $model);
519
        return $model;
520
    }
521
522
    /**
523
     * Performs a Model update commit and persists to the database.
524
     *
525
     * @param   Model   $model
526
     * @return  Model
527
     */
528 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...
529
    {
530
        $this->dispatchLifecycleEvent(Events::preUpdate, $model);
531
532
        $this->getPersisterFor($model->getType())->update($model);
533
        // Should the model always reload? Or should the commit be assumed correct and just clear the new/dirty state?
534
        $model->reload();
535
536
        $this->dispatchLifecycleEvent(Events::postUpdate, $model);
537
        return $model;
538
    }
539
540
    /**
541
     * Validates that an embed name/type can be set to an owning embed metadata type.
542
     *
543
     * @param   EmbedMetadata   $owningMeta     The metadata the type will be added to.
544
     * @param   string          $nameToCheck    The name to check.
545
     * @return  self
546
     * @throws  StoreException  If the type to add is not supported.
547
     */
548
    public function validateEmbedSet(EmbedMetadata $owningMeta, $nameToCheck)
549
    {
550
        if ($owningMeta->name !== $nameToCheck) {
551
            throw StoreException::badRequest(sprintf('The embed type "%s" cannot be added to "%s", as it is not supported.', $nameToCheck, $owningMeta->name));
552
        }
553
        return $this;
554
    }
555
556
    /**
557
     * Validates that a model type can be set to an owning metadata type.
558
     *
559
     * @param   EntityMetadata  $owningMeta The metadata the type will be added to.
560
     * @param   string          $typeToAdd  The type to add.
561
     * @return  self
562
     * @throws  StoreException  If the type to add is not supported.
563
     */
564
    public function validateRelationshipSet(EntityMetadata $owningMeta, $typeToAdd)
565
    {
566
        if (true === $owningMeta->isPolymorphic()) {
567
            $canSet = in_array($typeToAdd, $owningMeta->ownedTypes);
568
        } else {
569
            $canSet = $owningMeta->type === $typeToAdd;
570
        }
571
        if (false === $canSet) {
572
            throw StoreException::badRequest(sprintf('The model type "%s" cannot be added to "%s", as it is not supported.', $typeToAdd, $owningMeta->type));
573
        }
574
        return $this;
575
    }
576
577
    /**
578
     * Converts an attribute value to the proper Modlr data type.
579
     *
580
     * @param   string  $dataType   The data type, such as string, integer, boolean, etc.
581
     * @param   mixed   $value      The value to convert.
582
     * @return  mixed
583
     */
584
    public function convertAttributeValue($dataType, $value)
585
    {
586
        return $this->typeFactory->convertToModlrValue($dataType, $value);
587
    }
588
589
    /**
590
     * Determines if a model is eligible for commit.
591
     *
592
     * @todo    Does delete need to be here?
593
     * @param   Model   $model
594
     * @return  bool
595
     */
596
    protected function shouldCommit(Model $model)
597
    {
598
        $state = $model->getState();
599
        return true === $state->is('dirty') || $state->is('new') || $state->is('deleting');
600
    }
601
602
    /**
603
     * Determines the persister to use for the provided model key.
604
     *
605
     * @param   string  $typeKey    The model type key.
606
     * @return  PersisterInterface
607
     */
608
    protected function getPersisterFor($typeKey)
609
    {
610
        $metadata = $this->getMetadataForType($typeKey);
611
        return $this->storageManager->getPersister($metadata->persistence->getKey());
612
    }
613
614
    /**
615
     * Generates a new identifier value for a model type.
616
     *
617
     * @param   string  $typeKey    The model type.
618
     * @return  string
619
     */
620
    protected function generateIdentifier($typeKey)
621
    {
622
        return $this->convertId($this->getPersisterFor($typeKey)->generateId());
623
    }
624
625
    /**
626
     * Converts the id value to a normalized string.
627
     *
628
     * @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...
629
     * @return  string
630
     */
631
    protected function convertId($identifier)
632
    {
633
        return (String) $identifier;
634
    }
635
636
    /**
637
     * Gets the metadata for a model type.
638
     *
639
     * @param   string  $typeKey    The model type.
640
     * @return  EntityMetadata
641
     */
642
    public function getMetadataForType($typeKey)
643
    {
644
        return $this->mf->getMetadataForType($typeKey);
645
    }
646
647
    /**
648
     * Gets the metadata for a relationship.
649
     *
650
     * @param   RelationshipMetadata    $relMeta    The relationship metadata.
651
     * @return  EntityMetadata
652
     */
653
    public function getMetadataForRelationship(RelationshipMetadata $relMeta)
654
    {
655
        return $this->getMetadataForType($relMeta->getEntityType());
656
    }
657
658
    /**
659
     * Determines if an array is sequential.
660
     *
661
     * @param   array   $arr
662
     * @return  bool
663
     */
664
    protected function isSequentialArray(array $arr)
665
    {
666
        if (empty($arr)) {
667
            return true;
668
        }
669
        return (range(0, count($arr) - 1) === array_keys($arr));
670
    }
671
}
672