Completed
Push — master ( ebdf08...6b1cd5 )
by Jared
06:17 queued 04:01
created

Model::totalRecords()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 7
rs 9.4286
cc 1
eloc 4
nc 1
nop 1
1
<?php
2
3
/**
4
 * @package Pulsar
5
 * @author Jared King <[email protected]>
6
 * @link http://jaredtking.com
7
 * @copyright 2015 Jared King
8
 * @license MIT
9
 */
10
11
namespace Pulsar;
12
13
use ICanBoogie\Inflector;
14
use Pulsar\Driver\DriverInterface;
15
use Pulsar\Relation\HasOne;
16
use Pulsar\Relation\BelongsTo;
17
use Pulsar\Relation\HasMany;
18
use Pulsar\Relation\BelongsToMany;
19
use Pimple\Container;
20
use Symfony\Component\EventDispatcher\EventDispatcher;
21
22
abstract class Model implements \ArrayAccess
23
{
24
    const IMMUTABLE = 0;
25
    const MUTABLE_CREATE_ONLY = 1;
26
    const MUTABLE = 2;
27
28
    const TYPE_STRING = 'string';
29
    const TYPE_NUMBER = 'number';
30
    const TYPE_BOOLEAN = 'boolean';
31
    const TYPE_DATE = 'date';
32
    const TYPE_OBJECT = 'object';
33
    const TYPE_ARRAY = 'array';
34
35
    const ERROR_REQUIRED_FIELD_MISSING = 'required_field_missing';
36
    const ERROR_VALIDATION_FAILED = 'validation_failed';
37
    const ERROR_NOT_UNIQUE = 'not_unique';
38
39
    const DEFAULT_ID_PROPERTY = 'id';
40
41
    /////////////////////////////
42
    // Model visible variables
43
    /////////////////////////////
44
45
    /**
46
     * List of model ID property names.
47
     *
48
     * @staticvar array
49
     */
50
    protected static $ids = [self::DEFAULT_ID_PROPERTY];
51
52
    /**
53
     * Property definitions expressed as a key-value map with
54
     * property names as the keys.
55
     * i.e. ['enabled' => ['type' => Model::TYPE_BOOLEAN]].
56
     *
57
     * @staticvar array
58
     */
59
    protected static $properties = [];
60
61
    /**
62
     * @staticvar \Pimple\Container
63
     */
64
    protected static $injectedApp;
65
66
    /**
67
     * @staticvar array
68
     */
69
    protected static $dispatchers;
70
71
    /**
72
     * @var number|string|bool
73
     */
74
    protected $_id;
75
76
    /**
77
     * @var \Pimple\Container
78
     */
79
    protected $app;
80
81
    /**
82
     * @var array
83
     */
84
    protected $_values = [];
85
86
    /**
87
     * @var array
88
     */
89
    protected $_unsaved = [];
90
91
    /**
92
     * @var array
93
     */
94
    protected $_relationships = [];
95
96
    /////////////////////////////
97
    // Base model variables
98
    /////////////////////////////
99
100
    /**
101
     * @staticvar array
102
     */
103
    private static $propertyDefinitionBase = [
104
        'type' => self::TYPE_STRING,
105
        'mutable' => self::MUTABLE,
106
        'null' => false,
107
        'unique' => false,
108
        'required' => false,
109
    ];
110
111
    /**
112
     * @staticvar array
113
     */
114
    private static $defaultIDProperty = [
115
        'type' => self::TYPE_NUMBER,
116
        'mutable' => self::IMMUTABLE,
117
    ];
118
119
    /**
120
     * @staticvar array
121
     */
122
    private static $timestampProperties = [
123
        'created_at' => [
124
            'type' => self::TYPE_DATE,
125
            'default' => null,
126
            'null' => true,
127
            'validate' => 'timestamp|db_timestamp',
128
        ],
129
        'updated_at' => [
130
            'type' => self::TYPE_DATE,
131
            'validate' => 'timestamp|db_timestamp',
132
        ],
133
    ];
134
135
    /**
136
     * @staticvar array
137
     */
138
    private static $initialized = [];
139
140
    /**
141
     * @staticvar Model\Driver\DriverInterface
142
     */
143
    private static $driver;
144
145
    /**
146
     * @staticvar array
147
     */
148
    private static $accessors = [];
149
150
    /**
151
     * @staticvar array
152
     */
153
    private static $mutators = [];
154
155
    /**
156
     * @var bool
157
     */
158
    private $_ignoreUnsaved;
159
160
    /**
161
     * Creates a new model object.
162
     *
163
     * @param array|string|Model|false $id     ordered array of ids or comma-separated id string
164
     * @param array                    $values optional key-value map to pre-seed model
165
     */
166
    public function __construct($id = false, array $values = [])
167
    {
168
        // initialize the model
169
        $this->app = self::$injectedApp;
170
        $this->init();
171
172
        // TODO need to store the id as an array
173
        // instead of a string to maintain type integrity
174
        if (is_array($id)) {
175
            // A model can be supplied as a primary key
176
            foreach ($id as &$el) {
177
                if ($el instanceof self) {
178
                    $el = $el->id();
179
                }
180
            }
181
182
            $id = implode(',', $id);
183
        // A model can be supplied as a primary key
184
        } elseif ($id instanceof self) {
185
            $id = $id->id();
186
        }
187
188
        $this->_id = $id;
189
190
        // load any given values
191
        if (count($values) > 0) {
192
            $this->refreshWith($values);
193
        }
194
    }
195
196
    /**
197
     * Performs initialization on this model.
198
     */
199
    private function init()
200
    {
201
        // ensure the initialize function is called only once
202
        $k = get_called_class();
203
        if (!isset(self::$initialized[$k])) {
204
            $this->initialize();
205
            self::$initialized[$k] = true;
206
        }
207
    }
208
209
    /**
210
     * The initialize() method is called once per model. It's used
211
     * to perform any one-off tasks before the model gets
212
     * constructed. This is a great place to add any model
213
     * properties. When extending this method be sure to call
214
     * parent::initialize() as some important stuff happens here.
215
     * If extending this method to add properties then you should
216
     * call parent::initialize() after adding any properties.
217
     */
218
    protected function initialize()
219
    {
220
        // load the driver
221
        static::getDriver();
0 ignored issues
show
Unused Code introduced by
The call to the method Pulsar\Model::getDriver() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
222
223
        // add in the default ID property
224
        if (static::$ids == [self::DEFAULT_ID_PROPERTY] && !isset(static::$properties[self::DEFAULT_ID_PROPERTY])) {
225
            static::$properties[self::DEFAULT_ID_PROPERTY] = self::$defaultIDProperty;
226
        }
227
228
        // add in the auto timestamp properties
229
        if (property_exists(get_called_class(), 'autoTimestamps')) {
230
            static::$properties = array_replace(self::$timestampProperties, static::$properties);
231
        }
232
233
        // fill in each property by extending the property
234
        // definition base
235
        foreach (static::$properties as &$property) {
236
            $property = array_replace(self::$propertyDefinitionBase, $property);
237
        }
238
239
        // order the properties array by name for consistency
240
        // since it is constructed in a random order
241
        ksort(static::$properties);
242
    }
243
244
    /**
245
     * Injects a DI container.
246
     *
247
     * @param \Pimple\Container $app
248
     */
249
    public static function inject(Container $app)
250
    {
251
        self::$injectedApp = $app;
252
    }
253
254
    /**
255
     * Gets the DI container used for this model.
256
     *
257
     * @return Container
258
     */
259
    public function getApp()
260
    {
261
        return $this->app;
262
    }
263
264
    /**
265
     * Sets the driver for all models.
266
     *
267
     * @param Model\Driver\DriverInterface $driver
268
     */
269
    public static function setDriver(DriverInterface $driver)
270
    {
271
        self::$driver = $driver;
272
    }
273
274
    /**
275
     * Gets the driver for all models.
276
     *
277
     * @return Model\Driver\DriverInterface
278
     */
279
    public static function getDriver()
280
    {
281
        return self::$driver;
282
    }
283
284
    /**
285
     * Gets the name of the model without namespacing.
286
     *
287
     * @return string
288
     */
289
    public static function modelName()
290
    {
291
        $class_name = get_called_class();
292
293
        // strip namespacing
294
        $paths = explode('\\', $class_name);
295
296
        return end($paths);
297
    }
298
299
    /**
300
     * Gets the model ID.
301
     *
302
     * @return string|number|false ID
303
     */
304
    public function id()
305
    {
306
        return $this->_id;
307
    }
308
309
    /**
310
     * Gets a key-value map of the model ID.
311
     *
312
     * @return array ID map
313
     */
314
    public function ids()
315
    {
316
        $return = [];
317
318
        // match up id values from comma-separated id string with property names
319
        $ids = explode(',', $this->_id);
320
        $ids = array_reverse($ids);
321
322
        // TODO need to store the id as an array
323
        // instead of a string to maintain type integrity
324
        foreach (static::$ids as $k => $f) {
325
            $id = (count($ids) > 0) ? array_pop($ids) : false;
326
327
            $return[$f] = $id;
328
        }
329
330
        return $return;
331
    }
332
333
    /////////////////////////////
334
    // Magic Methods
335
    /////////////////////////////
336
337
    /**
338
     * Converts the model into a string.
339
     *
340
     * @return string
341
     */
342
    public function __toString()
343
    {
344
        return get_called_class().'('.$this->_id.')';
345
    }
346
347
    /**
348
     * Shortcut to a get() call for a given property.
349
     *
350
     * @param string $name
351
     *
352
     * @return mixed
353
     */
354
    public function __get($name)
355
    {
356
        $result = $this->get([$name]);
357
358
        return reset($result);
359
    }
360
361
    /**
362
     * Sets an unsaved value.
363
     *
364
     * @param string $name
365
     * @param mixed  $value
366
     */
367
    public function __set($name, $value)
368
    {
369
        // if changing property, remove relation model
370
        if (isset($this->_relationships[$name])) {
371
            unset($this->_relationships[$name]);
372
        }
373
374
        // call any mutators
375
        $mutator = self::getMutator($name);
376
        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...
377
            $this->_unsaved[$name] = $this->$mutator($value);
378
        } else {
379
            $this->_unsaved[$name] = $value;
380
        }
381
    }
382
383
    /**
384
     * Checks if an unsaved value or property exists by this name.
385
     *
386
     * @param string $name
387
     *
388
     * @return bool
389
     */
390
    public function __isset($name)
391
    {
392
        return array_key_exists($name, $this->_unsaved) || static::hasProperty($name);
393
    }
394
395
    /**
396
     * Unsets an unsaved value.
397
     *
398
     * @param string $name
399
     */
400
    public function __unset($name)
401
    {
402
        if (array_key_exists($name, $this->_unsaved)) {
403
            // if changing property, remove relation model
404
            if (isset($this->_relationships[$name])) {
405
                unset($this->_relationships[$name]);
406
            }
407
408
            unset($this->_unsaved[$name]);
409
        }
410
    }
411
412
    /////////////////////////////
413
    // ArrayAccess Interface
414
    /////////////////////////////
415
416
    public function offsetExists($offset)
417
    {
418
        return isset($this->$offset);
419
    }
420
421
    public function offsetGet($offset)
422
    {
423
        return $this->$offset;
424
    }
425
426
    public function offsetSet($offset, $value)
427
    {
428
        $this->$offset = $value;
429
    }
430
431
    public function offsetUnset($offset)
432
    {
433
        unset($this->$offset);
434
    }
435
436
    public static function __callStatic($name, $parameters)
437
    {
438
        // Any calls to unkown static methods should be deferred to
439
        // the query. This allows calls like User::where()
440
        // 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...
441
        return call_user_func_array([static::query(), $name], $parameters);
442
    }
443
444
    /////////////////////////////
445
    // Property Definitions
446
    /////////////////////////////
447
448
    /**
449
     * Gets all the property definitions for the model.
450
     *
451
     * @return array key-value map of properties
452
     */
453
    public static function getProperties()
454
    {
455
        return static::$properties;
456
    }
457
458
    /**
459
     * Gets a property defition for the model.
460
     *
461
     * @param string $property property to lookup
462
     *
463
     * @return array|null property
464
     */
465
    public static function getProperty($property)
466
    {
467
        return array_value(static::$properties, $property);
468
    }
469
470
    /**
471
     * Gets the names of the model ID properties.
472
     *
473
     * @return array
474
     */
475
    public static function getIDProperties()
476
    {
477
        return static::$ids;
478
    }
479
480
    /**
481
     * Checks if the model has a property.
482
     *
483
     * @param string $property property
484
     *
485
     * @return bool has property
486
     */
487
    public static function hasProperty($property)
488
    {
489
        return isset(static::$properties[$property]);
490
    }
491
492
    /**
493
     * Gets the mutator method name for a given proeprty name.
494
     * Looks for methods in the form of `setPropertyValue`.
495
     * i.e. the mutator for `last_name` would be `setLastNameValue`.
496
     *
497
     * @param string $property property
498
     *
499
     * @return string|false method name if it exists
500
     */
501 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...
502
    {
503
        $class = get_called_class();
504
505
        $k = $class.':'.$property;
506
        if (!array_key_exists($k, self::$mutators)) {
507
            $inflector = Inflector::get();
508
            $method = 'set'.$inflector->camelize($property).'Value';
509
510
            if (!method_exists($class, $method)) {
511
                $method = false;
512
            }
513
514
            self::$mutators[$k] = $method;
515
        }
516
517
        return self::$mutators[$k];
518
    }
519
520
    /**
521
     * Gets the accessor method name for a given proeprty name.
522
     * Looks for methods in the form of `getPropertyValue`.
523
     * i.e. the accessor for `last_name` would be `getLastNameValue`.
524
     *
525
     * @param string $property property
526
     *
527
     * @return string|false method name if it exists
528
     */
529 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...
530
    {
531
        $class = get_called_class();
532
533
        $k = $class.':'.$property;
534
        if (!array_key_exists($k, self::$accessors)) {
535
            $inflector = Inflector::get();
536
            $method = 'get'.$inflector->camelize($property).'Value';
537
538
            if (!method_exists($class, $method)) {
539
                $method = false;
540
            }
541
542
            self::$accessors[$k] = $method;
543
        }
544
545
        return self::$accessors[$k];
546
    }
547
548
    /////////////////////////////
549
    // CRUD Operations
550
    /////////////////////////////
551
552
    /**
553
     * Saves the model.
554
     *
555
     * @return bool
556
     */
557
    public function save()
558
    {
559
        if ($this->_id === false) {
560
            return $this->create();
561
        }
562
563
        return $this->set($this->_unsaved);
564
    }
565
566
    /**
567
     * Creates a new model.
568
     *
569
     * @param array $data optional key-value properties to set
570
     *
571
     * @return bool
572
     */
573
    public function create(array $data = [])
574
    {
575
        if ($this->_id !== false) {
576
            return false;
577
        }
578
579
        if (!empty($data)) {
580
            foreach ($data as $k => $value) {
581
                $this->$k = $value;
582
            }
583
        }
584
585
        // dispatch the model.creating event
586
        if (!$this->beforeCreate()) {
587
            return false;
588
        }
589
590
        $requiredProperties = [];
591
        foreach (static::$properties as $name => $property) {
592
            // build a list of the required properties
593
            if ($property['required']) {
594
                $requiredProperties[] = $name;
595
            }
596
597
            // add in default values
598
            if (!array_key_exists($name, $this->_unsaved) && array_key_exists('default', $property)) {
599
                $this->_unsaved[$name] = $property['default'];
600
            }
601
        }
602
603
        // validate the values being saved
604
        $validated = true;
605
        $insertArray = [];
606
        foreach ($this->_unsaved as $name => $value) {
607
            // exclude if value does not map to a property
608
            if (!isset(static::$properties[$name])) {
609
                continue;
610
            }
611
612
            $property = static::$properties[$name];
613
614
            // cannot insert immutable values
615
            // (unless using the default value)
616
            if ($property['mutable'] == self::IMMUTABLE && $value !== $this->getPropertyDefault($property)) {
617
                continue;
618
            }
619
620
            $validated = $validated && $this->filterAndValidate($property, $name, $value);
621
            $insertArray[$name] = $value;
622
        }
623
624
        // check for required fields
625
        foreach ($requiredProperties as $name) {
626
            if (!isset($insertArray[$name])) {
627
                $property = static::$properties[$name];
628
                $this->app['errors']->push([
629
                    'error' => self::ERROR_REQUIRED_FIELD_MISSING,
630
                    'params' => [
631
                        'field' => $name,
632
                        'field_name' => (isset($property['title'])) ? $property['title'] : Inflector::get()->titleize($name), ], ]);
633
634
                $validated = false;
635
            }
636
        }
637
638
        if (!$validated) {
639
            return false;
640
        }
641
642
        $created = self::$driver->createModel($this, $insertArray);
643
644
        if ($created) {
645
            // determine the model's new ID
646
            $this->_id = $this->getNewID();
647
648
            // NOTE clear the local cache before the model.created
649
            // event so that fetching values forces a reload
650
            // from the storage layer
651
            $this->clearCache();
652
653
            // dispatch the model.created event
654
            if (!$this->afterCreate()) {
655
                return false;
656
            }
657
        }
658
659
        return $created;
660
    }
661
662
    /**
663
     * Ignores unsaved values when fetching the next value.
664
     *
665
     * @return self
666
     */
667
    public function ignoreUnsaved()
668
    {
669
        $this->_ignoreUnsaved = true;
670
671
        return $this;
672
    }
673
674
    /**
675
     * Fetches property values from the model.
676
     *
677
     * This method looks up values in this order:
678
     * IDs, local cache, unsaved values, storage layer, defaults
679
     *
680
     * @param array $properties list of property names to fetch values of
681
     *
682
     * @return array
683
     */
684
    public function get(array $properties)
685
    {
686
        // load the values from the IDs and local model cache
687
        $values = array_replace($this->ids(), $this->_values);
688
689
        // unless specified, use any unsaved values
690
        $ignoreUnsaved = $this->_ignoreUnsaved;
691
        $this->_ignoreUnsaved = false;
692
693
        if (!$ignoreUnsaved) {
694
            $values = array_replace($values, $this->_unsaved);
695
        }
696
697
        // attempt to load any missing values from the storage layer
698
        $numMissing = count(array_diff($properties, array_keys($values)));
699
        if ($numMissing > 0) {
700
            $this->refresh();
701
            $values = array_replace($values, $this->_values);
702
703
            if (!$ignoreUnsaved) {
704
                $values = array_replace($values, $this->_unsaved);
705
            }
706
        }
707
708
        // build a key-value map of the requested properties
709
        $return = [];
710
        foreach ($properties as $k) {
711
            if (array_key_exists($k, $values)) {
712
                $return[$k] = $values[$k];
713
            // set any missing values to the default value
714
            } elseif (static::hasProperty($k)) {
715
                $return[$k] = $this->_values[$k] = $this->getPropertyDefault(static::$properties[$k]);
716
            // use null for values of non-properties
717
            } else {
718
                $return[$k] = null;
719
            }
720
721
            // call any accessors
722
            if ($accessor = self::getAccessor($k)) {
723
                $return[$k] = $this->$accessor($return[$k]);
724
            }
725
        }
726
727
        return $return;
728
    }
729
730
    /**
731
     * Gets the ID for a newly created model.
732
     *
733
     * @return string
734
     */
735
    protected function getNewID()
736
    {
737
        $ids = [];
738
        foreach (static::$ids as $k) {
739
            // attempt use the supplied value if the ID property is mutable
740
            $property = static::getProperty($k);
741
            if (in_array($property['mutable'], [self::MUTABLE, self::MUTABLE_CREATE_ONLY]) && isset($this->_unsaved[$k])) {
742
                $ids[] = $this->_unsaved[$k];
743
            } else {
744
                $ids[] = self::$driver->getCreatedID($this, $k);
745
            }
746
        }
747
748
        // TODO need to store the id as an array
749
        // instead of a string to maintain type integrity
750
        return (count($ids) > 1) ? implode(',', $ids) : $ids[0];
751
    }
752
753
    /**
754
     * Converts the model to an array.
755
     *
756
     * @return array model array
757
     */
758
    public function toArray()
759
    {
760
        // build the list of properties to retrieve
761
        $properties = array_keys(static::$properties);
762
763
        // remove any hidden properties
764
        $hide = (property_exists($this, 'hidden')) ? static::$hidden : [];
765
        $properties = array_diff($properties, $hide);
766
767
        // add any appended properties
768
        $append = (property_exists($this, 'appended')) ? static::$appended : [];
769
        $properties = array_merge($properties, $append);
770
771
        // get the values for the properties
772
        $result = $this->get($properties);
773
774
        // apply the transformation hook
775
        if (method_exists($this, 'toArrayHook')) {
776
            $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...
777
        }
778
779
        return $result;
780
    }
781
782
    /**
783
     * Converts the model to an array.
784
     *
785
     * @param array $exclude properties to exclude
786
     * @param array $include properties to include
787
     * @param array $expand  properties to expand
788
     *
789
     * @return array properties
790
     */
791
    public function toArrayDeprecated(array $exclude = [], array $include = [], array $expand = [])
792
    {
793
        // start with the base array representation of this object
794
        $result = $this->toArray();
795
796
        // apply namespacing to $exclude
797
        $namedExc = [];
798
        foreach ($exclude as $e) {
799
            array_set($namedExc, $e, true);
800
        }
801
802
        // apply namespacing to $include
803
        $namedInc = [];
804
        foreach ($include as $e) {
805
            array_set($namedInc, $e, true);
806
        }
807
808
        // apply namespacing to $expand
809
        $namedExp = [];
810
        foreach ($expand as $e) {
811
            array_set($namedExp, $e, true);
812
        }
813
814
        // remove excluded properties
815 View Code Duplication
        foreach (array_keys($result) as $k) {
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...
816
            if (isset($namedExc[$k]) && !is_array($namedExc[$k])) {
817
                unset($result[$k]);
818
            }
819
        }
820
821
        // add included properties
822 View Code Duplication
        foreach (array_keys($namedInc) as $k) {
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...
823
            if (!isset($result[$k]) && isset($namedInc[$k])) {
824
                $result[$k] = $this->$k;
825
            }
826
        }
827
828
        // expand any relational model properties
829
        $result = $this->toArrayExpand($result, $namedExc, $namedInc, $namedExp);
830
831
        // apply hooks, if available
832
        if (method_exists($this, 'toArrayHook')) {
833
            $this->toArrayHook($result, $namedExc, $namedInc, $namedExp);
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...
834
        }
835
836
        return $result;
837
    }
838
839
    /**
840
     * Expands any relational properties within a result.
841
     *
842
     * @param array $result
843
     * @param array $namedExc
844
     * @param array $namedInc
845
     * @param array $namedExp
846
     *
847
     * @return array
848
     */
849
    private function toArrayExpand(array $result, array $namedExc, array $namedInc, array $namedExp)
850
    {
851
        foreach ($namedExp as $k => $subExp) {
852
            // if not a property, or the value is null is null, excluded, or not included
853
            // then we are not going to expand it
854
            if (!static::hasProperty($k) || !isset($result[$k]) || !$result[$k]) {
855
                continue;
856
            }
857
858
            $subExc = array_value($namedExc, $k);
859
            $subInc = array_value($namedInc, $k);
860
861
            // convert exclude, include, and expand into dot notation
862
            // then take the keys for a flattened dot notation
863
            $flatExc = is_array($subExc) ? array_keys(array_dot($subExc)) : [];
864
            $flatInc = is_array($subInc) ? array_keys(array_dot($subInc)) : [];
865
            $flatExp = is_array($subExp) ? array_keys(array_dot($subExp)) : [];
866
867
            $relation = $this->relation($k);
868
            $result[$k] = $relation->toArrayDeprecated($flatExc, $flatInc, $flatExp);
869
        }
870
871
        return $result;
872
    }
873
874
    /**
875
     * Updates the model.
876
     *
877
     * @param array $data optional key-value properties to set
878
     *
879
     * @return bool
880
     */
881
    public function set(array $data = [])
882
    {
883
        if ($this->_id === false) {
884
            return false;
885
        }
886
887
        // not updating anything?
888
        if (count($data) == 0) {
889
            return true;
890
        }
891
892
        // apply mutators
893
        foreach ($data as $k => $value) {
894
            if ($mutator = self::getMutator($k)) {
895
                $data[$k] = $this->$mutator($value);
896
            }
897
        }
898
899
        // dispatch the model.updating event
900
        if (!$this->beforeUpdate($data)) {
901
            return false;
902
        }
903
904
        // validate the values being saved
905
        $validated = true;
906
        $updateArray = [];
907
        foreach ($data as $name => $value) {
908
            // exclude if value does not map to a property
909
            if (!isset(static::$properties[$name])) {
910
                continue;
911
            }
912
913
            $property = static::$properties[$name];
914
915
            // can only modify mutable properties
916
            if ($property['mutable'] != self::MUTABLE) {
917
                continue;
918
            }
919
920
            $validated = $validated && $this->filterAndValidate($property, $name, $value);
921
            $updateArray[$name] = $value;
922
        }
923
924
        if (!$validated) {
925
            return false;
926
        }
927
928
        $updated = self::$driver->updateModel($this, $updateArray);
929
930
        if ($updated) {
931
            // NOTE clear the local cache before the model.updated
932
            // event so that fetching values forces a reload
933
            // from the storage layer
934
            $this->clearCache();
935
936
            // dispatch the model.updated event
937
            if (!$this->afterUpdate()) {
938
                return false;
939
            }
940
        }
941
942
        return $updated;
943
    }
944
945
    /**
946
     * Delete the model.
947
     *
948
     * @return bool success
949
     */
950
    public function delete()
951
    {
952
        if ($this->_id === false) {
953
            return false;
954
        }
955
956
        // dispatch the model.deleting event
957
        if (!$this->beforeDelete()) {
958
            return false;
959
        }
960
961
        $deleted = self::$driver->deleteModel($this);
962
963
        if ($deleted) {
964
            // dispatch the model.deleted event
965
            if (!$this->afterDelete()) {
966
                return false;
967
            }
968
969
            // NOTE clear the local cache before the model.deleted
970
            // event so that fetching values forces a reload
971
            // from the storage layer
972
            $this->clearCache();
973
        }
974
975
        return $deleted;
976
    }
977
978
    /////////////////////////////
979
    // Queries
980
    /////////////////////////////
981
982
    /**
983
     * Generates a new query instance.
984
     *
985
     * @return Model\Query
986
     */
987
    public static function query()
988
    {
989
        // Create a new model instance for the query to ensure
990
        // that the model's initialize() method gets called.
991
        // Otherwise, the property definitions will be incomplete.
992
        $model = new static();
993
994
        return new Query($model);
995
    }
996
997
    /**
998
     * Gets the toal number of records matching an optional criteria.
999
     *
1000
     * @param array $where criteria
1001
     *
1002
     * @return int total
1003
     */
1004
    public static function totalRecords(array $where = [])
1005
    {
1006
        $query = static::query();
1007
        $query->where($where);
1008
1009
        return self::getDriver()->totalRecords($query);
1010
    }
1011
1012
    /**
1013
     * Checks if the model exists in the database.
1014
     *
1015
     * @return bool
1016
     */
1017
    public function exists()
1018
    {
1019
        return static::totalRecords($this->ids()) == 1;
1020
    }
1021
1022
    /**
1023
     * @deprecated alias for refresh()
1024
     */
1025
    public function load()
1026
    {
1027
        return $this->refresh();
1028
    }
1029
1030
    /**
1031
     * Loads the model from the storage layer.
1032
     *
1033
     * @return self
1034
     */
1035
    public function refresh()
1036
    {
1037
        if ($this->_id === false) {
1038
            return $this;
1039
        }
1040
1041
        $values = self::$driver->loadModel($this);
1042
1043
        if (!is_array($values)) {
1044
            return $this;
1045
        }
1046
1047
        // clear any relations
1048
        $this->_relationships = [];
1049
1050
        return $this->refreshWith($values);
1051
    }
1052
1053
    /**
1054
     * Loads values into the model.
1055
     *
1056
     * @param array $values values
1057
     *
1058
     * @return self
1059
     */
1060
    public function refreshWith(array $values)
1061
    {
1062
        $this->_values = $values;
1063
1064
        return $this;
1065
    }
1066
1067
    /**
1068
     * Clears the cache for this model.
1069
     *
1070
     * @return self
1071
     */
1072
    public function clearCache()
1073
    {
1074
        $this->_unsaved = [];
1075
        $this->_values = [];
1076
        $this->_relationships = [];
1077
1078
        return $this;
1079
    }
1080
1081
    /////////////////////////////
1082
    // Relationships
1083
    /////////////////////////////
1084
1085
    /**
1086
     * Gets the model object corresponding to a relation
1087
     * WARNING no check is used to see if the model returned actually exists.
1088
     *
1089
     * @param string $propertyName property
1090
     *
1091
     * @return \Pulsar\Model model
1092
     */
1093
    public function relation($propertyName)
1094
    {
1095
        // TODO deprecated
1096
        $property = static::getProperty($propertyName);
1097
1098
        if (!isset($this->_relationships[$propertyName])) {
1099
            $relationModelName = $property['relation'];
1100
            $this->_relationships[$propertyName] = new $relationModelName($this->$propertyName);
1101
        }
1102
1103
        return $this->_relationships[$propertyName];
1104
    }
1105
1106
    /**
1107
     * Creates the parent side of a One-To-One relationship.
1108
     *
1109
     * @param string $model      foreign model class
1110
     * @param string $foreignKey identifying key on foreign model
1111
     * @param string $localKey   identifying key on local model
1112
     *
1113
     * @return Relation
1114
     */
1115 View Code Duplication
    public function hasOne($model, $foreignKey = '', $localKey = '')
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...
1116
    {
1117
        // the default local key would look like `user_id`
1118
        // for a model named User
1119
        if (!$foreignKey) {
1120
            $inflector = Inflector::get();
1121
            $foreignKey = strtolower($inflector->underscore(static::modelName())).'_id';
1122
        }
1123
1124
        if (!$localKey) {
1125
            $localKey = self::DEFAULT_ID_PROPERTY;
1126
        }
1127
1128
        return new HasOne($model, $foreignKey, $localKey, $this);
1129
    }
1130
1131
    /**
1132
     * Creates the child side of a One-To-One or One-To-Many relationship.
1133
     *
1134
     * @param string $model      foreign model class
1135
     * @param string $foreignKey identifying key on foreign model
1136
     * @param string $localKey   identifying key on local model
1137
     *
1138
     * @return Relation
1139
     */
1140 View Code Duplication
    public function belongsTo($model, $foreignKey = '', $localKey = '')
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...
1141
    {
1142
        if (!$foreignKey) {
1143
            $foreignKey = self::DEFAULT_ID_PROPERTY;
1144
        }
1145
1146
        // the default local key would look like `user_id`
1147
        // for a model named User
1148
        if (!$localKey) {
1149
            $inflector = Inflector::get();
1150
            $localKey = strtolower($inflector->underscore($model::modelName())).'_id';
1151
        }
1152
1153
        return new BelongsTo($model, $foreignKey, $localKey, $this);
1154
    }
1155
1156
    /**
1157
     * Creates the parent side of a Many-To-One or Many-To-Many relationship.
1158
     *
1159
     * @param string $model      foreign model class
1160
     * @param string $foreignKey identifying key on foreign model
1161
     * @param string $localKey   identifying key on local model
1162
     *
1163
     * @return Relation
1164
     */
1165 View Code Duplication
    public function hasMany($model, $foreignKey = '', $localKey = '')
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...
1166
    {
1167
        // the default local key would look like `user_id`
1168
        // for a model named User
1169
        if (!$foreignKey) {
1170
            $inflector = Inflector::get();
1171
            $foreignKey = strtolower($inflector->underscore(static::modelName())).'_id';
1172
        }
1173
1174
        if (!$localKey) {
1175
            $localKey = self::DEFAULT_ID_PROPERTY;
1176
        }
1177
1178
        return new HasMany($model, $foreignKey, $localKey, $this);
1179
    }
1180
1181
    /**
1182
     * Creates the child side of a Many-To-Many relationship.
1183
     *
1184
     * @param string $model      foreign model class
1185
     * @param string $foreignKey identifying key on foreign model
1186
     * @param string $localKey   identifying key on local model
1187
     *
1188
     * @return Relation
1189
     */
1190 View Code Duplication
    public function belongsToMany($model, $foreignKey = '', $localKey = '')
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...
1191
    {
1192
        if (!$foreignKey) {
1193
            $foreignKey = self::DEFAULT_ID_PROPERTY;
1194
        }
1195
1196
        // the default local key would look like `user_id`
1197
        // for a model named User
1198
        if (!$localKey) {
1199
            $inflector = Inflector::get();
1200
            $localKey = strtolower($inflector->underscore($model::modelName())).'_id';
1201
        }
1202
1203
        return new BelongsToMany($model, $foreignKey, $localKey, $this);
1204
    }
1205
1206
    /////////////////////////////
1207
    // Events
1208
    /////////////////////////////
1209
1210
    /**
1211
     * Gets the event dispatcher.
1212
     *
1213
     * @return \Symfony\Component\EventDispatcher\EventDispatcher
1214
     */
1215
    public static function getDispatcher($ignoreCache = false)
1216
    {
1217
        $class = get_called_class();
1218
        if ($ignoreCache || !isset(self::$dispatchers[$class])) {
1219
            self::$dispatchers[$class] = new EventDispatcher();
1220
        }
1221
1222
        return self::$dispatchers[$class];
1223
    }
1224
1225
    /**
1226
     * Subscribes to a listener to an event.
1227
     *
1228
     * @param string   $event    event name
1229
     * @param callable $listener
1230
     * @param int      $priority optional priority, higher #s get called first
1231
     */
1232
    public static function listen($event, callable $listener, $priority = 0)
1233
    {
1234
        static::getDispatcher()->addListener($event, $listener, $priority);
1235
    }
1236
1237
    /**
1238
     * Adds a listener to the model.creating event.
1239
     *
1240
     * @param callable $listener
1241
     * @param int      $priority
1242
     */
1243
    public static function creating(callable $listener, $priority = 0)
1244
    {
1245
        static::listen(ModelEvent::CREATING, $listener, $priority);
1246
    }
1247
1248
    /**
1249
     * Adds a listener to the model.created event.
1250
     *
1251
     * @param callable $listener
1252
     * @param int      $priority
1253
     */
1254
    public static function created(callable $listener, $priority = 0)
1255
    {
1256
        static::listen(ModelEvent::CREATED, $listener, $priority);
1257
    }
1258
1259
    /**
1260
     * Adds a listener to the model.updating event.
1261
     *
1262
     * @param callable $listener
1263
     * @param int      $priority
1264
     */
1265
    public static function updating(callable $listener, $priority = 0)
1266
    {
1267
        static::listen(ModelEvent::UPDATING, $listener, $priority);
1268
    }
1269
1270
    /**
1271
     * Adds a listener to the model.updated event.
1272
     *
1273
     * @param callable $listener
1274
     * @param int      $priority
1275
     */
1276
    public static function updated(callable $listener, $priority = 0)
1277
    {
1278
        static::listen(ModelEvent::UPDATED, $listener, $priority);
1279
    }
1280
1281
    /**
1282
     * Adds a listener to the model.deleting event.
1283
     *
1284
     * @param callable $listener
1285
     * @param int      $priority
1286
     */
1287
    public static function deleting(callable $listener, $priority = 0)
1288
    {
1289
        static::listen(ModelEvent::DELETING, $listener, $priority);
1290
    }
1291
1292
    /**
1293
     * Adds a listener to the model.deleted event.
1294
     *
1295
     * @param callable $listener
1296
     * @param int      $priority
1297
     */
1298
    public static function deleted(callable $listener, $priority = 0)
1299
    {
1300
        static::listen(ModelEvent::DELETED, $listener, $priority);
1301
    }
1302
1303
    /**
1304
     * Dispatches an event.
1305
     *
1306
     * @param string $eventName
1307
     *
1308
     * @return Model\ModelEvent
1309
     */
1310
    protected function dispatch($eventName)
1311
    {
1312
        $event = new ModelEvent($this);
1313
1314
        return static::getDispatcher()->dispatch($eventName, $event);
1315
    }
1316
1317
    /**
1318
     * Dispatches the model.creating event.
1319
     *
1320
     * @return bool
1321
     */
1322 View Code Duplication
    private function beforeCreate()
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...
1323
    {
1324
        $event = $this->dispatch(ModelEvent::CREATING);
1325
        if ($event->isPropagationStopped()) {
1326
            return false;
1327
        }
1328
1329
        // TODO deprecated
1330
        if (method_exists($this, 'preCreateHook') && !$this->preCreateHook($this->_unsaved)) {
0 ignored issues
show
Bug introduced by
The method preCreateHook() does not exist on Pulsar\Model. Did you maybe mean create()?

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...
1331
            return false;
1332
        }
1333
1334
        return true;
1335
    }
1336
1337
    /**
1338
     * Dispatches the model.created event.
1339
     *
1340
     * @return bool
1341
     */
1342 View Code Duplication
    private function afterCreate()
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...
1343
    {
1344
        $event = $this->dispatch(ModelEvent::CREATED);
1345
        if ($event->isPropagationStopped()) {
1346
            return false;
1347
        }
1348
1349
        // TODO deprecated
1350
        if (method_exists($this, 'postCreateHook') && $this->postCreateHook() === false) {
0 ignored issues
show
Bug introduced by
The method postCreateHook() does not exist on Pulsar\Model. Did you maybe mean create()?

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...
1351
            return false;
1352
        }
1353
1354
        return true;
1355
    }
1356
1357
    /**
1358
     * Dispatches the model.updating event.
1359
     *
1360
     * @param array $data
1361
     *
1362
     * @return bool
1363
     */
1364 View Code Duplication
    private function beforeUpdate(array &$data)
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...
1365
    {
1366
        $event = $this->dispatch(ModelEvent::UPDATING);
1367
        if ($event->isPropagationStopped()) {
1368
            return false;
1369
        }
1370
1371
        // TODO deprecated
1372
        if (method_exists($this, 'preSetHook') && !$this->preSetHook($data)) {
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...
1373
            return false;
1374
        }
1375
1376
        return true;
1377
    }
1378
1379
    /**
1380
     * Dispatches the model.updated event.
1381
     *
1382
     * @return bool
1383
     */
1384 View Code Duplication
    private function afterUpdate()
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...
1385
    {
1386
        $event = $this->dispatch(ModelEvent::UPDATED);
1387
        if ($event->isPropagationStopped()) {
1388
            return false;
1389
        }
1390
1391
        // TODO deprecated
1392
        if (method_exists($this, 'postSetHook') && $this->postSetHook() === false) {
0 ignored issues
show
Bug introduced by
The method postSetHook() 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...
1393
            return false;
1394
        }
1395
1396
        return true;
1397
    }
1398
1399
    /**
1400
     * Dispatches the model.deleting event.
1401
     *
1402
     * @return bool
1403
     */
1404 View Code Duplication
    private function beforeDelete()
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...
1405
    {
1406
        $event = $this->dispatch(ModelEvent::DELETING);
1407
        if ($event->isPropagationStopped()) {
1408
            return false;
1409
        }
1410
1411
        // TODO deprecated
1412
        if (method_exists($this, 'preDeleteHook') && !$this->preDeleteHook()) {
0 ignored issues
show
Bug introduced by
The method preDeleteHook() does not exist on Pulsar\Model. Did you maybe mean delete()?

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...
1413
            return false;
1414
        }
1415
1416
        return true;
1417
    }
1418
1419
    /**
1420
     * Dispatches the model.created event.
1421
     *
1422
     * @return bool
1423
     */
1424 View Code Duplication
    private function afterDelete()
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...
1425
    {
1426
        $event = $this->dispatch(ModelEvent::DELETED);
1427
        if ($event->isPropagationStopped()) {
1428
            return false;
1429
        }
1430
1431
        // TODO deprecated
1432
        if (method_exists($this, 'postDeleteHook') && $this->postDeleteHook() === false) {
0 ignored issues
show
Bug introduced by
The method postDeleteHook() does not exist on Pulsar\Model. Did you maybe mean delete()?

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...
1433
            return false;
1434
        }
1435
1436
        return true;
1437
    }
1438
1439
    /////////////////////////////
1440
    // Validation
1441
    /////////////////////////////
1442
1443
    /**
1444
     * Validates and marshals a value to storage.
1445
     *
1446
     * @param array  $property
1447
     * @param string $propertyName
1448
     * @param mixed  $value
1449
     *
1450
     * @return bool
1451
     */
1452
    private function filterAndValidate(array $property, $propertyName, &$value)
1453
    {
1454
        // assume empty string is a null value for properties
1455
        // that are marked as optionally-null
1456
        if ($property['null'] && empty($value)) {
1457
            $value = null;
1458
1459
            return true;
1460
        }
1461
1462
        // validate
1463
        list($valid, $value) = $this->validate($property, $propertyName, $value);
1464
1465
        // unique?
1466
        if ($valid && $property['unique'] && ($this->_id === false || $value != $this->ignoreUnsaved()->$propertyName)) {
1467
            $valid = $this->checkUniqueness($property, $propertyName, $value);
1468
        }
1469
1470
        return $valid;
1471
    }
1472
1473
    /**
1474
     * Validates a value for a property.
1475
     *
1476
     * @param array  $property
1477
     * @param string $propertyName
1478
     * @param mixed  $value
1479
     *
1480
     * @return bool
1481
     */
1482
    private function validate(array $property, $propertyName, $value)
1483
    {
1484
        $valid = true;
1485
1486
        if (isset($property['validate']) && is_callable($property['validate'])) {
1487
            $valid = call_user_func_array($property['validate'], [$value]);
1488
        } elseif (isset($property['validate'])) {
1489
            $valid = Validate::is($value, $property['validate']);
1490
        }
1491
1492
        if (!$valid) {
1493
            $this->app['errors']->push([
1494
                'error' => self::ERROR_VALIDATION_FAILED,
1495
                'params' => [
1496
                    'field' => $propertyName,
1497
                    'field_name' => (isset($property['title'])) ? $property['title'] : Inflector::get()->titleize($propertyName), ], ]);
1498
        }
1499
1500
        return [$valid, $value];
1501
    }
1502
1503
    /**
1504
     * Checks if a value is unique for a property.
1505
     *
1506
     * @param array  $property
1507
     * @param string $propertyName
1508
     * @param mixed  $value
1509
     *
1510
     * @return bool
1511
     */
1512
    private function checkUniqueness(array $property, $propertyName, $value)
1513
    {
1514
        if (static::totalRecords([$propertyName => $value]) > 0) {
1515
            $this->app['errors']->push([
1516
                'error' => self::ERROR_NOT_UNIQUE,
1517
                'params' => [
1518
                    'field' => $propertyName,
1519
                    'field_name' => (isset($property['title'])) ? $property['title'] : Inflector::get()->titleize($propertyName), ], ]);
1520
1521
            return false;
1522
        }
1523
1524
        return true;
1525
    }
1526
1527
    /**
1528
     * Gets the marshaled default value for a property (if set).
1529
     *
1530
     * @param string $property
1531
     *
1532
     * @return mixed
1533
     */
1534
    private function getPropertyDefault(array $property)
1535
    {
1536
        return array_value($property, 'default');
1537
    }
1538
}
1539