Completed
Push — master ( fd0b68...70c5e9 )
by Jared
01:51
created

Model::cast()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 10
nc 4
nop 2
1
<?php
2
3
/**
4
 * @author Jared King <[email protected]>
5
 *
6
 * @see http://jaredtking.com
7
 *
8
 * @copyright 2015 Jared King
9
 * @license MIT
10
 */
11
12
namespace Pulsar;
13
14
use BadMethodCallException;
15
use ICanBoogie\Inflector;
16
use Pimple\Container;
17
use Pulsar\Driver\DriverInterface;
18
use Pulsar\Exception\DriverMissingException;
19
use Pulsar\Exception\MassAssignmentException;
20
use Pulsar\Exception\ModelException;
21
use Pulsar\Exception\ModelNotFoundException;
22
use Pulsar\Relation\BelongsTo;
23
use Pulsar\Relation\BelongsToMany;
24
use Pulsar\Relation\HasMany;
25
use Pulsar\Relation\HasOne;
26
use Symfony\Component\EventDispatcher\EventDispatcher;
27
28
/**
29
 * Class Model.
30
 */
31
abstract class Model implements \ArrayAccess
32
{
33
    const IMMUTABLE = 0;
34
    const MUTABLE_CREATE_ONLY = 1;
35
    const MUTABLE = 2;
36
37
    const TYPE_STRING = 'string';
38
    const TYPE_NUMBER = 'number'; // DEPRECATED
39
    const TYPE_INTEGER = 'integer';
40
    const TYPE_FLOAT = 'float';
41
    const TYPE_BOOLEAN = 'boolean';
42
    const TYPE_DATE = 'date';
43
    const TYPE_OBJECT = 'object';
44
    const TYPE_ARRAY = 'array';
45
46
    const ERROR_REQUIRED_FIELD_MISSING = 'required_field_missing';
47
    const ERROR_VALIDATION_FAILED = 'validation_failed';
48
    const ERROR_NOT_UNIQUE = 'not_unique';
49
50
    const DEFAULT_ID_PROPERTY = 'id';
51
52
    /////////////////////////////
53
    // Model visible variables
54
    /////////////////////////////
55
56
    /**
57
     * List of model ID property names.
58
     *
59
     * @var array
60
     */
61
    protected static $ids = [self::DEFAULT_ID_PROPERTY];
62
63
    /**
64
     * Property definitions expressed as a key-value map with
65
     * property names as the keys.
66
     * i.e. ['enabled' => ['type' => Model::TYPE_BOOLEAN]].
67
     *
68
     * @var array
69
     */
70
    protected static $properties = [];
71
72
    /**
73
     * @var array
74
     */
75
    protected static $dispatchers;
76
77
    /**
78
     * @var Container
79
     */
80
    protected static $globalContainer;
81
82
    /**
83
     * @var ErrorStack
84
     */
85
    protected static $globalErrorStack;
86
87
    /**
88
     * @var number|string|false
89
     */
90
    protected $_id;
91
92
    /**
93
     * @var array
94
     */
95
    protected $_ids;
96
97
    /**
98
     * @var array
99
     */
100
    protected $_values = [];
101
102
    /**
103
     * @var array
104
     */
105
    protected $_unsaved = [];
106
107
    /**
108
     * @var bool
109
     */
110
    protected $_persisted = false;
111
112
    /**
113
     * @var array
114
     */
115
    protected $_relationships = [];
116
117
    /**
118
     * @var ErrorStack
119
     */
120
    protected $_errors;
121
122
    /////////////////////////////
123
    // Base model variables
124
    /////////////////////////////
125
126
    /**
127
     * @var array
128
     */
129
    private static $propertyDefinitionBase = [
130
        'type' => self::TYPE_STRING,
131
        'mutable' => self::MUTABLE,
132
        'null' => false,
133
        'unique' => false,
134
        'required' => false,
135
    ];
136
137
    /**
138
     * @var array
139
     */
140
    private static $defaultIDProperty = [
141
        'type' => self::TYPE_INTEGER,
142
        'mutable' => self::IMMUTABLE,
143
    ];
144
145
    /**
146
     * @var array
147
     */
148
    private static $timestampProperties = [
149
        'created_at' => [
150
            'type' => self::TYPE_DATE,
151
            'validate' => 'timestamp|db_timestamp',
152
        ],
153
        'updated_at' => [
154
            'type' => self::TYPE_DATE,
155
            'validate' => 'timestamp|db_timestamp',
156
        ],
157
    ];
158
159
    /**
160
     * @var array
161
     */
162
    private static $softDeleteProperties = [
163
        'deleted_at' => [
164
            'type' => self::TYPE_DATE,
165
            'validate' => 'timestamp|db_timestamp',
166
        ],
167
    ];
168
169
    /**
170
     * @var array
171
     */
172
    private static $initialized = [];
173
174
    /**
175
     * @var DriverInterface
176
     */
177
    private static $driver;
178
179
    /**
180
     * @var array
181
     */
182
    private static $accessors = [];
183
184
    /**
185
     * @var array
186
     */
187
    private static $mutators = [];
188
189
    /**
190
     * @var bool
191
     */
192
    private $_ignoreUnsaved;
193
194
    /**
195
     * Creates a new model object.
196
     *
197
     * @param array|string|Model|false $id     ordered array of ids or comma-separated id string
198
     * @param array                    $values optional key-value map to pre-seed model
199
     */
200
    public function __construct($id = false, array $values = [])
201
    {
202
        // initialize the model
203
        $this->init();
204
205
        // parse the supplied model ID
206
        if (is_array($id)) {
207
            // A model can be supplied as a primary key
208
            foreach ($id as &$el) {
209
                if ($el instanceof self) {
210
                    $el = $el->id();
211
                }
212
            }
213
214
            // The IDs come in as the same order as ::$ids.
215
            // We need to match up the elements on that
216
            // input into a key-value map for each ID property.
217
            $ids = [];
218
            $idQueue = array_reverse($id);
219
            foreach (static::$ids as $k => $f) {
220
                $ids[$f] = (count($idQueue) > 0) ? array_pop($idQueue) : false;
221
            }
222
223
            $this->_id = implode(',', $id);
224
            $this->_ids = $ids;
225
        } elseif ($id instanceof self) {
226
            // A model can be supplied as a primary key
227
            $this->_id = $id->id();
228
            $this->_ids = $id->ids();
229
        } else {
230
            $this->_id = $id;
231
            $idProperty = static::$ids[0];
232
            $this->_ids = [$idProperty => $id];
233
        }
234
235
        // load any given values
236
        if (count($values) > 0) {
237
            $this->refreshWith($values);
238
        }
239
    }
240
241
    /**
242
     * Performs initialization on this model.
243
     */
244
    private function init()
245
    {
246
        // ensure the initialize function is called only once
247
        $k = get_called_class();
248
        if (!isset(self::$initialized[$k])) {
249
            $this->initialize();
250
            self::$initialized[$k] = true;
251
        }
252
    }
253
254
    /**
255
     * The initialize() method is called once per model. It's used
256
     * to perform any one-off tasks before the model gets
257
     * constructed. This is a great place to add any model
258
     * properties. When extending this method be sure to call
259
     * parent::initialize() as some important stuff happens here.
260
     * If extending this method to add properties then you should
261
     * call parent::initialize() after adding any properties.
262
     */
263
    protected function initialize()
264
    {
265
        // load the driver
266
        static::getDriver();
267
268
        // add in the default ID property
269
        if (static::$ids == [self::DEFAULT_ID_PROPERTY] && !isset(static::$properties[self::DEFAULT_ID_PROPERTY])) {
270
            static::$properties[self::DEFAULT_ID_PROPERTY] = self::$defaultIDProperty;
271
        }
272
273
        // generates created_at and updated_at timestamps
274
        if (property_exists($this, 'autoTimestamps')) {
275
            $this->installAutoTimestamps();
276
        }
277
278
        // generates deleted_at timestamps
279
        if (property_exists($this, 'softDelete')) {
280
            $this->installSoftDelete();
281
        }
282
283
        // fill in each property by extending the property
284
        // definition base
285
        foreach (static::$properties as &$property) {
286
            $property = array_replace(self::$propertyDefinitionBase, $property);
287
        }
288
289
        // order the properties array by name for consistency
290
        // since it is constructed in a random order
291
        ksort(static::$properties);
292
    }
293
294
    /**
295
     * Installs the `created_at` and `updated_at` properties.
296
     */
297
    private function installAutoTimestamps()
298
    {
299
        static::$properties = array_replace(self::$timestampProperties, static::$properties);
300
301
        self::creating(function (ModelEvent $event) {
302
            $model = $event->getModel();
303
            $model->created_at = time();
0 ignored issues
show
Documentation introduced by
The property created_at does not exist on object<Pulsar\Model>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
304
            $model->updated_at = time();
0 ignored issues
show
Documentation introduced by
The property updated_at does not exist on object<Pulsar\Model>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
305
        });
306
307
        self::updating(function (ModelEvent $event) {
308
            $event->getModel()->updated_at = time();
0 ignored issues
show
Documentation introduced by
The property updated_at does not exist on object<Pulsar\Model>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
309
        });
310
    }
311
312
    /**
313
     * Installs the `deleted_at` properties.
314
     */
315
    private function installSoftDelete()
316
    {
317
        static::$properties = array_replace(self::$softDeleteProperties, static::$properties);
318
    }
319
320
    /**
321
     * @deprecated
322
     *
323
     * Injects a DI container
324
     *
325
     * @param Container $container
326
     */
327
    public static function inject(Container $container)
328
    {
329
        self::$globalContainer = $container;
330
    }
331
332
    /**
333
     * @deprecated
334
     *
335
     * Gets the DI container used for this model
336
     *
337
     * @return Container|null
338
     */
339
    public function getApp()
340
    {
341
        return self::$globalContainer;
342
    }
343
344
    /**
345
     * Sets the driver for all models.
346
     *
347
     * @param DriverInterface $driver
348
     */
349
    public static function setDriver(DriverInterface $driver)
350
    {
351
        self::$driver = $driver;
352
    }
353
354
    /**
355
     * Gets the driver for all models.
356
     *
357
     * @return DriverInterface
358
     *
359
     * @throws DriverMissingException when a driver has not been set yet
360
     */
361
    public static function getDriver()
362
    {
363
        if (!self::$driver) {
364
            throw new DriverMissingException('A model driver has not been set yet.');
365
        }
366
367
        return self::$driver;
368
    }
369
370
    /**
371
     * Clears the driver for all models.
372
     */
373
    public static function clearDriver()
374
    {
375
        self::$driver = null;
376
    }
377
378
    /**
379
     * Gets the name of the model, i.e. User.
380
     *
381
     * @return string
382
     */
383
    public static function modelName()
384
    {
385
        // strip namespacing
386
        $paths = explode('\\', get_called_class());
387
388
        return end($paths);
389
    }
390
391
    /**
392
     * Gets the model ID.
393
     *
394
     * @return string|number|false ID
395
     */
396
    public function id()
397
    {
398
        return $this->_id;
399
    }
400
401
    /**
402
     * Gets a key-value map of the model ID.
403
     *
404
     * @return array ID map
405
     */
406
    public function ids()
407
    {
408
        return $this->_ids;
409
    }
410
411
    /**
412
     * Sets the global error stack instance.
413
     *
414
     * @param ErrorStack $stack
415
     */
416
    public static function setErrorStack(ErrorStack $stack)
417
    {
418
        self::$globalErrorStack = $stack;
419
    }
420
421
    /**
422
     * Clears the global error stack instance.
423
     */
424
    public static function clearErrorStack()
425
    {
426
        self::$globalErrorStack = null;
427
    }
428
429
    /////////////////////////////
430
    // Magic Methods
431
    /////////////////////////////
432
433
    /**
434
     * Converts the model into a string.
435
     *
436
     * @return string
437
     */
438
    public function __toString()
439
    {
440
        return get_called_class().'('.$this->_id.')';
441
    }
442
443
    /**
444
     * Shortcut to a get() call for a given property.
445
     *
446
     * @param string $name
447
     *
448
     * @return mixed
449
     */
450
    public function __get($name)
451
    {
452
        $result = $this->get([$name]);
453
454
        return reset($result);
455
    }
456
457
    /**
458
     * Sets an unsaved value.
459
     *
460
     * @param string $name
461
     * @param mixed  $value
462
     */
463
    public function __set($name, $value)
464
    {
465
        // if changing property, remove relation model
466
        if (isset($this->_relationships[$name])) {
467
            unset($this->_relationships[$name]);
468
        }
469
470
        // call any mutators
471
        $mutator = self::getMutator($name);
472
        if ($mutator) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $mutator of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
473
            $this->_unsaved[$name] = $this->$mutator($value);
474
        } else {
475
            $this->_unsaved[$name] = $value;
476
        }
477
    }
478
479
    /**
480
     * Checks if an unsaved value or property exists by this name.
481
     *
482
     * @param string $name
483
     *
484
     * @return bool
485
     */
486
    public function __isset($name)
487
    {
488
        return array_key_exists($name, $this->_unsaved) || static::hasProperty($name);
489
    }
490
491
    /**
492
     * Unsets an unsaved value.
493
     *
494
     * @param string $name
495
     */
496
    public function __unset($name)
497
    {
498
        if (array_key_exists($name, $this->_unsaved)) {
499
            // if changing property, remove relation model
500
            if (isset($this->_relationships[$name])) {
501
                unset($this->_relationships[$name]);
502
            }
503
504
            unset($this->_unsaved[$name]);
505
        }
506
    }
507
508
    /////////////////////////////
509
    // ArrayAccess Interface
510
    /////////////////////////////
511
512
    public function offsetExists($offset)
513
    {
514
        return isset($this->$offset);
515
    }
516
517
    public function offsetGet($offset)
518
    {
519
        return $this->$offset;
520
    }
521
522
    public function offsetSet($offset, $value)
523
    {
524
        $this->$offset = $value;
525
    }
526
527
    public function offsetUnset($offset)
528
    {
529
        unset($this->$offset);
530
    }
531
532
    public static function __callStatic($name, $parameters)
533
    {
534
        // Any calls to unkown static methods should be deferred to
535
        // the query. This allows calls like User::where()
536
        // to replace User::query()->where().
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
537
        return call_user_func_array([static::query(), $name], $parameters);
538
    }
539
540
    /////////////////////////////
541
    // Property Definitions
542
    /////////////////////////////
543
544
    /**
545
     * Gets all the property definitions for the model.
546
     *
547
     * @return array key-value map of properties
548
     */
549
    public static function getProperties()
550
    {
551
        return static::$properties;
552
    }
553
554
    /**
555
     * Gets a property defition for the model.
556
     *
557
     * @param string $property property to lookup
558
     *
559
     * @return array|null property
560
     */
561
    public static function getProperty($property)
562
    {
563
        return array_value(static::$properties, $property);
564
    }
565
566
    /**
567
     * Gets the names of the model ID properties.
568
     *
569
     * @return array
570
     */
571
    public static function getIDProperties()
572
    {
573
        return static::$ids;
574
    }
575
576
    /**
577
     * Checks if the model has a property.
578
     *
579
     * @param string $property property
580
     *
581
     * @return bool has property
582
     */
583
    public static function hasProperty($property)
584
    {
585
        return isset(static::$properties[$property]);
586
    }
587
588
    /**
589
     * Gets the mutator method name for a given proeprty name.
590
     * Looks for methods in the form of `setPropertyValue`.
591
     * i.e. the mutator for `last_name` would be `setLastNameValue`.
592
     *
593
     * @param string $property property
594
     *
595
     * @return string|false method name if it exists
596
     */
597 View Code Duplication
    public static function getMutator($property)
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...
598
    {
599
        $class = get_called_class();
600
601
        $k = $class.':'.$property;
602
        if (!array_key_exists($k, self::$mutators)) {
603
            $inflector = Inflector::get();
604
            $method = 'set'.$inflector->camelize($property).'Value';
605
606
            if (!method_exists($class, $method)) {
607
                $method = false;
608
            }
609
610
            self::$mutators[$k] = $method;
611
        }
612
613
        return self::$mutators[$k];
614
    }
615
616
    /**
617
     * Gets the accessor method name for a given proeprty name.
618
     * Looks for methods in the form of `getPropertyValue`.
619
     * i.e. the accessor for `last_name` would be `getLastNameValue`.
620
     *
621
     * @param string $property property
622
     *
623
     * @return string|false method name if it exists
624
     */
625 View Code Duplication
    public static function getAccessor($property)
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...
626
    {
627
        $class = get_called_class();
628
629
        $k = $class.':'.$property;
630
        if (!array_key_exists($k, self::$accessors)) {
631
            $inflector = Inflector::get();
632
            $method = 'get'.$inflector->camelize($property).'Value';
633
634
            if (!method_exists($class, $method)) {
635
                $method = false;
636
            }
637
638
            self::$accessors[$k] = $method;
639
        }
640
641
        return self::$accessors[$k];
642
    }
643
644
    /**
645
     * Marshals a value for a given property from storage.
646
     *
647
     * @param array $property
648
     * @param mixed $value
649
     *
650
     * @return mixed type-casted value
651
     */
652
    public static function cast(array $property, $value)
653
    {
654
        if ($value === null) {
655
            return;
656
        }
657
658
        // handle empty strings as null
659
        if ($property['null'] && $value == '') {
660
            return;
661
        }
662
663
        $type = array_value($property, 'type');
664
        $m = 'to_'.$type;
665
666
        if (!method_exists(Property::class, $m)) {
667
            return $value;
668
        }
669
670
        return Property::$m($value);
671
    }
672
673
    /////////////////////////////
674
    // CRUD Operations
675
    /////////////////////////////
676
677
    /**
678
     * Gets the tablename for storing this model.
679
     *
680
     * @return string
681
     */
682
    public function getTablename()
683
    {
684
        $inflector = Inflector::get();
685
686
        return $inflector->camelize($inflector->pluralize(static::modelName()));
687
    }
688
689
    /**
690
     * Gets the ID of the connection in the connection manager
691
     * that stores this model.
692
     *
693
     * @return string|false
694
     */
695
    public function getConnection()
696
    {
697
        return false;
698
    }
699
700
    /**
701
     * Saves the model.
702
     *
703
     * @return bool true when the operation was successful
704
     */
705
    public function save()
706
    {
707
        if ($this->_id === false) {
708
            return $this->create();
709
        }
710
711
        return $this->set();
712
    }
713
714
    /**
715
     * Saves the model. Throws an exception when the operation fails.
716
     *
717
     * @throws ModelException when the model cannot be saved
718
     */
719
    public function saveOrFail()
720
    {
721
        if (!$this->save()) {
722
            throw new ModelException('Failed to save '.static::modelName());
723
        }
724
    }
725
726
    /**
727
     * Creates a new model.
728
     *
729
     * @param array $data optional key-value properties to set
730
     *
731
     * @return bool true when the operation was successful
732
     *
733
     * @throws BadMethodCallException when called on an existing model
734
     */
735
    public function create(array $data = [])
736
    {
737
        if ($this->_id !== false) {
738
            throw new BadMethodCallException('Cannot call create() on an existing model');
739
        }
740
741
        // mass assign values passed into create()
742
        $this->setValues($data);
743
744
        // dispatch the model.creating event
745
        if (!$this->handleDispatch(ModelEvent::CREATING)) {
746
            return false;
747
        }
748
749
        $requiredProperties = [];
750
        foreach (static::$properties as $name => $property) {
751
            // build a list of the required properties
752
            if ($property['required']) {
753
                $requiredProperties[] = $name;
754
            }
755
756
            // add in default values
757
            if (!array_key_exists($name, $this->_unsaved) && array_key_exists('default', $property)) {
758
                $this->_unsaved[$name] = $property['default'];
759
            }
760
        }
761
762
        // validate the values being saved
763
        $validated = true;
764
        $insertArray = [];
765
        foreach ($this->_unsaved as $name => $value) {
766
            // exclude if value does not map to a property
767
            if (!isset(static::$properties[$name])) {
768
                continue;
769
            }
770
771
            $property = static::$properties[$name];
772
773
            // cannot insert immutable values
774
            // (unless using the default value)
775
            if ($property['mutable'] == self::IMMUTABLE && $value !== $this->getPropertyDefault($property)) {
776
                continue;
777
            }
778
779
            $validated = $validated && $this->filterAndValidate($property, $name, $value);
780
            $insertArray[$name] = $value;
781
        }
782
783
        // check for required fields
784
        foreach ($requiredProperties as $name) {
785
            if (!isset($insertArray[$name])) {
786
                $property = static::$properties[$name];
787
                $this->getErrors()->push([
788
                    'error' => self::ERROR_REQUIRED_FIELD_MISSING,
789
                    'params' => [
790
                        'field' => $name,
791
                        'field_name' => (isset($property['title'])) ? $property['title'] : Inflector::get()->titleize($name), ], ]);
792
793
                $validated = false;
794
            }
795
        }
796
797
        if (!$validated) {
798
            return false;
799
        }
800
801
        $created = self::$driver->createModel($this, $insertArray);
802
803 View Code Duplication
        if ($created) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
804
            // determine the model's new ID
805
            $this->getNewID();
806
807
            // NOTE clear the local cache before the model.created
808
            // event so that fetching values forces a reload
809
            // from the storage layer
810
            $this->clearCache();
811
            $this->_persisted = true;
812
813
            // dispatch the model.created event
814
            if (!$this->handleDispatch(ModelEvent::CREATED)) {
815
                return false;
816
            }
817
        }
818
819
        return $created;
820
    }
821
822
    /**
823
     * Ignores unsaved values when fetching the next value.
824
     *
825
     * @return self
826
     */
827
    public function ignoreUnsaved()
828
    {
829
        $this->_ignoreUnsaved = true;
830
831
        return $this;
832
    }
833
834
    /**
835
     * Fetches property values from the model.
836
     *
837
     * This method looks up values in this order:
838
     * IDs, local cache, unsaved values, storage layer, defaults
839
     *
840
     * @param array $properties list of property names to fetch values of
841
     *
842
     * @return array
843
     */
844
    public function get(array $properties)
845
    {
846
        // load the values from the IDs and local model cache
847
        $values = array_replace($this->ids(), $this->_values);
848
849
        // unless specified, use any unsaved values
850
        $ignoreUnsaved = $this->_ignoreUnsaved;
851
        $this->_ignoreUnsaved = false;
852
853
        if (!$ignoreUnsaved) {
854
            $values = array_replace($values, $this->_unsaved);
855
        }
856
857
        // attempt to load any missing values from the storage layer
858
        $numMissing = count(array_diff($properties, array_keys($values)));
859
        if ($numMissing > 0) {
860
            $this->refresh();
861
            $values = array_replace($values, $this->_values);
862
863
            if (!$ignoreUnsaved) {
864
                $values = array_replace($values, $this->_unsaved);
865
            }
866
        }
867
868
        // build a key-value map of the requested properties
869
        $return = [];
870
        foreach ($properties as $k) {
871
            if (array_key_exists($k, $values)) {
872
                $return[$k] = $values[$k];
873
            // set any missing values to the default value
874
            } elseif (static::hasProperty($k)) {
875
                $return[$k] = $this->_values[$k] = $this->getPropertyDefault(static::$properties[$k]);
876
            // use null for values of non-properties
877
            } else {
878
                $return[$k] = null;
879
            }
880
881
            // call any accessors
882
            if ($accessor = self::getAccessor($k)) {
883
                $return[$k] = $this->$accessor($return[$k]);
884
            }
885
        }
886
887
        return $return;
888
    }
889
890
    /**
891
     * Populates a newly created model with its ID.
892
     */
893
    protected function getNewID()
894
    {
895
        $ids = [];
896
        $namedIds = [];
897
        foreach (static::$ids as $k) {
898
            // attempt use the supplied value if the ID property is mutable
899
            $property = static::getProperty($k);
900
            if (in_array($property['mutable'], [self::MUTABLE, self::MUTABLE_CREATE_ONLY]) && isset($this->_unsaved[$k])) {
901
                $id = $this->_unsaved[$k];
902
            } else {
903
                $id = self::$driver->getCreatedID($this, $k);
904
            }
905
906
            $ids[] = $id;
907
            $namedIds[$k] = $id;
908
        }
909
910
        $this->_id = implode(',', $ids);
911
        $this->_ids = $namedIds;
912
    }
913
914
    /**
915
     * Sets a collection values on the model from an untrusted input.
916
     *
917
     * @param array $values
918
     *
919
     * @throws MassAssignmentException when assigning a value that is protected or not whitelisted
920
     *
921
     * @return self
922
     */
923
    public function setValues($values)
924
    {
925
        // check if the model has a mass assignment whitelist
926
        $permitted = (property_exists($this, 'permitted')) ? static::$permitted : false;
927
928
        // if no whitelist, then check for a blacklist
929
        $protected = (!is_array($permitted) && property_exists($this, 'protected')) ? static::$protected : false;
930
931
        foreach ($values as $k => $value) {
932
            // check for mass assignment violations
933
            if (($permitted && !in_array($k, $permitted)) ||
934
                ($protected && in_array($k, $protected))) {
935
                throw new MassAssignmentException("Mass assignment of $k on ".static::modelName().' is not allowed');
936
            }
937
938
            $this->$k = $value;
939
        }
940
941
        return $this;
942
    }
943
944
    /**
945
     * Converts the model to an array.
946
     *
947
     * @return array
948
     */
949
    public function toArray()
950
    {
951
        // build the list of properties to retrieve
952
        $properties = array_keys(static::$properties);
953
954
        // remove any hidden properties
955
        $hide = (property_exists($this, 'hidden')) ? static::$hidden : [];
956
        $properties = array_diff($properties, $hide);
957
958
        // add any appended properties
959
        $append = (property_exists($this, 'appended')) ? static::$appended : [];
960
        $properties = array_merge($properties, $append);
961
962
        // get the values for the properties
963
        $result = $this->get($properties);
964
965
        foreach ($result as $k => &$value) {
966
            // convert any models to arrays
967
            if ($value instanceof self) {
968
                $value = $value->toArray();
969
            }
970
        }
971
972
        // DEPRECATED
973
        // apply the transformation hook
974
        if (method_exists($this, 'toArrayHook')) {
975
            $this->toArrayHook($result, [], [], []);
0 ignored issues
show
Bug introduced by
The method toArrayHook() does not exist on Pulsar\Model. Did you maybe mean toArray()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
976
        }
977
978
        return $result;
979
    }
980
981
    /**
982
     * Updates the model.
983
     *
984
     * @param array $data optional key-value properties to set
985
     *
986
     * @return bool true when the operation was successful
987
     *
988
     * @throws BadMethodCallException when not called on an existing model
989
     */
990
    public function set(array $data = [])
991
    {
992
        if ($this->_id === false) {
993
            throw new BadMethodCallException('Can only call set() on an existing model');
994
        }
995
996
        // mass assign values passed into set()
997
        $this->setValues($data);
998
999
        // not updating anything?
1000
        if (count($this->_unsaved) == 0) {
1001
            return true;
1002
        }
1003
1004
        // dispatch the model.updating event
1005
        if (!$this->handleDispatch(ModelEvent::UPDATING)) {
1006
            return false;
1007
        }
1008
1009
        // DEPRECATED
1010
        if (method_exists($this, 'preSetHook') && !$this->preSetHook($this->_unsaved)) {
0 ignored issues
show
Bug introduced by
The method preSetHook() does not seem to exist on object<Pulsar\Model>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1011
            return false;
1012
        }
1013
1014
        // validate the values being saved
1015
        $validated = true;
1016
        $updateArray = [];
1017
        foreach ($this->_unsaved as $name => $value) {
1018
            // exclude if value does not map to a property
1019
            if (!isset(static::$properties[$name])) {
1020
                continue;
1021
            }
1022
1023
            $property = static::$properties[$name];
1024
1025
            // can only modify mutable properties
1026
            if ($property['mutable'] != self::MUTABLE) {
1027
                continue;
1028
            }
1029
1030
            $validated = $validated && $this->filterAndValidate($property, $name, $value);
1031
            $updateArray[$name] = $value;
1032
        }
1033
1034
        if (!$validated) {
1035
            return false;
1036
        }
1037
1038
        $updated = self::$driver->updateModel($this, $updateArray);
1039
1040 View Code Duplication
        if ($updated) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
1041
            // NOTE clear the local cache before the model.updated
1042
            // event so that fetching values forces a reload
1043
            // from the storage layer
1044
            $this->clearCache();
1045
            $this->_persisted = true;
1046
1047
            // dispatch the model.updated event
1048
            if (!$this->handleDispatch(ModelEvent::UPDATED)) {
1049
                return false;
1050
            }
1051
        }
1052
1053
        return $updated;
1054
    }
1055
1056
    /**
1057
     * Delete the model.
1058
     *
1059
     * @return bool true when the operation was successful
1060
     */
1061
    public function delete()
1062
    {
1063
        if ($this->_id === false) {
1064
            throw new BadMethodCallException('Can only call delete() on an existing model');
1065
        }
1066
1067
        // dispatch the model.deleting event
1068
        if (!$this->handleDispatch(ModelEvent::DELETING)) {
1069
            return false;
1070
        }
1071
1072
        $deleted = self::$driver->deleteModel($this);
1073
1074
        if ($deleted) {
1075
            // dispatch the model.deleted event
1076
            if (!$this->handleDispatch(ModelEvent::DELETED)) {
1077
                return false;
1078
            }
1079
1080
            $this->_persisted = false;
1081
        }
1082
1083
        return $deleted;
1084
    }
1085
1086
    /////////////////////////////
1087
    // Queries
1088
    /////////////////////////////
1089
1090
    /**
1091
     * Generates a new query instance.
1092
     *
1093
     * @return Query
1094
     */
1095
    public static function query()
1096
    {
1097
        // Create a new model instance for the query to ensure
1098
        // that the model's initialize() method gets called.
1099
        // Otherwise, the property definitions will be incomplete.
1100
        $model = new static();
1101
1102
        return new Query($model);
1103
    }
1104
1105
    /**
1106
     * Finds a single instance of a model given it's ID.
1107
     *
1108
     * @param mixed $id
1109
     *
1110
     * @return Model|null
1111
     */
1112
    public static function find($id)
1113
    {
1114
        $ids = [];
1115
        $id = (array) $id;
1116
        foreach (static::$ids as $j => $k) {
1117
            $ids[$k] = $id[$j];
1118
        }
1119
1120
        return static::where($ids)->first();
1121
    }
1122
1123
    /**
1124
     * Finds a single instance of a model given it's ID or throws an exception.
1125
     *
1126
     * @param mixed $id
1127
     *
1128
     * @return Model
1129
     *
1130
     * @throws ModelNotFoundException when a model could not be found
1131
     */
1132
    public static function findOrFail($id)
1133
    {
1134
        $model = static::find($id);
1135
        if (!$model) {
1136
            throw new ModelNotFoundException('Could not find the requested '.static::modelName());
1137
        }
1138
1139
        return $model;
1140
    }
1141
1142
    /**
1143
     * @deprecated
1144
     *
1145
     * Checks if the model exists in the database
1146
     *
1147
     * @return bool
1148
     */
1149
    public function exists()
1150
    {
1151
        return static::where($this->ids())->count() == 1;
1152
    }
1153
1154
    /**
1155
     * Tells if this model instance has been persisted to the data layer.
1156
     *
1157
     * NOTE: this does not actually perform a check with the data layer
1158
     *
1159
     * @return bool
1160
     */
1161
    public function persisted()
1162
    {
1163
        return $this->_persisted;
1164
    }
1165
1166
    /**
1167
     * Loads the model from the storage layer.
1168
     *
1169
     * @return self
1170
     */
1171
    public function refresh()
1172
    {
1173
        if ($this->_id === false) {
1174
            return $this;
1175
        }
1176
1177
        $values = self::$driver->loadModel($this);
1178
1179
        if (!is_array($values)) {
1180
            return $this;
1181
        }
1182
1183
        // clear any relations
1184
        $this->_relationships = [];
1185
1186
        return $this->refreshWith($values);
1187
    }
1188
1189
    /**
1190
     * Loads values into the model.
1191
     *
1192
     * @param array $values values
1193
     *
1194
     * @return self
1195
     */
1196
    public function refreshWith(array $values)
1197
    {
1198
        $this->_persisted = true;
1199
        $this->_values = $values;
1200
1201
        return $this;
1202
    }
1203
1204
    /**
1205
     * Clears the cache for this model.
1206
     *
1207
     * @return self
1208
     */
1209
    public function clearCache()
1210
    {
1211
        $this->_unsaved = [];
1212
        $this->_values = [];
1213
        $this->_relationships = [];
1214
1215
        return $this;
1216
    }
1217
1218
    /////////////////////////////
1219
    // Relationships
1220
    /////////////////////////////
1221
1222
    /**
1223
     * @deprecated
1224
     *
1225
     * Gets the model for a has-one relationship
1226
     *
1227
     * @param string $k property
1228
     *
1229
     * @return Model|null
1230
     */
1231
    public function relation($k)
1232
    {
1233
        $id = $this->$k;
1234
        if (!$id) {
1235
            return;
1236
        }
1237
1238
        if (!isset($this->_relationships[$k])) {
1239
            $property = static::getProperty($k);
1240
            $relationModelClass = $property['relation'];
1241
            $this->_relationships[$k] = $relationModelClass::find($id);
1242
        }
1243
1244
        return $this->_relationships[$k];
1245
    }
1246
1247
    /**
1248
     * @deprecated
1249
     *
1250
     * Sets the model for a has-one relationship
1251
     *
1252
     * @param string $k
1253
     * @param Model  $model
1254
     *
1255
     * @return self
1256
     */
1257
    public function setRelation($k, Model $model)
1258
    {
1259
        $this->$k = $model->id();
1260
        $this->_relationships[$k] = $model;
1261
1262
        return $this;
1263
    }
1264
1265
    /**
1266
     * Creates the parent side of a One-To-One relationship.
1267
     *
1268
     * @param string $model      foreign model class
1269
     * @param string $foreignKey identifying key on foreign model
1270
     * @param string $localKey   identifying key on local model
1271
     *
1272
     * @return Relation\Relation
1273
     */
1274
    public function hasOne($model, $foreignKey = '', $localKey = '')
1275
    {
1276
        return new HasOne($this, $localKey, $model, $foreignKey);
1277
    }
1278
1279
    /**
1280
     * Creates the child side of a One-To-One or One-To-Many relationship.
1281
     *
1282
     * @param string $model      foreign model class
1283
     * @param string $foreignKey identifying key on foreign model
1284
     * @param string $localKey   identifying key on local model
1285
     *
1286
     * @return Relation\Relation
1287
     */
1288
    public function belongsTo($model, $foreignKey = '', $localKey = '')
1289
    {
1290
        return new BelongsTo($this, $localKey, $model, $foreignKey);
1291
    }
1292
1293
    /**
1294
     * Creates the parent side of a Many-To-One or Many-To-Many relationship.
1295
     *
1296
     * @param string $model      foreign model class
1297
     * @param string $foreignKey identifying key on foreign model
1298
     * @param string $localKey   identifying key on local model
1299
     *
1300
     * @return Relation\Relation
1301
     */
1302
    public function hasMany($model, $foreignKey = '', $localKey = '')
1303
    {
1304
        return new HasMany($this, $localKey, $model, $foreignKey);
1305
    }
1306
1307
    /**
1308
     * Creates the child side of a Many-To-Many relationship.
1309
     *
1310
     * @param string $model      foreign model class
1311
     * @param string $tablename  pivot table name
1312
     * @param string $foreignKey identifying key on foreign model
1313
     * @param string $localKey   identifying key on local model
1314
     *
1315
     * @return \Pulsar\Relation\Relation
1316
     */
1317
    public function belongsToMany($model, $tablename = '', $foreignKey = '', $localKey = '')
1318
    {
1319
        return new BelongsToMany($this, $localKey, $tablename, $model, $foreignKey);
1320
    }
1321
1322
    /////////////////////////////
1323
    // Events
1324
    /////////////////////////////
1325
1326
    /**
1327
     * Gets the event dispatcher.
1328
     *
1329
     * @return EventDispatcher
1330
     */
1331
    public static function getDispatcher($ignoreCache = false)
1332
    {
1333
        $class = get_called_class();
1334
        if ($ignoreCache || !isset(self::$dispatchers[$class])) {
1335
            self::$dispatchers[$class] = new EventDispatcher();
1336
        }
1337
1338
        return self::$dispatchers[$class];
1339
    }
1340
1341
    /**
1342
     * Subscribes to a listener to an event.
1343
     *
1344
     * @param string   $event    event name
1345
     * @param callable $listener
1346
     * @param int      $priority optional priority, higher #s get called first
1347
     */
1348
    public static function listen($event, callable $listener, $priority = 0)
1349
    {
1350
        static::getDispatcher()->addListener($event, $listener, $priority);
1351
    }
1352
1353
    /**
1354
     * Adds a listener to the model.creating and model.updating events.
1355
     *
1356
     * @param callable $listener
1357
     * @param int      $priority
1358
     */
1359
    public static function saving(callable $listener, $priority = 0)
1360
    {
1361
        static::listen(ModelEvent::CREATING, $listener, $priority);
1362
        static::listen(ModelEvent::UPDATING, $listener, $priority);
1363
    }
1364
1365
    /**
1366
     * Adds a listener to the model.created and model.updated events.
1367
     *
1368
     * @param callable $listener
1369
     * @param int      $priority
1370
     */
1371
    public static function saved(callable $listener, $priority = 0)
1372
    {
1373
        static::listen(ModelEvent::CREATED, $listener, $priority);
1374
        static::listen(ModelEvent::UPDATED, $listener, $priority);
1375
    }
1376
1377
    /**
1378
     * Adds a listener to the model.creating event.
1379
     *
1380
     * @param callable $listener
1381
     * @param int      $priority
1382
     */
1383
    public static function creating(callable $listener, $priority = 0)
1384
    {
1385
        static::listen(ModelEvent::CREATING, $listener, $priority);
1386
    }
1387
1388
    /**
1389
     * Adds a listener to the model.created event.
1390
     *
1391
     * @param callable $listener
1392
     * @param int      $priority
1393
     */
1394
    public static function created(callable $listener, $priority = 0)
1395
    {
1396
        static::listen(ModelEvent::CREATED, $listener, $priority);
1397
    }
1398
1399
    /**
1400
     * Adds a listener to the model.updating event.
1401
     *
1402
     * @param callable $listener
1403
     * @param int      $priority
1404
     */
1405
    public static function updating(callable $listener, $priority = 0)
1406
    {
1407
        static::listen(ModelEvent::UPDATING, $listener, $priority);
1408
    }
1409
1410
    /**
1411
     * Adds a listener to the model.updated event.
1412
     *
1413
     * @param callable $listener
1414
     * @param int      $priority
1415
     */
1416
    public static function updated(callable $listener, $priority = 0)
1417
    {
1418
        static::listen(ModelEvent::UPDATED, $listener, $priority);
1419
    }
1420
1421
    /**
1422
     * Adds a listener to the model.deleting event.
1423
     *
1424
     * @param callable $listener
1425
     * @param int      $priority
1426
     */
1427
    public static function deleting(callable $listener, $priority = 0)
1428
    {
1429
        static::listen(ModelEvent::DELETING, $listener, $priority);
1430
    }
1431
1432
    /**
1433
     * Adds a listener to the model.deleted event.
1434
     *
1435
     * @param callable $listener
1436
     * @param int      $priority
1437
     */
1438
    public static function deleted(callable $listener, $priority = 0)
1439
    {
1440
        static::listen(ModelEvent::DELETED, $listener, $priority);
1441
    }
1442
1443
    /**
1444
     * Dispatches an event.
1445
     *
1446
     * @param string $eventName
1447
     *
1448
     * @return ModelEvent
1449
     */
1450
    protected function dispatch($eventName)
1451
    {
1452
        $event = new ModelEvent($this);
1453
1454
        return static::getDispatcher()->dispatch($eventName, $event);
1455
    }
1456
1457
    /**
1458
     * Dispatches the given event and checks if it was successful.
1459
     *
1460
     * @param string $eventName
1461
     *
1462
     * @return bool true if the events were successfully propagated
1463
     */
1464
    private function handleDispatch($eventName)
1465
    {
1466
        $event = $this->dispatch($eventName);
1467
1468
        return !$event->isPropagationStopped();
1469
    }
1470
1471
    /////////////////////////////
1472
    // Validation
1473
    /////////////////////////////
1474
1475
    /**
1476
     * Gets the error stack for this model.
1477
     *
1478
     * @return ErrorStack
1479
     */
1480
    public function getErrors()
1481
    {
1482
        if (!$this->_errors && self::$globalErrorStack) {
1483
            $this->_errors = self::$globalErrorStack;
1484
        } elseif (!$this->_errors) {
1485
            $this->_errors = new ErrorStack();
1486
        }
1487
1488
        return $this->_errors;
1489
    }
1490
1491
    /**
1492
     * Validates and marshals a value to storage.
1493
     *
1494
     * @param array  $property
1495
     * @param string $propertyName
1496
     * @param mixed  $value
1497
     *
1498
     * @return bool
1499
     */
1500
    private function filterAndValidate(array $property, $propertyName, &$value)
1501
    {
1502
        // assume empty string is a null value for properties
1503
        // that are marked as optionally-null
1504
        if ($property['null'] && empty($value)) {
1505
            $value = null;
1506
1507
            return true;
1508
        }
1509
1510
        // validate
1511
        list($valid, $value) = $this->validate($property, $propertyName, $value);
1512
1513
        // unique?
1514
        if ($valid && $property['unique'] && ($this->_id === false || $value != $this->ignoreUnsaved()->$propertyName)) {
1515
            $valid = $this->checkUniqueness($property, $propertyName, $value);
1516
        }
1517
1518
        return $valid;
1519
    }
1520
1521
    /**
1522
     * Validates a value for a property.
1523
     *
1524
     * @param array  $property
1525
     * @param string $propertyName
1526
     * @param mixed  $value
1527
     *
1528
     * @return array
1529
     */
1530
    private function validate(array $property, $propertyName, $value)
1531
    {
1532
        $valid = true;
1533
1534
        if (isset($property['validate']) && is_callable($property['validate'])) {
1535
            $valid = call_user_func_array($property['validate'], [$value]);
1536
        } elseif (isset($property['validate'])) {
1537
            $valid = Validate::is($value, $property['validate']);
0 ignored issues
show
Deprecated Code introduced by
The method Pulsar\Validate::is() has been deprecated with message: Validates one or more fields based upon certain filters. Filters may be chained and will be executed in order
i.e. Validate::is( '[email protected]', 'email' ) or Validate::is( ['password1', 'password2'], 'matching|password:8|required' ). NOTE: some filters may modify the data, which is passed in by reference

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1538
        }
1539
1540 View Code Duplication
        if (!$valid) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
1541
            $this->getErrors()->push([
1542
                'error' => self::ERROR_VALIDATION_FAILED,
1543
                'params' => [
1544
                    'field' => $propertyName,
1545
                    'field_name' => (isset($property['title'])) ? $property['title'] : Inflector::get()->titleize($propertyName), ], ]);
1546
        }
1547
1548
        return [$valid, $value];
1549
    }
1550
1551
    /**
1552
     * Checks if a value is unique for a property.
1553
     *
1554
     * @param array  $property
1555
     * @param string $propertyName
1556
     * @param mixed  $value
1557
     *
1558
     * @return bool
1559
     */
1560
    private function checkUniqueness(array $property, $propertyName, $value)
1561
    {
1562
        $n = static::where([$propertyName => $value])->count();
1563 View Code Duplication
        if ($n > 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
1564
            $this->getErrors()->push([
1565
                'error' => self::ERROR_NOT_UNIQUE,
1566
                'params' => [
1567
                    'field' => $propertyName,
1568
                    'field_name' => (isset($property['title'])) ? $property['title'] : Inflector::get()->titleize($propertyName), ], ]);
1569
1570
            return false;
1571
        }
1572
1573
        return true;
1574
    }
1575
1576
    /**
1577
     * Gets the marshaled default value for a property (if set).
1578
     *
1579
     * @param string $property
1580
     *
1581
     * @return mixed
1582
     */
1583
    private function getPropertyDefault(array $property)
1584
    {
1585
        return array_value($property, 'default');
1586
    }
1587
}
1588