Completed
Push — master ( a3416c...a17b49 )
by Jared
02:25
created

Model   F

Complexity

Total Complexity 181

Size/Duplication

Total Lines 1523
Duplicated Lines 5.19 %

Coupling/Cohesion

Components 1
Dependencies 15

Importance

Changes 0
Metric Value
wmc 181
lcom 1
cbo 15
dl 79
loc 1523
rs 0.5217
c 0
b 0
f 0

73 Methods

Rating   Name   Duplication   Size   Complexity  
A getMutator() 18 18 3
A getAccessor() 18 18 3
C __construct() 0 40 8
A init() 0 9 2
B initialize() 0 25 5
A installAutoTimestamps() 0 14 1
A inject() 0 4 1
A getApp() 0 4 1
A setDriver() 0 4 1
A getDriver() 0 8 2
A clearDriver() 0 4 1
A modelName() 0 7 1
A id() 0 4 1
A ids() 0 4 1
A setErrorStack() 0 4 1
A clearErrorStack() 0 4 1
A __toString() 0 4 1
A __get() 0 6 1
A __set() 0 15 3
A __isset() 0 4 2
A __unset() 0 11 3
A offsetExists() 0 4 1
A offsetGet() 0 4 1
A offsetSet() 0 4 1
A offsetUnset() 0 4 1
A __callStatic() 0 7 1
A getProperties() 0 4 1
A getProperty() 0 4 1
A getIDProperties() 0 4 1
A hasProperty() 0 4 1
B cast() 0 20 5
A getTablename() 0 6 1
A save() 0 8 2
A saveOrFail() 0 6 2
F create() 15 86 18
A ignoreUnsaved() 0 6 1
C get() 0 45 8
A getNewID() 0 20 4
B setValues() 0 20 9
B toArray() 0 31 6
C set() 12 65 13
B delete() 0 24 5
A query() 0 9 1
A find() 0 10 2
A findOrFail() 0 9 2
A exists() 0 4 1
A persisted() 0 4 1
A refresh() 0 17 3
A refreshWith() 0 7 1
A clearCache() 0 8 1
A relation() 0 15 3
A setRelation() 0 7 1
A hasOne() 0 4 1
A belongsTo() 0 4 1
A hasMany() 0 4 1
A belongsToMany() 0 4 1
A getDispatcher() 0 9 3
A listen() 0 4 1
A saving() 0 5 1
A saved() 0 5 1
A creating() 0 4 1
A created() 0 4 1
A updating() 0 4 1
A updated() 0 4 1
A deleting() 0 4 1
A deleted() 0 4 1
A dispatch() 0 6 1
A handleDispatch() 0 6 1
A getErrors() 0 10 4
B filterAndValidate() 0 20 7
B validate() 7 20 6
A checkUniqueness() 9 15 3
A getPropertyDefault() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Model often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Model, and based on these observations, apply Extract Interface, too.

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 $initialized = [];
163
164
    /**
165
     * @var DriverInterface
166
     */
167
    private static $driver;
168
169
    /**
170
     * @var array
171
     */
172
    private static $accessors = [];
173
174
    /**
175
     * @var array
176
     */
177
    private static $mutators = [];
178
179
    /**
180
     * @var bool
181
     */
182
    private $_ignoreUnsaved;
183
184
    /**
185
     * Creates a new model object.
186
     *
187
     * @param array|string|Model|false $id     ordered array of ids or comma-separated id string
188
     * @param array                    $values optional key-value map to pre-seed model
189
     */
190
    public function __construct($id = false, array $values = [])
191
    {
192
        // initialize the model
193
        $this->init();
194
195
        // parse the supplied model ID
196
        if (is_array($id)) {
197
            // A model can be supplied as a primary key
198
            foreach ($id as &$el) {
199
                if ($el instanceof self) {
200
                    $el = $el->id();
201
                }
202
            }
203
204
            // The IDs come in as the same order as ::$ids.
205
            // We need to match up the elements on that
206
            // input into a key-value map for each ID property.
207
            $ids = [];
208
            $idQueue = array_reverse($id);
209
            foreach (static::$ids as $k => $f) {
210
                $ids[$f] = (count($idQueue) > 0) ? array_pop($idQueue) : false;
211
            }
212
213
            $this->_id = implode(',', $id);
214
            $this->_ids = $ids;
215
        } elseif ($id instanceof self) {
216
            // A model can be supplied as a primary key
217
            $this->_id = $id->id();
218
            $this->_ids = $id->ids();
219
        } else {
220
            $this->_id = $id;
221
            $idProperty = static::$ids[0];
222
            $this->_ids = [$idProperty => $id];
223
        }
224
225
        // load any given values
226
        if (count($values) > 0) {
227
            $this->refreshWith($values);
228
        }
229
    }
230
231
    /**
232
     * Performs initialization on this model.
233
     */
234
    private function init()
235
    {
236
        // ensure the initialize function is called only once
237
        $k = get_called_class();
238
        if (!isset(self::$initialized[$k])) {
239
            $this->initialize();
240
            self::$initialized[$k] = true;
241
        }
242
    }
243
244
    /**
245
     * The initialize() method is called once per model. It's used
246
     * to perform any one-off tasks before the model gets
247
     * constructed. This is a great place to add any model
248
     * properties. When extending this method be sure to call
249
     * parent::initialize() as some important stuff happens here.
250
     * If extending this method to add properties then you should
251
     * call parent::initialize() after adding any properties.
252
     */
253
    protected function initialize()
254
    {
255
        // load the driver
256
        static::getDriver();
257
258
        // add in the default ID property
259
        if (static::$ids == [self::DEFAULT_ID_PROPERTY] && !isset(static::$properties[self::DEFAULT_ID_PROPERTY])) {
260
            static::$properties[self::DEFAULT_ID_PROPERTY] = self::$defaultIDProperty;
261
        }
262
263
        // generates created_at and updated_at timestamps
264
        if (property_exists($this, 'autoTimestamps')) {
265
            $this->installAutoTimestamps();
266
        }
267
268
        // fill in each property by extending the property
269
        // definition base
270
        foreach (static::$properties as &$property) {
271
            $property = array_replace(self::$propertyDefinitionBase, $property);
272
        }
273
274
        // order the properties array by name for consistency
275
        // since it is constructed in a random order
276
        ksort(static::$properties);
277
    }
278
279
    /**
280
     * Installs the `created_at` and `updated_at` properties.
281
     */
282
    private function installAutoTimestamps()
283
    {
284
        static::$properties = array_replace(self::$timestampProperties, static::$properties);
285
286
        self::creating(function (ModelEvent $event) {
287
            $model = $event->getModel();
288
            $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...
289
            $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...
290
        });
291
292
        self::updating(function (ModelEvent $event) {
293
            $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...
294
        });
295
    }
296
297
    /**
298
     * @deprecated
299
     *
300
     * Injects a DI container
301
     *
302
     * @param Container $container
303
     */
304
    public static function inject(Container $container)
305
    {
306
        self::$globalContainer = $container;
307
    }
308
309
    /**
310
     * @deprecated
311
     *
312
     * Gets the DI container used for this model
313
     *
314
     * @return Container|null
315
     */
316
    public function getApp()
317
    {
318
        return self::$globalContainer;
319
    }
320
321
    /**
322
     * Sets the driver for all models.
323
     *
324
     * @param DriverInterface $driver
325
     */
326
    public static function setDriver(DriverInterface $driver)
327
    {
328
        self::$driver = $driver;
329
    }
330
331
    /**
332
     * Gets the driver for all models.
333
     *
334
     * @return DriverInterface
335
     *
336
     * @throws DriverMissingException when a driver has not been set yet
337
     */
338
    public static function getDriver()
339
    {
340
        if (!self::$driver) {
341
            throw new DriverMissingException('A model driver has not been set yet.');
342
        }
343
344
        return self::$driver;
345
    }
346
347
    /**
348
     * Clears the driver for all models.
349
     */
350
    public static function clearDriver()
351
    {
352
        self::$driver = null;
353
    }
354
355
    /**
356
     * Gets the name of the model, i.e. User.
357
     *
358
     * @return string
359
     */
360
    public static function modelName()
361
    {
362
        // strip namespacing
363
        $paths = explode('\\', get_called_class());
364
365
        return end($paths);
366
    }
367
368
    /**
369
     * Gets the model ID.
370
     *
371
     * @return string|number|false ID
372
     */
373
    public function id()
374
    {
375
        return $this->_id;
376
    }
377
378
    /**
379
     * Gets a key-value map of the model ID.
380
     *
381
     * @return array ID map
382
     */
383
    public function ids()
384
    {
385
        return $this->_ids;
386
    }
387
388
    /**
389
     * Sets the global error stack instance.
390
     *
391
     * @param ErrorStack $stack
392
     */
393
    public static function setErrorStack(ErrorStack $stack)
394
    {
395
        self::$globalErrorStack = $stack;
396
    }
397
398
    /**
399
     * Clears the global error stack instance.
400
     */
401
    public static function clearErrorStack()
402
    {
403
        self::$globalErrorStack = null;
404
    }
405
406
    /////////////////////////////
407
    // Magic Methods
408
    /////////////////////////////
409
410
    /**
411
     * Converts the model into a string.
412
     *
413
     * @return string
414
     */
415
    public function __toString()
416
    {
417
        return get_called_class().'('.$this->_id.')';
418
    }
419
420
    /**
421
     * Shortcut to a get() call for a given property.
422
     *
423
     * @param string $name
424
     *
425
     * @return mixed
426
     */
427
    public function __get($name)
428
    {
429
        $result = $this->get([$name]);
430
431
        return reset($result);
432
    }
433
434
    /**
435
     * Sets an unsaved value.
436
     *
437
     * @param string $name
438
     * @param mixed  $value
439
     */
440
    public function __set($name, $value)
441
    {
442
        // if changing property, remove relation model
443
        if (isset($this->_relationships[$name])) {
444
            unset($this->_relationships[$name]);
445
        }
446
447
        // call any mutators
448
        $mutator = self::getMutator($name);
449
        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...
450
            $this->_unsaved[$name] = $this->$mutator($value);
451
        } else {
452
            $this->_unsaved[$name] = $value;
453
        }
454
    }
455
456
    /**
457
     * Checks if an unsaved value or property exists by this name.
458
     *
459
     * @param string $name
460
     *
461
     * @return bool
462
     */
463
    public function __isset($name)
464
    {
465
        return array_key_exists($name, $this->_unsaved) || static::hasProperty($name);
466
    }
467
468
    /**
469
     * Unsets an unsaved value.
470
     *
471
     * @param string $name
472
     */
473
    public function __unset($name)
474
    {
475
        if (array_key_exists($name, $this->_unsaved)) {
476
            // if changing property, remove relation model
477
            if (isset($this->_relationships[$name])) {
478
                unset($this->_relationships[$name]);
479
            }
480
481
            unset($this->_unsaved[$name]);
482
        }
483
    }
484
485
    /////////////////////////////
486
    // ArrayAccess Interface
487
    /////////////////////////////
488
489
    public function offsetExists($offset)
490
    {
491
        return isset($this->$offset);
492
    }
493
494
    public function offsetGet($offset)
495
    {
496
        return $this->$offset;
497
    }
498
499
    public function offsetSet($offset, $value)
500
    {
501
        $this->$offset = $value;
502
    }
503
504
    public function offsetUnset($offset)
505
    {
506
        unset($this->$offset);
507
    }
508
509
    public static function __callStatic($name, $parameters)
510
    {
511
        // Any calls to unkown static methods should be deferred to
512
        // the query. This allows calls like User::where()
513
        // 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...
514
        return call_user_func_array([static::query(), $name], $parameters);
515
    }
516
517
    /////////////////////////////
518
    // Property Definitions
519
    /////////////////////////////
520
521
    /**
522
     * Gets all the property definitions for the model.
523
     *
524
     * @return array key-value map of properties
525
     */
526
    public static function getProperties()
527
    {
528
        return static::$properties;
529
    }
530
531
    /**
532
     * Gets a property defition for the model.
533
     *
534
     * @param string $property property to lookup
535
     *
536
     * @return array|null property
537
     */
538
    public static function getProperty($property)
539
    {
540
        return array_value(static::$properties, $property);
541
    }
542
543
    /**
544
     * Gets the names of the model ID properties.
545
     *
546
     * @return array
547
     */
548
    public static function getIDProperties()
549
    {
550
        return static::$ids;
551
    }
552
553
    /**
554
     * Checks if the model has a property.
555
     *
556
     * @param string $property property
557
     *
558
     * @return bool has property
559
     */
560
    public static function hasProperty($property)
561
    {
562
        return isset(static::$properties[$property]);
563
    }
564
565
    /**
566
     * Gets the mutator method name for a given proeprty name.
567
     * Looks for methods in the form of `setPropertyValue`.
568
     * i.e. the mutator for `last_name` would be `setLastNameValue`.
569
     *
570
     * @param string $property property
571
     *
572
     * @return string|false method name if it exists
573
     */
574 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...
575
    {
576
        $class = get_called_class();
577
578
        $k = $class.':'.$property;
579
        if (!array_key_exists($k, self::$mutators)) {
580
            $inflector = Inflector::get();
581
            $method = 'set'.$inflector->camelize($property).'Value';
582
583
            if (!method_exists($class, $method)) {
584
                $method = false;
585
            }
586
587
            self::$mutators[$k] = $method;
588
        }
589
590
        return self::$mutators[$k];
591
    }
592
593
    /**
594
     * Gets the accessor method name for a given proeprty name.
595
     * Looks for methods in the form of `getPropertyValue`.
596
     * i.e. the accessor for `last_name` would be `getLastNameValue`.
597
     *
598
     * @param string $property property
599
     *
600
     * @return string|false method name if it exists
601
     */
602 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...
603
    {
604
        $class = get_called_class();
605
606
        $k = $class.':'.$property;
607
        if (!array_key_exists($k, self::$accessors)) {
608
            $inflector = Inflector::get();
609
            $method = 'get'.$inflector->camelize($property).'Value';
610
611
            if (!method_exists($class, $method)) {
612
                $method = false;
613
            }
614
615
            self::$accessors[$k] = $method;
616
        }
617
618
        return self::$accessors[$k];
619
    }
620
621
    /**
622
     * Marshals a value for a given property from storage.
623
     *
624
     * @param array $property
625
     * @param mixed $value
626
     *
627
     * @return mixed type-casted value
628
     */
629
    public static function cast(array $property, $value)
630
    {
631
        if ($value === null) {
632
            return;
633
        }
634
635
        // handle empty strings as null
636
        if ($property['null'] && $value == '') {
637
            return;
638
        }
639
640
        $type = array_value($property, 'type');
641
        $m = 'to_'.$type;
642
643
        if (!method_exists(Property::class, $m)) {
644
            return $value;
645
        }
646
647
        return Property::$m($value);
648
    }
649
650
    /////////////////////////////
651
    // CRUD Operations
652
    /////////////////////////////
653
654
    /**
655
     * Gets the tablename for storing this model.
656
     *
657
     * @return string
658
     */
659
    public function getTablename()
660
    {
661
        $inflector = Inflector::get();
662
663
        return $inflector->camelize($inflector->pluralize(static::modelName()));
664
    }
665
666
    /**
667
     * Saves the model.
668
     *
669
     * @return bool true when the operation was successful
670
     */
671
    public function save()
672
    {
673
        if ($this->_id === false) {
674
            return $this->create();
675
        }
676
677
        return $this->set();
678
    }
679
680
    /**
681
     * Saves the model. Throws an exception when the operation fails.
682
     *
683
     * @throws ModelException when the model cannot be saved
684
     */
685
    public function saveOrFail()
686
    {
687
        if (!$this->save()) {
688
            throw new ModelException('Failed to save '.static::modelName());
689
        }
690
    }
691
692
    /**
693
     * Creates a new model.
694
     *
695
     * @param array $data optional key-value properties to set
696
     *
697
     * @return bool true when the operation was successful
698
     *
699
     * @throws BadMethodCallException when called on an existing model
700
     */
701
    public function create(array $data = [])
702
    {
703
        if ($this->_id !== false) {
704
            throw new BadMethodCallException('Cannot call create() on an existing model');
705
        }
706
707
        // mass assign values passed into create()
708
        $this->setValues($data);
709
710
        // dispatch the model.creating event
711
        if (!$this->handleDispatch(ModelEvent::CREATING)) {
712
            return false;
713
        }
714
715
        $requiredProperties = [];
716
        foreach (static::$properties as $name => $property) {
717
            // build a list of the required properties
718
            if ($property['required']) {
719
                $requiredProperties[] = $name;
720
            }
721
722
            // add in default values
723
            if (!array_key_exists($name, $this->_unsaved) && array_key_exists('default', $property)) {
724
                $this->_unsaved[$name] = $property['default'];
725
            }
726
        }
727
728
        // validate the values being saved
729
        $validated = true;
730
        $insertArray = [];
731
        foreach ($this->_unsaved as $name => $value) {
732
            // exclude if value does not map to a property
733
            if (!isset(static::$properties[$name])) {
734
                continue;
735
            }
736
737
            $property = static::$properties[$name];
738
739
            // cannot insert immutable values
740
            // (unless using the default value)
741
            if ($property['mutable'] == self::IMMUTABLE && $value !== $this->getPropertyDefault($property)) {
742
                continue;
743
            }
744
745
            $validated = $validated && $this->filterAndValidate($property, $name, $value);
746
            $insertArray[$name] = $value;
747
        }
748
749
        // check for required fields
750
        foreach ($requiredProperties as $name) {
751
            if (!isset($insertArray[$name])) {
752
                $property = static::$properties[$name];
753
                $this->getErrors()->push([
754
                    'error' => self::ERROR_REQUIRED_FIELD_MISSING,
755
                    'params' => [
756
                        'field' => $name,
757
                        'field_name' => (isset($property['title'])) ? $property['title'] : Inflector::get()->titleize($name), ], ]);
758
759
                $validated = false;
760
            }
761
        }
762
763
        if (!$validated) {
764
            return false;
765
        }
766
767
        $created = self::$driver->createModel($this, $insertArray);
768
769 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...
770
            // determine the model's new ID
771
            $this->getNewID();
772
773
            // NOTE clear the local cache before the model.created
774
            // event so that fetching values forces a reload
775
            // from the storage layer
776
            $this->clearCache();
777
            $this->_persisted = true;
778
779
            // dispatch the model.created event
780
            if (!$this->handleDispatch(ModelEvent::CREATED)) {
781
                return false;
782
            }
783
        }
784
785
        return $created;
786
    }
787
788
    /**
789
     * Ignores unsaved values when fetching the next value.
790
     *
791
     * @return self
792
     */
793
    public function ignoreUnsaved()
794
    {
795
        $this->_ignoreUnsaved = true;
796
797
        return $this;
798
    }
799
800
    /**
801
     * Fetches property values from the model.
802
     *
803
     * This method looks up values in this order:
804
     * IDs, local cache, unsaved values, storage layer, defaults
805
     *
806
     * @param array $properties list of property names to fetch values of
807
     *
808
     * @return array
809
     */
810
    public function get(array $properties)
811
    {
812
        // load the values from the IDs and local model cache
813
        $values = array_replace($this->ids(), $this->_values);
814
815
        // unless specified, use any unsaved values
816
        $ignoreUnsaved = $this->_ignoreUnsaved;
817
        $this->_ignoreUnsaved = false;
818
819
        if (!$ignoreUnsaved) {
820
            $values = array_replace($values, $this->_unsaved);
821
        }
822
823
        // attempt to load any missing values from the storage layer
824
        $numMissing = count(array_diff($properties, array_keys($values)));
825
        if ($numMissing > 0) {
826
            $this->refresh();
827
            $values = array_replace($values, $this->_values);
828
829
            if (!$ignoreUnsaved) {
830
                $values = array_replace($values, $this->_unsaved);
831
            }
832
        }
833
834
        // build a key-value map of the requested properties
835
        $return = [];
836
        foreach ($properties as $k) {
837
            if (array_key_exists($k, $values)) {
838
                $return[$k] = $values[$k];
839
            // set any missing values to the default value
840
            } elseif (static::hasProperty($k)) {
841
                $return[$k] = $this->_values[$k] = $this->getPropertyDefault(static::$properties[$k]);
842
            // use null for values of non-properties
843
            } else {
844
                $return[$k] = null;
845
            }
846
847
            // call any accessors
848
            if ($accessor = self::getAccessor($k)) {
849
                $return[$k] = $this->$accessor($return[$k]);
850
            }
851
        }
852
853
        return $return;
854
    }
855
856
    /**
857
     * Populates a newly created model with its ID.
858
     */
859
    protected function getNewID()
860
    {
861
        $ids = [];
862
        $namedIds = [];
863
        foreach (static::$ids as $k) {
864
            // attempt use the supplied value if the ID property is mutable
865
            $property = static::getProperty($k);
866
            if (in_array($property['mutable'], [self::MUTABLE, self::MUTABLE_CREATE_ONLY]) && isset($this->_unsaved[$k])) {
867
                $id = $this->_unsaved[$k];
868
            } else {
869
                $id = self::$driver->getCreatedID($this, $k);
870
            }
871
872
            $ids[] = $id;
873
            $namedIds[$k] = $id;
874
        }
875
876
        $this->_id = implode(',', $ids);
877
        $this->_ids = $namedIds;
878
    }
879
880
    /**
881
     * Sets a collection values on the model from an untrusted input.
882
     *
883
     * @param array $values
884
     *
885
     * @throws MassAssignmentException when assigning a value that is protected or not whitelisted
886
     *
887
     * @return self
888
     */
889
    public function setValues($values)
890
    {
891
        // check if the model has a mass assignment whitelist
892
        $permitted = (property_exists($this, 'permitted')) ? static::$permitted : false;
893
894
        // if no whitelist, then check for a blacklist
895
        $protected = (!is_array($permitted) && property_exists($this, 'protected')) ? static::$protected : false;
896
897
        foreach ($values as $k => $value) {
898
            // check for mass assignment violations
899
            if (($permitted && !in_array($k, $permitted)) ||
900
                ($protected && in_array($k, $protected))) {
901
                throw new MassAssignmentException("Mass assignment of $k on ".static::modelName().' is not allowed');
902
            }
903
904
            $this->$k = $value;
905
        }
906
907
        return $this;
908
    }
909
910
    /**
911
     * Converts the model to an array.
912
     *
913
     * @return array
914
     */
915
    public function toArray()
916
    {
917
        // build the list of properties to retrieve
918
        $properties = array_keys(static::$properties);
919
920
        // remove any hidden properties
921
        $hide = (property_exists($this, 'hidden')) ? static::$hidden : [];
922
        $properties = array_diff($properties, $hide);
923
924
        // add any appended properties
925
        $append = (property_exists($this, 'appended')) ? static::$appended : [];
926
        $properties = array_merge($properties, $append);
927
928
        // get the values for the properties
929
        $result = $this->get($properties);
930
931
        foreach ($result as $k => &$value) {
932
            // convert any models to arrays
933
            if ($value instanceof self) {
934
                $value = $value->toArray();
935
            }
936
        }
937
938
        // DEPRECATED
939
        // apply the transformation hook
940
        if (method_exists($this, 'toArrayHook')) {
941
            $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...
942
        }
943
944
        return $result;
945
    }
946
947
    /**
948
     * Updates the model.
949
     *
950
     * @param array $data optional key-value properties to set
951
     *
952
     * @return bool true when the operation was successful
953
     *
954
     * @throws BadMethodCallException when not called on an existing model
955
     */
956
    public function set(array $data = [])
957
    {
958
        if ($this->_id === false) {
959
            throw new BadMethodCallException('Can only call set() on an existing model');
960
        }
961
962
        // mass assign values passed into set()
963
        $this->setValues($data);
964
965
        // not updating anything?
966
        if (count($this->_unsaved) == 0) {
967
            return true;
968
        }
969
970
        // dispatch the model.updating event
971
        if (!$this->handleDispatch(ModelEvent::UPDATING)) {
972
            return false;
973
        }
974
975
        // DEPRECATED
976
        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...
977
            return false;
978
        }
979
980
        // validate the values being saved
981
        $validated = true;
982
        $updateArray = [];
983
        foreach ($this->_unsaved as $name => $value) {
984
            // exclude if value does not map to a property
985
            if (!isset(static::$properties[$name])) {
986
                continue;
987
            }
988
989
            $property = static::$properties[$name];
990
991
            // can only modify mutable properties
992
            if ($property['mutable'] != self::MUTABLE) {
993
                continue;
994
            }
995
996
            $validated = $validated && $this->filterAndValidate($property, $name, $value);
997
            $updateArray[$name] = $value;
998
        }
999
1000
        if (!$validated) {
1001
            return false;
1002
        }
1003
1004
        $updated = self::$driver->updateModel($this, $updateArray);
1005
1006 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...
1007
            // NOTE clear the local cache before the model.updated
1008
            // event so that fetching values forces a reload
1009
            // from the storage layer
1010
            $this->clearCache();
1011
            $this->_persisted = true;
1012
1013
            // dispatch the model.updated event
1014
            if (!$this->handleDispatch(ModelEvent::UPDATED)) {
1015
                return false;
1016
            }
1017
        }
1018
1019
        return $updated;
1020
    }
1021
1022
    /**
1023
     * Delete the model.
1024
     *
1025
     * @return bool true when the operation was successful
1026
     */
1027
    public function delete()
1028
    {
1029
        if ($this->_id === false) {
1030
            throw new BadMethodCallException('Can only call delete() on an existing model');
1031
        }
1032
1033
        // dispatch the model.deleting event
1034
        if (!$this->handleDispatch(ModelEvent::DELETING)) {
1035
            return false;
1036
        }
1037
1038
        $deleted = self::$driver->deleteModel($this);
1039
1040
        if ($deleted) {
1041
            // dispatch the model.deleted event
1042
            if (!$this->handleDispatch(ModelEvent::DELETED)) {
1043
                return false;
1044
            }
1045
1046
            $this->_persisted = false;
1047
        }
1048
1049
        return $deleted;
1050
    }
1051
1052
    /////////////////////////////
1053
    // Queries
1054
    /////////////////////////////
1055
1056
    /**
1057
     * Generates a new query instance.
1058
     *
1059
     * @return Query
1060
     */
1061
    public static function query()
1062
    {
1063
        // Create a new model instance for the query to ensure
1064
        // that the model's initialize() method gets called.
1065
        // Otherwise, the property definitions will be incomplete.
1066
        $model = new static();
1067
1068
        return new Query($model);
1069
    }
1070
1071
    /**
1072
     * Finds a single instance of a model given it's ID.
1073
     *
1074
     * @param mixed $id
1075
     *
1076
     * @return Model|null
1077
     */
1078
    public static function find($id)
1079
    {
1080
        $ids = [];
1081
        $id = (array) $id;
1082
        foreach (static::$ids as $j => $k) {
1083
            $ids[$k] = $id[$j];
1084
        }
1085
1086
        return static::where($ids)->first();
1087
    }
1088
1089
    /**
1090
     * Finds a single instance of a model given it's ID or throws an exception.
1091
     *
1092
     * @param mixed $id
1093
     *
1094
     * @return Model
1095
     *
1096
     * @throws ModelNotFoundException when a model could not be found
1097
     */
1098
    public static function findOrFail($id)
1099
    {
1100
        $model = static::find($id);
1101
        if (!$model) {
1102
            throw new ModelNotFoundException('Could not find the requested '.static::modelName());
1103
        }
1104
1105
        return $model;
1106
    }
1107
1108
    /**
1109
     * @deprecated
1110
     *
1111
     * Checks if the model exists in the database
1112
     *
1113
     * @return bool
1114
     */
1115
    public function exists()
1116
    {
1117
        return static::where($this->ids())->count() == 1;
1118
    }
1119
1120
    /**
1121
     * Tells if this model instance has been persisted to the data layer.
1122
     *
1123
     * NOTE: this does not actually perform a check with the data layer
1124
     *
1125
     * @return bool
1126
     */
1127
    public function persisted()
1128
    {
1129
        return $this->_persisted;
1130
    }
1131
1132
    /**
1133
     * Loads the model from the storage layer.
1134
     *
1135
     * @return self
1136
     */
1137
    public function refresh()
1138
    {
1139
        if ($this->_id === false) {
1140
            return $this;
1141
        }
1142
1143
        $values = self::$driver->loadModel($this);
1144
1145
        if (!is_array($values)) {
1146
            return $this;
1147
        }
1148
1149
        // clear any relations
1150
        $this->_relationships = [];
1151
1152
        return $this->refreshWith($values);
1153
    }
1154
1155
    /**
1156
     * Loads values into the model.
1157
     *
1158
     * @param array $values values
1159
     *
1160
     * @return self
1161
     */
1162
    public function refreshWith(array $values)
1163
    {
1164
        $this->_persisted = true;
1165
        $this->_values = $values;
1166
1167
        return $this;
1168
    }
1169
1170
    /**
1171
     * Clears the cache for this model.
1172
     *
1173
     * @return self
1174
     */
1175
    public function clearCache()
1176
    {
1177
        $this->_unsaved = [];
1178
        $this->_values = [];
1179
        $this->_relationships = [];
1180
1181
        return $this;
1182
    }
1183
1184
    /////////////////////////////
1185
    // Relationships
1186
    /////////////////////////////
1187
1188
    /**
1189
     * @deprecated
1190
     *
1191
     * Gets the model for a has-one relationship
1192
     *
1193
     * @param string $k property
1194
     *
1195
     * @return Model|null
1196
     */
1197
    public function relation($k)
1198
    {
1199
        $id = $this->$k;
1200
        if (!$id) {
1201
            return;
1202
        }
1203
1204
        if (!isset($this->_relationships[$k])) {
1205
            $property = static::getProperty($k);
1206
            $relationModelClass = $property['relation'];
1207
            $this->_relationships[$k] = $relationModelClass::find($id);
1208
        }
1209
1210
        return $this->_relationships[$k];
1211
    }
1212
1213
    /**
1214
     * @deprecated
1215
     *
1216
     * Sets the model for a has-one relationship
1217
     *
1218
     * @param string $k
1219
     * @param Model  $model
1220
     *
1221
     * @return self
1222
     */
1223
    public function setRelation($k, Model $model)
1224
    {
1225
        $this->$k = $model->id();
1226
        $this->_relationships[$k] = $model;
1227
1228
        return $this;
1229
    }
1230
1231
    /**
1232
     * Creates the parent side of a One-To-One relationship.
1233
     *
1234
     * @param string $model      foreign model class
1235
     * @param string $foreignKey identifying key on foreign model
1236
     * @param string $localKey   identifying key on local model
1237
     *
1238
     * @return Relation\Relation
1239
     */
1240
    public function hasOne($model, $foreignKey = '', $localKey = '')
1241
    {
1242
        return new HasOne($this, $localKey, $model, $foreignKey);
1243
    }
1244
1245
    /**
1246
     * Creates the child side of a One-To-One or One-To-Many relationship.
1247
     *
1248
     * @param string $model      foreign model class
1249
     * @param string $foreignKey identifying key on foreign model
1250
     * @param string $localKey   identifying key on local model
1251
     *
1252
     * @return Relation\Relation
1253
     */
1254
    public function belongsTo($model, $foreignKey = '', $localKey = '')
1255
    {
1256
        return new BelongsTo($this, $localKey, $model, $foreignKey);
1257
    }
1258
1259
    /**
1260
     * Creates the parent side of a Many-To-One or Many-To-Many relationship.
1261
     *
1262
     * @param string $model      foreign model class
1263
     * @param string $foreignKey identifying key on foreign model
1264
     * @param string $localKey   identifying key on local model
1265
     *
1266
     * @return Relation\Relation
1267
     */
1268
    public function hasMany($model, $foreignKey = '', $localKey = '')
1269
    {
1270
        return new HasMany($this, $localKey, $model, $foreignKey);
1271
    }
1272
1273
    /**
1274
     * Creates the child side of a Many-To-Many relationship.
1275
     *
1276
     * @param string $model      foreign model class
1277
     * @param string $tablename  pivot table name
1278
     * @param string $foreignKey identifying key on foreign model
1279
     * @param string $localKey   identifying key on local model
1280
     *
1281
     * @return \Pulsar\Relation\Relation
1282
     */
1283
    public function belongsToMany($model, $tablename = '', $foreignKey = '', $localKey = '')
1284
    {
1285
        return new BelongsToMany($this, $localKey, $tablename, $model, $foreignKey);
1286
    }
1287
1288
    /////////////////////////////
1289
    // Events
1290
    /////////////////////////////
1291
1292
    /**
1293
     * Gets the event dispatcher.
1294
     *
1295
     * @return EventDispatcher
1296
     */
1297
    public static function getDispatcher($ignoreCache = false)
1298
    {
1299
        $class = get_called_class();
1300
        if ($ignoreCache || !isset(self::$dispatchers[$class])) {
1301
            self::$dispatchers[$class] = new EventDispatcher();
1302
        }
1303
1304
        return self::$dispatchers[$class];
1305
    }
1306
1307
    /**
1308
     * Subscribes to a listener to an event.
1309
     *
1310
     * @param string   $event    event name
1311
     * @param callable $listener
1312
     * @param int      $priority optional priority, higher #s get called first
1313
     */
1314
    public static function listen($event, callable $listener, $priority = 0)
1315
    {
1316
        static::getDispatcher()->addListener($event, $listener, $priority);
1317
    }
1318
1319
    /**
1320
     * Adds a listener to the model.creating and model.updating events.
1321
     *
1322
     * @param callable $listener
1323
     * @param int      $priority
1324
     */
1325
    public static function saving(callable $listener, $priority = 0)
1326
    {
1327
        static::listen(ModelEvent::CREATING, $listener, $priority);
1328
        static::listen(ModelEvent::UPDATING, $listener, $priority);
1329
    }
1330
1331
    /**
1332
     * Adds a listener to the model.created and model.updated events.
1333
     *
1334
     * @param callable $listener
1335
     * @param int      $priority
1336
     */
1337
    public static function saved(callable $listener, $priority = 0)
1338
    {
1339
        static::listen(ModelEvent::CREATED, $listener, $priority);
1340
        static::listen(ModelEvent::UPDATED, $listener, $priority);
1341
    }
1342
1343
    /**
1344
     * Adds a listener to the model.creating event.
1345
     *
1346
     * @param callable $listener
1347
     * @param int      $priority
1348
     */
1349
    public static function creating(callable $listener, $priority = 0)
1350
    {
1351
        static::listen(ModelEvent::CREATING, $listener, $priority);
1352
    }
1353
1354
    /**
1355
     * Adds a listener to the model.created event.
1356
     *
1357
     * @param callable $listener
1358
     * @param int      $priority
1359
     */
1360
    public static function created(callable $listener, $priority = 0)
1361
    {
1362
        static::listen(ModelEvent::CREATED, $listener, $priority);
1363
    }
1364
1365
    /**
1366
     * Adds a listener to the model.updating event.
1367
     *
1368
     * @param callable $listener
1369
     * @param int      $priority
1370
     */
1371
    public static function updating(callable $listener, $priority = 0)
1372
    {
1373
        static::listen(ModelEvent::UPDATING, $listener, $priority);
1374
    }
1375
1376
    /**
1377
     * Adds a listener to the model.updated event.
1378
     *
1379
     * @param callable $listener
1380
     * @param int      $priority
1381
     */
1382
    public static function updated(callable $listener, $priority = 0)
1383
    {
1384
        static::listen(ModelEvent::UPDATED, $listener, $priority);
1385
    }
1386
1387
    /**
1388
     * Adds a listener to the model.deleting event.
1389
     *
1390
     * @param callable $listener
1391
     * @param int      $priority
1392
     */
1393
    public static function deleting(callable $listener, $priority = 0)
1394
    {
1395
        static::listen(ModelEvent::DELETING, $listener, $priority);
1396
    }
1397
1398
    /**
1399
     * Adds a listener to the model.deleted event.
1400
     *
1401
     * @param callable $listener
1402
     * @param int      $priority
1403
     */
1404
    public static function deleted(callable $listener, $priority = 0)
1405
    {
1406
        static::listen(ModelEvent::DELETED, $listener, $priority);
1407
    }
1408
1409
    /**
1410
     * Dispatches an event.
1411
     *
1412
     * @param string $eventName
1413
     *
1414
     * @return ModelEvent
1415
     */
1416
    protected function dispatch($eventName)
1417
    {
1418
        $event = new ModelEvent($this);
1419
1420
        return static::getDispatcher()->dispatch($eventName, $event);
1421
    }
1422
1423
    /**
1424
     * Dispatches the given event and checks if it was successful.
1425
     *
1426
     * @param string $eventName
1427
     *
1428
     * @return bool true if the events were successfully propagated
1429
     */
1430
    private function handleDispatch($eventName)
1431
    {
1432
        $event = $this->dispatch($eventName);
1433
1434
        return !$event->isPropagationStopped();
1435
    }
1436
1437
    /////////////////////////////
1438
    // Validation
1439
    /////////////////////////////
1440
1441
    /**
1442
     * Gets the error stack for this model.
1443
     *
1444
     * @return ErrorStack
1445
     */
1446
    public function getErrors()
1447
    {
1448
        if (!$this->_errors && self::$globalErrorStack) {
1449
            $this->_errors = self::$globalErrorStack;
1450
        } elseif (!$this->_errors) {
1451
            $this->_errors = new ErrorStack();
1452
        }
1453
1454
        return $this->_errors;
1455
    }
1456
1457
    /**
1458
     * Validates and marshals a value to storage.
1459
     *
1460
     * @param array  $property
1461
     * @param string $propertyName
1462
     * @param mixed  $value
1463
     *
1464
     * @return bool
1465
     */
1466
    private function filterAndValidate(array $property, $propertyName, &$value)
1467
    {
1468
        // assume empty string is a null value for properties
1469
        // that are marked as optionally-null
1470
        if ($property['null'] && empty($value)) {
1471
            $value = null;
1472
1473
            return true;
1474
        }
1475
1476
        // validate
1477
        list($valid, $value) = $this->validate($property, $propertyName, $value);
1478
1479
        // unique?
1480
        if ($valid && $property['unique'] && ($this->_id === false || $value != $this->ignoreUnsaved()->$propertyName)) {
1481
            $valid = $this->checkUniqueness($property, $propertyName, $value);
1482
        }
1483
1484
        return $valid;
1485
    }
1486
1487
    /**
1488
     * Validates a value for a property.
1489
     *
1490
     * @param array  $property
1491
     * @param string $propertyName
1492
     * @param mixed  $value
1493
     *
1494
     * @return array
1495
     */
1496
    private function validate(array $property, $propertyName, $value)
1497
    {
1498
        $valid = true;
1499
1500
        if (isset($property['validate']) && is_callable($property['validate'])) {
1501
            $valid = call_user_func_array($property['validate'], [$value]);
1502
        } elseif (isset($property['validate'])) {
1503
            $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...
1504
        }
1505
1506 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...
1507
            $this->getErrors()->push([
1508
                'error' => self::ERROR_VALIDATION_FAILED,
1509
                'params' => [
1510
                    'field' => $propertyName,
1511
                    'field_name' => (isset($property['title'])) ? $property['title'] : Inflector::get()->titleize($propertyName), ], ]);
1512
        }
1513
1514
        return [$valid, $value];
1515
    }
1516
1517
    /**
1518
     * Checks if a value is unique for a property.
1519
     *
1520
     * @param array  $property
1521
     * @param string $propertyName
1522
     * @param mixed  $value
1523
     *
1524
     * @return bool
1525
     */
1526
    private function checkUniqueness(array $property, $propertyName, $value)
1527
    {
1528
        $n = static::where([$propertyName => $value])->count();
1529 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...
1530
            $this->getErrors()->push([
1531
                'error' => self::ERROR_NOT_UNIQUE,
1532
                'params' => [
1533
                    'field' => $propertyName,
1534
                    'field_name' => (isset($property['title'])) ? $property['title'] : Inflector::get()->titleize($propertyName), ], ]);
1535
1536
            return false;
1537
        }
1538
1539
        return true;
1540
    }
1541
1542
    /**
1543
     * Gets the marshaled default value for a property (if set).
1544
     *
1545
     * @param string $property
1546
     *
1547
     * @return mixed
1548
     */
1549
    private function getPropertyDefault(array $property)
1550
    {
1551
        return array_value($property, 'default');
1552
    }
1553
}
1554