Completed
Pull Request — master (#68)
by Jacob
04:12 queued 01:12
created

Model::getStore()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace As3\Modlr\Models;
4
5
use As3\Modlr\Models\Relationships;
6
use As3\Modlr\Persister\Record;
7
use As3\Modlr\Store\Store;
8
use As3\Modlr\Metadata\EntityMetadata;
9
10
/**
11
 * Represents a data record from a persistence (database) layer.
12
 *
13
 * @author Jacob Bare <[email protected]>
14
 */
15
class Model
16
{
17
    /**
18
     * The id value of this model.
19
     * Always converted to a string when in the model context.
20
     *
21
     * @var string
22
     */
23
    protected $identifier;
24
25
    /**
26
     * The Model's attributes
27
     *
28
     * @var Attributes
29
     */
30
    protected $attributes;
31
32
    /**
33
     * The Model's has-one relationships
34
     *
35
     * @var Relationships\HasOne
36
     */
37
    protected $hasOneRelationships;
38
39
    /**
40
     * The Model's has-many relationships
41
     *
42
     * @var Relationships\HasMany
43
     */
44
    protected $hasManyRelationships;
45
46
    /**
47
     * Enables/disables collection auto-initialization on iteration.
48
     * Will not load/fill the collection from the database if false.
49
     * Is useful for large hasMany iterations where only id and type are required (ala serialization).
50
     *
51
     * @var bool
52
     */
53
    protected $collectionAutoInit = true;
54
55
    /**
56
     * The model state.
57
     *
58
     * @var State
59
     */
60
    protected $state;
61
62
    /**
63
     * The EntityMetadata that defines this Model.
64
     *
65
     * @var EntityMetadata
66
     */
67
    protected $metadata;
68
69
    /**
70
     * The Model Store for handling lifecycle operations.
71
     *
72
     * @var Store
73
     */
74
    protected $store;
75
76
    /**
77
     * Constructor.
78
     *
79
     * @param   EntityMetadata  $metadata   The internal entity metadata that supports this Model.
80
     * @param   string          $identifier The database identifier.
81
     * @param   Store           $store      The model store service for handling persistence operations.
82
     * @param   Record|null     $record     The model's attributes and relationships from the db layer to init the model with. New models will constructed with a null record.
83
     */
84
    public function __construct(EntityMetadata $metadata, $identifier, Store $store, Record $record = null)
85
    {
86
        $this->metadata = $metadata;
87
        $this->identifier = $identifier;
88
        $this->store = $store;
89
        $this->state = new State();
90
        $this->initialize($record);
91
    }
92
93
    /**
94
     * Gets the unique identifier of this model.
95
     *
96
     * @api
97
     * @return  string
98
     */
99
    public function getId()
100
    {
101
        return $this->identifier;
102
    }
103
104
    /**
105
     * Gets the model type.
106
     *
107
     * @api
108
     * @return  string
109
     */
110
    public function getType()
111
    {
112
        return $this->metadata->type;
113
    }
114
115
    /**
116
     * Gets the model store.
117
     *
118
     * @api
119
     * @return  Store
120
     */
121
    public function getStore()
122
    {
123
        return $this->store;
124
    }
125
126
    /**
127
     * Gets the composite key of the model by combining the model type with the unique id.
128
     *
129
     * @api
130
     * @return  string
131
     */
132
    public function getCompositeKey()
133
    {
134
        return sprintf('%s.%s', $this->getType(), $this->getId());
135
    }
136
137
    /**
138
     * Enables or disables has-many collection auto-initialization from the database.
139
     *
140
     * @param   bool    $bit    Whether to enable/disable.
141
     * @return  self
142
     */
143
    public function enableCollectionAutoInit($bit = true)
144
    {
145
        $this->collectionAutoInit = (Boolean) $bit;
146
        return $this;
147
    }
148
149
    /**
150
     * Gets a model property.
151
     * Will either be an attribute value, a has-one model, or an array representation of a has-many collection.
152
     * Returns null if the property does not exist on the model or is not set.
153
     * Is a proxy for @see getAttribute($key) and getRelationship($key)
154
     *
155
     * @api
156
     * @param   string  $key    The property field key.
157
     * @return  Model|Model[]|null|mixed
158
     */
159
    public function get($key)
160
    {
161
        if (true === $this->isAttribute($key)) {
162
            return $this->getAttribute($key);
163
        }
164
        return $this->getRelationship($key);
165
    }
166
167
    /**
168
     * Determines if a property key is an attribute.
169
     *
170
     * @api
171
     * @param   string  $key    The property key.
172
     * @return  bool
173
     */
174
    public function isAttribute($key)
175
    {
176
        return $this->getMetadata()->hasAttribute($key);
177
    }
178
179
    /**
180
     * Determines if an attribute key is calculated.
181
     *
182
     * @param   string  $key    The attribute key.
183
     * @return  bool
184
     */
185
    protected function isCalculatedAttribute($key)
186
    {
187
        if (false === $this->isAttribute($key)) {
188
            return false;
189
        }
190
        return $this->getMetadata()->getAttribute($key)->isCalculated();
191
    }
192
193
    /**
194
     * Gets an attribute value.
195
     *
196
     * @param   string  $key    The attribute key (field) name.
197
     * @return  mixed
198
     */
199
    protected function getAttribute($key)
200
    {
201
        if (true === $this->isCalculatedAttribute($key)) {
202
            return $this->getCalculatedAttribute($key);
203
        }
204
        $this->touch();
205
        return $this->attributes->get($key);
206
    }
207
208
    /**
209
     * Gets a calculated attribute value.
210
     *
211
     * @param   string  $key    The attribute key (field) name.
212
     * @return  mixed
213
     */
214
    protected function getCalculatedAttribute($key)
215
    {
216
        $attrMeta = $this->getMetadata()->getAttribute($key);
217
        $class  = $attrMeta->calculated['class'];
218
        $method = $attrMeta->calculated['method'];
219
220
        $value = $class::$method($this);
221
        return $this->convertAttributeValue($key, $value);
222
    }
223
224
    /**
225
     * Determines if a property key is a relationship (either has-one or has-many).
226
     *
227
     * @api
228
     * @param   string  $key    The property key.
229
     * @return  bool
230
     */
231
    public function isRelationship($key)
232
    {
233
        return $this->getMetadata()->hasRelationship($key);
234
    }
235
236
    /**
237
     * Determines if a property key is a an inverse relationship.
238
     *
239
     * @api
240
     * @param   string  $key    The property key.
241
     * @return  bool
242
     */
243
    public function isInverse($key)
244
    {
245
        if (false === $this->isRelationship($key)) {
246
            return false;
247
        }
248
        return $this->getMetadata()->getRelationship($key)->isInverse;
249
    }
250
251
    /**
252
     * Determines if a property key is a has-one relationship.
253
     *
254
     * @api
255
     * @param   string  $key    The property key.
256
     * @return  bool
257
     */
258
    public function isHasOne($key)
259
    {
260
        if (false === $this->isRelationship($key)) {
261
            return false;
262
        }
263
        return $this->getMetadata()->getRelationship($key)->isOne();
264
    }
265
266
    /**
267
     * Determines if a property key is a has-many relationship.
268
     *
269
     * @api
270
     * @param   string  $key    The property key.
271
     * @return  bool
272
     */
273
    public function isHasMany($key)
274
    {
275
        if (false === $this->isRelationship($key)) {
276
            return false;
277
        }
278
        return $this->getMetadata()->getRelationship($key)->isMany();
279
    }
280
281
    /**
282
     * Gets a relationship value.
283
     *
284
     * @param   string  $key    The relationship key (field) name.
285
     * @return  Model|array|null
286
     * @throws  \RuntimeException If hasMany relationships are accessed directly.
287
     */
288
    protected function getRelationship($key)
289
    {
290
        if (true === $this->isHasOne($key)) {
291
            $this->touch();
292
            return $this->hasOneRelationships->get($key);
293
        }
294
        if (true === $this->isHasMany($key)) {
295
            $this->touch();
296
            $collection = $this->hasManyRelationships->get($key);
297
            if ($collection->isLoaded($collection)) {
298
                return iterator_to_array($collection);
299
            }
300
            return (true === $this->collectionAutoInit) ? iterator_to_array($collection) : $collection->allWithoutLoad();
301
        }
302
        return null;
303
    }
304
305
    /**
306
     * Pushes a Model into a has-many relationship collection.
307
     * This method must be used for has-many relationships. Direct set is not supported.
308
     * To completely replace a has-many, call clear() first and then push() the new Models.
309
     *
310
     * @api
311
     * @param   string  $key
312
     * @param   Model   $model
313
     * @return  self
314
     */
315
    public function push($key, Model $model)
316
    {
317
        if (true === $this->isHasOne($key)) {
318
            return $this->setHasOne($key, $model);
319
        }
320
        if (false === $this->isHasMany($key)) {
321
            return $this;
322
        }
323
        if (true === $this->isInverse($key)) {
324
            throw ModelException::cannotModifyInverse($this, $key);
325
        }
326
        $this->touch();
327
        $collection = $this->hasManyRelationships->get($key);
328
        $collection->push($model);
329
        $this->doDirtyCheck();
330
        return $this;
331
    }
332
333
    /**
334
     * Clears a has-many relationship collection, sets an attribute to null, or sets a has-one relationship to null.
335
     *
336
     * @api
337
     * @param   string  $key    The property key.
338
     * @return  self
339
     */
340
    public function clear($key)
341
    {
342
        if (true === $this->isAttribute($key)) {
343
            return $this->setAttribute($key, null);
344
        }
345
        if (true === $this->isHasOne($key)) {
346
            return $this->setHasOne($key, null);
347
        }
348
        if (true === $this->isInverse($key)) {
349
            throw ModelException::cannotModifyInverse($this, $key);
350
        }
351
        if (true === $this->isHasMany($key)) {
352
            $collection = $this->hasManyRelationships->get($key);
353
            $collection->clear();
354
            $this->doDirtyCheck();
355
            return $this;
356
        }
357
        return $this;
358
    }
359
360
    /**
361
     * Removes a specific Model from a has-many relationship collection.
362
     *
363
     * @api
364
     * @param   string  $key    The has-many relationship key.
365
     * @param   Model   $model  The model to remove from the collection.
366
     * @return  self
367
     */
368 View Code Duplication
    public function remove($key, 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...
369
    {
370
        if (false === $this->isHasMany($key)) {
371
            return $this;
372
        }
373
        if (true === $this->isInverse($key)) {
374
            throw ModelException::cannotModifyInverse($this, $key);
375
        }
376
        $this->touch();
377
        $collection = $this->hasManyRelationships->get($key);
378
        $collection->remove($model);
379
        $this->doDirtyCheck();
380
        return $this;
381
    }
382
383
    /**
384
     * Sets a model property: an attribute value, a has-one model, or an entire has-many model collection.
385
     * Note: To push/remove a single Model into a has-many collection, or clear a collection, use @see push(), remove() and clear().
386
     * Is a proxy for @see setAttribute() and setRelationship()
387
     *
388
     * @api
389
     * @param   string  $key                The property field key.
390
     * @param   Model|Collection|null|mixed The value to set.
391
     * @return  self.
0 ignored issues
show
Documentation introduced by
The doc-type self. could not be parsed: Unknown type name "self." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
392
     */
393
    public function set($key, $value)
394
    {
395
        if (true === $this->isAttribute($key)) {
396
            return $this->setAttribute($key, $value);
397
        }
398
        return $this->setRelationship($key, $value);
399
    }
400
401
    /**
402
     * Sets an attribute value.
403
     * Will convert the value to the proper, internal PHP/Modlr data type.
404
     * Will do a dirty check immediately after setting.
405
     *
406
     * @param   string  $key    The attribute key (field) name.
407
     * @param   mixed   $value  The value to apply.
408
     * @return  self
409
     */
410
    protected function setAttribute($key, $value)
411
    {
412
        if (true === $this->isCalculatedAttribute($key)) {
413
            return $this;
414
        }
415
        $this->touch();
416
        $value = $this->convertAttributeValue($key, $value);
417
        $this->attributes->set($key, $value);
418
        $this->doDirtyCheck();
419
        return $this;
420
    }
421
422
    protected function convertAttributeValue($key, $value)
423
    {
424
        return $this->store->convertAttributeValue($this->getDataType($key), $value);
425
    }
426
427
    /**
428
     * Gets a data type from an attribute key.
429
     *
430
     * @param   string  $key The attribute key.
431
     * @return  string
432
     */
433
    protected function getDataType($key)
434
    {
435
        return $this->getMetadata()->getAttribute($key)->dataType;
436
    }
437
438
    /**
439
     * Sets a relationship value.
440
     *
441
     * @param   string      $key
442
     * @param   Model|null  $value
443
     * @return  self
444
     */
445
    protected function setRelationship($key, $value)
446
    {
447
        if (true === $this->isHasOne($key)) {
448
            return $this->setHasOne($key, $value);
449
        }
450
        if (true === $this->isHasMany($key)) {
451
            throw new \RuntimeException('You cannot set a hasMany relationship directly. Please access using push(), clear(), and/or remove()');
452
        }
453
        return $this;
454
    }
455
456
    /**
457
     * Sets a has-one relationship.
458
     *
459
     * @param   string      $key    The relationship key (field) name.
460
     * @param   Model|null  $model  The model to relate.
461
     * @return  self
462
     */
463 View Code Duplication
    protected function setHasOne($key, Model $model = null)
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...
464
    {
465
        if (true === $this->isInverse($key)) {
466
            throw ModelException::cannotModifyInverse($this, $key);
467
        }
468
        if (null !== $model) {
469
            $this->validateRelSet($key, $model->getType());
470
        }
471
        $this->touch();
472
        $this->hasOneRelationships->set($key, $model);
473
        $this->doDirtyCheck();
474
        return $this;
475
    }
476
477
    /**
478
     * Validates that the model type (from a Model or Collection instance) can be set to the relationship field.
479
     *
480
     * @param   string  $relKey The relationship field key.
481
     * @param   string  $type   The model type that is being related.
482
     * @return  self
483
     */
484
    protected function validateRelSet($relKey, $type)
485
    {
486
        $relMeta = $this->getMetadata()->getRelationship($relKey);
487
        $relatedModelMeta = $this->store->getMetadataForRelationship($relMeta);
0 ignored issues
show
Bug introduced by
It seems like $relMeta defined by $this->getMetadata()->getRelationship($relKey) on line 486 can be null; however, As3\Modlr\Store\Store::g...tadataForRelationship() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
488
        $this->store->validateRelationshipSet($relatedModelMeta, $type);
489
        return $this;
490
    }
491
492
    /**
493
     * Determines if the model uses a particlar mixin.
494
     *
495
     * @api
496
     * @param   string  $name
497
     * @return  bool
498
     */
499
    public function usesMixin($name)
500
    {
501
        return $this->metadata->hasMixin($name);
502
    }
503
504
    /**
505
     * Saves the model.
506
     *
507
     * @api
508
     * @param   Implement cascade relationship saves. Or should the store handle this?
509
     * @return  self
510
     */
511
    public function save()
512
    {
513
        if (true === $this->getState()->is('deleted')) {
514
            return $this;
515
        }
516
        $this->store->commit($this);
517
        return $this;
518
    }
519
520
    /**
521
     * Rolls back a model to its original, database values.
522
     *
523
     * @api
524
     * @return  self
525
     */
526
    public function rollback()
527
    {
528
        $this->attributes->rollback();
529
        $this->hasOneRelationships->rollback();
530
        $this->hasManyRelationships->rollback();
531
        $this->doDirtyCheck();
532
        return $this;
533
    }
534
535
    /**
536
     * Reloads the model from the database.
537
     *
538
     * @api
539
     * @return  self
540
     */
541
    public function reload()
542
    {
543
        return $this->touch(true);
544
    }
545
546
    /**
547
     * Restores an in-memory deleted object back to the database.
548
     *
549
     * @api
550
     * @todo    Implement if needed. Or should restore clear a pending delete?
551
     * @return  self
552
     */
553
    public function restore()
554
    {
555
        return $this;
556
    }
557
558
    /**
559
     * Marks the record for deletion.
560
     * Will not remove from the database until $this->save() is called.
561
     *
562
     * @api
563
     * @return  self
564
     * @throws  \RuntimeException   If a new (unsaved) model is deleted.
565
     */
566
    public function delete()
567
    {
568
        if (true === $this->getState()->is('new')) {
569
            throw new \RuntimeException('You cannot delete a new model');
570
        }
571
        if (true === $this->getState()->is('deleted')) {
572
            return $this;
573
        }
574
        $this->getState()->setDeleting();
575
        return $this;
576
    }
577
578
    /**
579
     * Touches the model.
580
     * If the model is currently empty, it will query the database and fill/load the model.
581
     *
582
     * @param   bool    $force  Whether to force the load, even if the model is currently loaded.
583
     * @return  self
584
     */
585
    protected function touch($force = false)
586
    {
587
        if (true === $this->getState()->is('deleted')) {
588
            return $this;
589
        }
590
        if (true === $this->getState()->is('empty') || true === $force) {
591
            $record = $this->store->retrieveRecord($this->getType(), $this->getId());
592
            $this->initialize($record);
593
            $this->state->setLoaded();
594
            // @todo Should this trigger a postReload event? Likely not.
595
        }
596
        return $this;
597
    }
598
599
    /**
600
     * Applies an array of raw model properties (attributes and relationships) to the model instance.
601
     *
602
     * @todo    Confirm that we want this method. It's currently used for creating and updating via the API adapter. Also see initialize()
603
     * @param   array   $properties     The properties to apply.
604
     * @return  self
605
     */
606
    public function apply(array $properties)
607
    {
608
        $properties = $this->applyDefaultAttrValues($properties);
609
        foreach ($properties as $key => $value) {
610
            if (true === $this->isAttribute($key)) {
611
                $this->set($key, $value);
612
                continue;
613
            }
614
            if (true === $this->isHasOne($key)) {
615
                if (empty($value)) {
616
                    $this->clear($key);
617
                    continue;
618
                }
619
                $value = $this->store->loadProxyModel($value['type'], $value['id']);
620
                $this->set($key, $value);
621
                continue;
622
            }
623
624
        }
625
626
        foreach ($this->getMetadata()->getRelationships() as $key => $relMeta) {
627
            if (true === $relMeta->isOne()) {
628
                continue;
629
            }
630
            // Array key exists must exist to determine if the
631
            if (!isset($properties[$key]) || true === $relMeta->isInverse) {
632
                continue;
633
            }
634
635
            $this->clear($key);
636
            $collection = $this->store->createCollection($relMeta, $properties[$key]);
637
            foreach ($collection->allWithoutLoad() as $value) {
638
                $this->push($key, $value);
639
            }
640
        }
641
        $this->doDirtyCheck();
642
        return $this;
643
    }
644
645
    /**
646
     * Initializes the model and loads its attributes and relationships.
647
     *
648
     * @todo    Made public so collections can initialize models. Not sure if we want this??
649
     * @param   Record|null   $record     The db attributes and relationships to apply.
650
     * @return  self
651
     */
652
    public function initialize(Record $record = null)
653
    {
654
        $hasOne = [];
655
        $hasMany = [];
656
        $attributes = [];
657
658
        if (null !== $record) {
659
            $attributes = $this->applyDefaultAttrValues($attributes);
660
            foreach ($record->getProperties() as $key => $value) {
661
                if (true === $this->isAttribute($key)) {
662
                    // Load attribute.
663
                    $attributes[$key] = $this->convertAttributeValue($key, $value);
664
                    continue;
665
                }
666
                if (true === $this->isHasOne($key)) {
667
                    // Load hasOne relationship.
668
                    $hasOne[$key] = $this->store->loadProxyModel($value['type'], $value['id']);
669
                    continue;
670
                }
671
            }
672
        }
673
674
        foreach ($this->getMetadata()->getRelationships() as $key => $relMeta) {
675
            if (true === $relMeta->isOne()) {
676
                continue;
677
            }
678
            if (true === $relMeta->isInverse) {
679
                $hasMany[$key] = $this->store->createInverseCollection($relMeta, $this);
680
            } else {
681
                $references = (null === $record || !isset($record->getProperties()[$key])) ? [] : $record->getProperties()[$key];
682
                $hasMany[$key] = $this->store->createCollection($relMeta, $references);
683
            }
684
        }
685
686
        $this->attributes           = (null === $this->attributes) ? new Attributes($attributes) : $this->attributes->replace($attributes);
687
        $this->hasOneRelationships  = (null === $this->hasOneRelationships) ? new Relationships\HasOne($hasOne) : $this->hasOneRelationships->replace($hasOne);
688
        $this->hasManyRelationships = (null === $this->hasManyRelationships) ? new Relationships\HasMany($hasMany) : $this->hasManyRelationships->replace($hasMany);
689
        $this->doDirtyCheck();
690
        return $this;
691
    }
692
693
    /**
694
     * Applies default attribute values from metadata, if set.
695
     *
696
     * @param   array   $attributes     The attributes to apply the defaults to.
697
     * @return  array
698
     */
699
    protected function applyDefaultAttrValues(array $attributes = [])
700
    {
701
        // Set defaults for each attribute.
702
        foreach ($this->getMetadata()->getAttributes() as $key => $attrMeta) {
703
            if (!isset($attrMeta->defaultValue) || isset($attributes[$key])) {
704
                continue;
705
            }
706
            $attributes[$key] = $this->convertAttributeValue($key, $attrMeta->defaultValue);
707
        }
708
709
        // Set defaults for the entire entity.
710
        foreach ($this->getMetadata()->defaultValues as $key => $value) {
711
            if (isset($attributes[$key])) {
712
                continue;
713
            }
714
            $attributes[$key] = $this->convertAttributeValue($key, $value);
715
        }
716
        return $attributes;
717
    }
718
719
    /**
720
     * Determines if the model is currently dirty.
721
     * Checks against the attribute and relationship dirty states.
722
     *
723
     * @api
724
     * @return  bool
725
     */
726
    public function isDirty()
727
    {
728
        return true === $this->attributes->areDirty()
729
            || true === $this->hasOneRelationships->areDirty()
730
            || true === $this->hasManyRelationships->areDirty()
731
        ;
732
    }
733
734
    /**
735
     * Does a dirty check and sets the state to this model.
736
     *
737
     * @return  self
738
     */
739
    protected function doDirtyCheck()
740
    {
741
        $this->state->setDirty($this->isDirty());
742
        return $this;
743
    }
744
745
    /**
746
     * Gets the current change set of attributes and relationships.
747
     *
748
     * @api
749
     * @return  array
750
     */
751
    public function getChangeSet()
752
    {
753
        $changeset = [
754
            'attributes'    => $this->attributes->calculateChangeSet(),
755
            'hasOne'        => $this->hasOneRelationships->calculateChangeSet(),
756
            'hasMany'       => $this->hasManyRelationships->calculateChangeSet(),
757
        ];
758
759
        foreach ($changeset as $type => $properties) {
760
            $changeset[$type] = $this->filterNotSavedProperties($type, $properties);
761
        }
762
        return $changeset;
763
    }
764
765
    /**
766
     * Gets the model state object.
767
     *
768
     * @todo    Should this be public? State setting should likely be locked from the outside world.
769
     * @return  State
770
     */
771
    public function getState()
772
    {
773
        return $this->state;
774
    }
775
776
    /**
777
     * Gets the metadata for this model.
778
     *
779
     * @return  EntityMetadata
780
     */
781
    public function getMetadata()
782
    {
783
        return $this->metadata;
784
    }
785
786
    /**
787
     * Removes properties marked as non-saved.
788
     *
789
     * @param   string  $propType
790
     * @param   array   $properties
791
     * @return  array
792
     */
793
    private function filterNotSavedProperties($propType, array $properties)
794
    {
795
        $method = ('attributes' === $propType) ? 'getAttributes' : 'getRelationships';
796
        foreach ($this->getMetadata()->$method() as $fieldKey => $propMeta) {
797
            if (true === $propMeta->shouldSave() || !isset($properties[$fieldKey])) {
798
                continue;
799
            }
800
            unset($properties[$fieldKey]);
801
        }
802
        return $properties;
803
    }
804
}
805