HasAttributes::addMutatedAttributesToArray()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 2
dl 0
loc 20
rs 9.6
c 0
b 0
f 0
1
<?php
2
3
namespace Illuminate\Database\Eloquent\Concerns;
4
5
use LogicException;
6
use DateTimeInterface;
7
use Illuminate\Support\Arr;
8
use Illuminate\Support\Str;
9
use Illuminate\Support\Carbon;
10
use Illuminate\Contracts\Support\Arrayable;
11
use Illuminate\Database\Eloquent\Relations\Relation;
12
use Illuminate\Support\Collection as BaseCollection;
13
use Illuminate\Database\Eloquent\JsonEncodingException;
14
15
trait HasAttributes
16
{
17
    /**
18
     * The model's attributes.
19
     *
20
     * @var array
21
     */
22
    protected $attributes = [];
23
24
    /**
25
     * The model attribute's original state.
26
     *
27
     * @var array
28
     */
29
    protected $original = [];
30
31
    /**
32
     * The changed model attributes.
33
     *
34
     * @var array
35
     */
36
    protected $changes = [];
37
38
    /**
39
     * The attributes that should be cast to native types.
40
     *
41
     * @var array
42
     */
43
    protected $casts = [];
44
45
    /**
46
     * The attributes that should be mutated to dates.
47
     *
48
     * @var array
49
     */
50
    protected $dates = [];
51
52
    /**
53
     * The storage format of the model's date columns.
54
     *
55
     * @var string
56
     */
57
    protected $dateFormat;
58
59
    /**
60
     * The accessors to append to the model's array form.
61
     *
62
     * @var array
63
     */
64
    protected $appends = [];
65
66
    /**
67
     * Indicates whether attributes are snake cased on arrays.
68
     *
69
     * @var bool
70
     */
71
    public static $snakeAttributes = true;
72
73
    /**
74
     * The cache of the mutated attributes for each class.
75
     *
76
     * @var array
77
     */
78
    protected static $mutatorCache = [];
79
80
    /**
81
     * Convert the model's attributes to an array.
82
     *
83
     * @return array
84
     */
85
    public function attributesToArray()
86
    {
87
        // If an attribute is a date, we will cast it to a string after converting it
88
        // to a DateTime / Carbon instance. This is so we will get some consistent
89
        // formatting while accessing attributes vs. arraying / JSONing a model.
90
        $attributes = $this->addDateAttributesToArray(
91
            $attributes = $this->getArrayableAttributes()
92
        );
93
94
        $attributes = $this->addMutatedAttributesToArray(
95
            $attributes, $mutatedAttributes = $this->getMutatedAttributes()
96
        );
97
98
        // Next we will handle any casts that have been setup for this model and cast
99
        // the values to their appropriate type. If the attribute has a mutator we
100
        // will not perform the cast on those attributes to avoid any confusion.
101
        $attributes = $this->addCastAttributesToArray(
102
            $attributes, $mutatedAttributes
103
        );
104
105
        // Here we will grab all of the appended, calculated attributes to this model
106
        // as these attributes are not really in the attributes array, but are run
107
        // when we need to array or JSON the model for convenience to the coder.
108
        foreach ($this->getArrayableAppends() as $key) {
109
            $attributes[$key] = $this->mutateAttributeForArray($key, null);
110
        }
111
112
        return $attributes;
113
    }
114
115
    /**
116
     * Add the date attributes to the attributes array.
117
     *
118
     * @param  array  $attributes
119
     * @return array
120
     */
121
    protected function addDateAttributesToArray(array $attributes)
122
    {
123
        foreach ($this->getDates() as $key) {
124
            if (! isset($attributes[$key])) {
125
                continue;
126
            }
127
128
            $attributes[$key] = $this->serializeDate(
129
                $this->asDateTime($attributes[$key])
130
            );
131
        }
132
133
        return $attributes;
134
    }
135
136
    /**
137
     * Add the mutated attributes to the attributes array.
138
     *
139
     * @param  array  $attributes
140
     * @param  array  $mutatedAttributes
141
     * @return array
142
     */
143
    protected function addMutatedAttributesToArray(array $attributes, array $mutatedAttributes)
144
    {
145
        foreach ($mutatedAttributes as $key) {
146
            // We want to spin through all the mutated attributes for this model and call
147
            // the mutator for the attribute. We cache off every mutated attributes so
148
            // we don't have to constantly check on attributes that actually change.
149
            if (! array_key_exists($key, $attributes)) {
150
                continue;
151
            }
152
153
            // Next, we will call the mutator for this attribute so that we can get these
154
            // mutated attribute's actual values. After we finish mutating each of the
155
            // attributes we will return this final array of the mutated attributes.
156
            $attributes[$key] = $this->mutateAttributeForArray(
157
                $key, $attributes[$key]
158
            );
159
        }
160
161
        return $attributes;
162
    }
163
164
    /**
165
     * Add the casted attributes to the attributes array.
166
     *
167
     * @param  array  $attributes
168
     * @param  array  $mutatedAttributes
169
     * @return array
170
     */
171
    protected function addCastAttributesToArray(array $attributes, array $mutatedAttributes)
172
    {
173
        foreach ($this->getCasts() as $key => $value) {
174
            if (! array_key_exists($key, $attributes) || in_array($key, $mutatedAttributes)) {
175
                continue;
176
            }
177
178
            // Here we will cast the attribute. Then, if the cast is a date or datetime cast
179
            // then we will serialize the date for the array. This will convert the dates
180
            // to strings based on the date format specified for these Eloquent models.
181
            $attributes[$key] = $this->castAttribute(
182
                $key, $attributes[$key]
183
            );
184
185
            // If the attribute cast was a date or a datetime, we will serialize the date as
186
            // a string. This allows the developers to customize how dates are serialized
187
            // into an array without affecting how they are persisted into the storage.
188
            if ($attributes[$key] &&
189
                ($value === 'date' || $value === 'datetime')) {
190
                $attributes[$key] = $this->serializeDate($attributes[$key]);
191
            }
192
193
            if ($attributes[$key] && $this->isCustomDateTimeCast($value)) {
194
                $attributes[$key] = $attributes[$key]->format(explode(':', $value, 2)[1]);
195
            }
196
        }
197
198
        return $attributes;
199
    }
200
201
    /**
202
     * Get an attribute array of all arrayable attributes.
203
     *
204
     * @return array
205
     */
206
    protected function getArrayableAttributes()
207
    {
208
        return $this->getArrayableItems($this->attributes);
209
    }
210
211
    /**
212
     * Get all of the appendable values that are arrayable.
213
     *
214
     * @return array
215
     */
216
    protected function getArrayableAppends()
217
    {
218
        if (! count($this->appends)) {
219
            return [];
220
        }
221
222
        return $this->getArrayableItems(
223
            array_combine($this->appends, $this->appends)
224
        );
225
    }
226
227
    /**
228
     * Get the model's relationships in array form.
229
     *
230
     * @return array
231
     */
232
    public function relationsToArray()
233
    {
234
        $attributes = [];
235
236
        foreach ($this->getArrayableRelations() as $key => $value) {
237
            // If the values implements the Arrayable interface we can just call this
238
            // toArray method on the instances which will convert both models and
239
            // collections to their proper array form and we'll set the values.
240
            if ($value instanceof Arrayable) {
241
                $relation = $value->toArray();
242
            }
243
244
            // If the value is null, we'll still go ahead and set it in this list of
245
            // attributes since null is used to represent empty relationships if
246
            // if it a has one or belongs to type relationships on the models.
247
            elseif (is_null($value)) {
248
                $relation = $value;
249
            }
250
251
            // If the relationships snake-casing is enabled, we will snake case this
252
            // key so that the relation attribute is snake cased in this returned
253
            // array to the developers, making this consistent with attributes.
254
            if (static::$snakeAttributes) {
255
                $key = Str::snake($key);
256
            }
257
258
            // If the relation value has been set, we will set it on this attributes
259
            // list for returning. If it was not arrayable or null, we'll not set
260
            // the value on the array because it is some type of invalid value.
261
            if (isset($relation) || is_null($value)) {
262
                $attributes[$key] = $relation;
263
            }
264
265
            unset($relation);
266
        }
267
268
        return $attributes;
269
    }
270
271
    /**
272
     * Get an attribute array of all arrayable relations.
273
     *
274
     * @return array
275
     */
276
    protected function getArrayableRelations()
277
    {
278
        return $this->getArrayableItems($this->relations);
0 ignored issues
show
Bug introduced by
The property relations does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
279
    }
280
281
    /**
282
     * Get an attribute array of all arrayable values.
283
     *
284
     * @param  array  $values
285
     * @return array
286
     */
287
    protected function getArrayableItems(array $values)
288
    {
289
        if (count($this->getVisible()) > 0) {
0 ignored issues
show
Bug introduced by
It seems like getVisible() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
290
            $values = array_intersect_key($values, array_flip($this->getVisible()));
0 ignored issues
show
Bug introduced by
It seems like getVisible() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
291
        }
292
293
        if (count($this->getHidden()) > 0) {
0 ignored issues
show
Bug introduced by
It seems like getHidden() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
294
            $values = array_diff_key($values, array_flip($this->getHidden()));
0 ignored issues
show
Bug introduced by
It seems like getHidden() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
295
        }
296
297
        return $values;
298
    }
299
300
    /**
301
     * Get an attribute from the model.
302
     *
303
     * @param  string  $key
304
     * @return mixed
305
     */
306
    public function getAttribute($key)
307
    {
308
        if (! $key) {
309
            return;
310
        }
311
312
        // If the attribute exists in the attribute array or has a "get" mutator we will
313
        // get the attribute's value. Otherwise, we will proceed as if the developers
314
        // are asking for a relationship's value. This covers both types of values.
315
        if (array_key_exists($key, $this->attributes) ||
316
            $this->hasGetMutator($key)) {
317
            return $this->getAttributeValue($key);
318
        }
319
320
        // Here we will determine if the model base class itself contains this given key
321
        // since we don't want to treat any of those methods as relationships because
322
        // they are all intended as helper methods and none of these are relations.
323
        if (method_exists(self::class, $key)) {
324
            return;
325
        }
326
327
        return $this->getRelationValue($key);
328
    }
329
330
    /**
331
     * Get a plain attribute (not a relationship).
332
     *
333
     * @param  string  $key
334
     * @return mixed
335
     */
336
    public function getAttributeValue($key)
337
    {
338
        $value = $this->getAttributeFromArray($key);
339
340
        // If the attribute has a get mutator, we will call that then return what
341
        // it returns as the value, which is useful for transforming values on
342
        // retrieval from the model to a form that is more useful for usage.
343
        if ($this->hasGetMutator($key)) {
344
            return $this->mutateAttribute($key, $value);
345
        }
346
347
        // If the attribute exists within the cast array, we will convert it to
348
        // an appropriate native PHP type dependant upon the associated value
349
        // given with the key in the pair. Dayle made this comment line up.
350
        if ($this->hasCast($key)) {
351
            return $this->castAttribute($key, $value);
352
        }
353
354
        // If the attribute is listed as a date, we will convert it to a DateTime
355
        // instance on retrieval, which makes it quite convenient to work with
356
        // date fields without having to create a mutator for each property.
357
        if (in_array($key, $this->getDates()) &&
358
            ! is_null($value)) {
359
            return $this->asDateTime($value);
360
        }
361
362
        return $value;
363
    }
364
365
    /**
366
     * Get an attribute from the $attributes array.
367
     *
368
     * @param  string  $key
369
     * @return mixed
370
     */
371
    protected function getAttributeFromArray($key)
372
    {
373
        if (isset($this->attributes[$key])) {
374
            return $this->attributes[$key];
375
        }
376
    }
377
378
    /**
379
     * Get a relationship.
380
     *
381
     * @param  string  $key
382
     * @return mixed
383
     */
384
    public function getRelationValue($key)
385
    {
386
        // If the key already exists in the relationships array, it just means the
387
        // relationship has already been loaded, so we'll just return it out of
388
        // here because there is no need to query within the relations twice.
389
        if ($this->relationLoaded($key)) {
0 ignored issues
show
Bug introduced by
It seems like relationLoaded() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
390
            return $this->relations[$key];
391
        }
392
393
        // If the "attribute" exists as a method on the model, we will just assume
394
        // it is a relationship and will load and return results from the query
395
        // and hydrate the relationship's value on the "relationships" array.
396
        if (method_exists($this, $key)) {
397
            return $this->getRelationshipFromMethod($key);
398
        }
399
    }
400
401
    /**
402
     * Get a relationship value from a method.
403
     *
404
     * @param  string  $method
405
     * @return mixed
406
     *
407
     * @throws \LogicException
408
     */
409
    protected function getRelationshipFromMethod($method)
410
    {
411
        $relation = $this->$method();
412
413
        if (! $relation instanceof Relation) {
414
            throw new LogicException(sprintf(
415
                '%s::%s must return a relationship instance.', static::class, $method
416
            ));
417
        }
418
419
        return tap($relation->getResults(), function ($results) use ($method) {
420
            $this->setRelation($method, $results);
0 ignored issues
show
Bug introduced by
It seems like setRelation() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
421
        });
422
    }
423
424
    /**
425
     * Determine if a get mutator exists for an attribute.
426
     *
427
     * @param  string  $key
428
     * @return bool
429
     */
430
    public function hasGetMutator($key)
431
    {
432
        return method_exists($this, 'get'.Str::studly($key).'Attribute');
433
    }
434
435
    /**
436
     * Get the value of an attribute using its mutator.
437
     *
438
     * @param  string  $key
439
     * @param  mixed  $value
440
     * @return mixed
441
     */
442
    protected function mutateAttribute($key, $value)
443
    {
444
        return $this->{'get'.Str::studly($key).'Attribute'}($value);
445
    }
446
447
    /**
448
     * Get the value of an attribute using its mutator for array conversion.
449
     *
450
     * @param  string  $key
451
     * @param  mixed  $value
452
     * @return mixed
453
     */
454
    protected function mutateAttributeForArray($key, $value)
455
    {
456
        $value = $this->mutateAttribute($key, $value);
457
458
        return $value instanceof Arrayable ? $value->toArray() : $value;
459
    }
460
461
    /**
462
     * Cast an attribute to a native PHP type.
463
     *
464
     * @param  string  $key
465
     * @param  mixed  $value
466
     * @return mixed
467
     */
468
    protected function castAttribute($key, $value)
469
    {
470
        if (is_null($value)) {
471
            return $value;
472
        }
473
474
        switch ($this->getCastType($key)) {
475
            case 'int':
476
            case 'integer':
477
                return (int) $value;
478
            case 'real':
479
            case 'float':
480
            case 'double':
481
                return (float) $value;
482
            case 'string':
483
                return (string) $value;
484
            case 'bool':
485
            case 'boolean':
486
                return (bool) $value;
487
            case 'object':
488
                return $this->fromJson($value, true);
489
            case 'array':
490
            case 'json':
491
                return $this->fromJson($value);
492
            case 'collection':
493
                return new BaseCollection($this->fromJson($value));
494
            case 'date':
495
                return $this->asDate($value);
496
            case 'datetime':
497
            case 'custom_datetime':
498
                return $this->asDateTime($value);
499
            case 'timestamp':
500
                return $this->asTimestamp($value);
501
            default:
502
                return $value;
503
        }
504
    }
505
506
    /**
507
     * Get the type of cast for a model attribute.
508
     *
509
     * @param  string  $key
510
     * @return string
511
     */
512
    protected function getCastType($key)
513
    {
514
        if ($this->isCustomDateTimeCast($this->getCasts()[$key])) {
515
            return 'custom_datetime';
516
        }
517
518
        return trim(strtolower($this->getCasts()[$key]));
519
    }
520
521
    /**
522
     * Determine if the cast type is a custom date time cast.
523
     *
524
     * @param  string  $cast
525
     * @return bool
526
     */
527
    protected function isCustomDateTimeCast($cast)
528
    {
529
        return strncmp($cast, 'date:', 5) === 0 ||
530
               strncmp($cast, 'datetime:', 9) === 0;
531
    }
532
533
    /**
534
     * Set a given attribute on the model.
535
     *
536
     * @param  string  $key
537
     * @param  mixed  $value
538
     * @return mixed
539
     */
540
    public function setAttribute($key, $value)
541
    {
542
        // First we will check for the presence of a mutator for the set operation
543
        // which simply lets the developers tweak the attribute as it is set on
544
        // the model, such as "json_encoding" an listing of data for storage.
545
        if ($this->hasSetMutator($key)) {
546
            return $this->setMutatedAttributeValue($key, $value);
547
        }
548
549
        // If an attribute is listed as a "date", we'll convert it from a DateTime
550
        // instance into a form proper for storage on the database tables using
551
        // the connection grammar's date format. We will auto set the values.
552
        elseif ($value && $this->isDateAttribute($key)) {
553
            $value = $this->fromDateTime($value);
554
        }
555
556
        if ($this->isJsonCastable($key) && ! is_null($value)) {
557
            $value = $this->castAttributeAsJson($key, $value);
558
        }
559
560
        // If this attribute contains a JSON ->, we'll set the proper value in the
561
        // attribute's underlying array. This takes care of properly nesting an
562
        // attribute in the array's value in the case of deeply nested items.
563
        if (Str::contains($key, '->')) {
564
            return $this->fillJsonAttribute($key, $value);
565
        }
566
567
        $this->attributes[$key] = $value;
568
569
        return $this;
570
    }
571
572
    /**
573
     * Determine if a set mutator exists for an attribute.
574
     *
575
     * @param  string  $key
576
     * @return bool
577
     */
578
    public function hasSetMutator($key)
579
    {
580
        return method_exists($this, 'set'.Str::studly($key).'Attribute');
581
    }
582
583
    /**
584
     * Set the value of an attribute using its mutator.
585
     *
586
     * @param  string  $key
587
     * @param  mixed  $value
588
     * @return mixed
589
     */
590
    protected function setMutatedAttributeValue($key, $value)
591
    {
592
        return $this->{'set'.Str::studly($key).'Attribute'}($value);
593
    }
594
595
    /**
596
     * Determine if the given attribute is a date or date castable.
597
     *
598
     * @param  string  $key
599
     * @return bool
600
     */
601
    protected function isDateAttribute($key)
602
    {
603
        return in_array($key, $this->getDates()) ||
604
                                    $this->isDateCastable($key);
605
    }
606
607
    /**
608
     * Set a given JSON attribute on the model.
609
     *
610
     * @param  string  $key
611
     * @param  mixed  $value
612
     * @return $this
613
     */
614
    public function fillJsonAttribute($key, $value)
615
    {
616
        list($key, $path) = explode('->', $key, 2);
617
618
        $this->attributes[$key] = $this->asJson($this->getArrayAttributeWithValue(
619
            $path, $key, $value
620
        ));
621
622
        return $this;
623
    }
624
625
    /**
626
     * Get an array attribute with the given key and value set.
627
     *
628
     * @param  string  $path
629
     * @param  string  $key
630
     * @param  mixed  $value
631
     * @return $this
632
     */
633
    protected function getArrayAttributeWithValue($path, $key, $value)
634
    {
635
        return tap($this->getArrayAttributeByKey($key), function (&$array) use ($path, $value) {
636
            Arr::set($array, str_replace('->', '.', $path), $value);
637
        });
638
    }
639
640
    /**
641
     * Get an array attribute or return an empty array if it is not set.
642
     *
643
     * @param  string  $key
644
     * @return array
645
     */
646
    protected function getArrayAttributeByKey($key)
647
    {
648
        return isset($this->attributes[$key]) ?
649
                    $this->fromJson($this->attributes[$key]) : [];
650
    }
651
652
    /**
653
     * Cast the given attribute to JSON.
654
     *
655
     * @param  string  $key
656
     * @param  mixed  $value
657
     * @return string
658
     */
659
    protected function castAttributeAsJson($key, $value)
660
    {
661
        $value = $this->asJson($value);
662
663
        if ($value === false) {
664
            throw JsonEncodingException::forAttribute(
665
                $this, $key, json_last_error_msg()
666
            );
667
        }
668
669
        return $value;
670
    }
671
672
    /**
673
     * Encode the given value as JSON.
674
     *
675
     * @param  mixed  $value
676
     * @return string
677
     */
678
    protected function asJson($value)
679
    {
680
        return json_encode($value);
681
    }
682
683
    /**
684
     * Decode the given JSON back into an array or object.
685
     *
686
     * @param  string  $value
687
     * @param  bool  $asObject
688
     * @return mixed
689
     */
690
    public function fromJson($value, $asObject = false)
691
    {
692
        return json_decode($value, ! $asObject);
693
    }
694
695
    /**
696
     * Return a timestamp as DateTime object with time set to 00:00:00.
697
     *
698
     * @param  mixed  $value
699
     * @return \Illuminate\Support\Carbon
700
     */
701
    protected function asDate($value)
702
    {
703
        return $this->asDateTime($value)->startOfDay();
704
    }
705
706
    /**
707
     * Return a timestamp as DateTime object.
708
     *
709
     * @param  mixed  $value
710
     * @return \Illuminate\Support\Carbon
711
     */
712
    protected function asDateTime($value)
713
    {
714
        // If this value is already a Carbon instance, we shall just return it as is.
715
        // This prevents us having to re-instantiate a Carbon instance when we know
716
        // it already is one, which wouldn't be fulfilled by the DateTime check.
717
        if ($value instanceof Carbon) {
718
            return $value;
719
        }
720
721
        // If the value is already a DateTime instance, we will just skip the rest of
722
        // these checks since they will be a waste of time, and hinder performance
723
        // when checking the field. We will just return the DateTime right away.
724
        if ($value instanceof DateTimeInterface) {
725
            return new Carbon(
726
                $value->format('Y-m-d H:i:s.u'), $value->getTimezone()
727
            );
728
        }
729
730
        // If this value is an integer, we will assume it is a UNIX timestamp's value
731
        // and format a Carbon object from this timestamp. This allows flexibility
732
        // when defining your date fields as they might be UNIX timestamps here.
733
        if (is_numeric($value)) {
734
            return Carbon::createFromTimestamp($value);
735
        }
736
737
        // If the value is in simply year, month, day format, we will instantiate the
738
        // Carbon instances from that format. Again, this provides for simple date
739
        // fields on the database, while still supporting Carbonized conversion.
740
        if ($this->isStandardDateFormat($value)) {
741
            return Carbon::createFromFormat('Y-m-d', $value)->startOfDay();
742
        }
743
744
        // Finally, we will just assume this date is in the format used by default on
745
        // the database connection and use that format to create the Carbon object
746
        // that is returned back out to the developers after we convert it here.
747
        return Carbon::createFromFormat(
748
            str_replace('.v', '.u', $this->getDateFormat()), $value
749
        );
750
    }
751
752
    /**
753
     * Determine if the given value is a standard date format.
754
     *
755
     * @param  string  $value
756
     * @return bool
0 ignored issues
show
Documentation introduced by
Should the return type not be integer?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
757
     */
758
    protected function isStandardDateFormat($value)
759
    {
760
        return preg_match('/^(\d{4})-(\d{1,2})-(\d{1,2})$/', $value);
761
    }
762
763
    /**
764
     * Convert a DateTime to a storable string.
765
     *
766
     * @param  \DateTime|int  $value
767
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
768
     */
769
    public function fromDateTime($value)
770
    {
771
        return empty($value) ? $value : $this->asDateTime($value)->format(
772
            $this->getDateFormat()
773
        );
774
    }
775
776
    /**
777
     * Return a timestamp as unix timestamp.
778
     *
779
     * @param  mixed  $value
780
     * @return int
781
     */
782
    protected function asTimestamp($value)
783
    {
784
        return $this->asDateTime($value)->getTimestamp();
785
    }
786
787
    /**
788
     * Prepare a date for array / JSON serialization.
789
     *
790
     * @param  \DateTimeInterface  $date
791
     * @return string
792
     */
793
    protected function serializeDate(DateTimeInterface $date)
794
    {
795
        return $date->format($this->getDateFormat());
796
    }
797
798
    /**
799
     * Get the attributes that should be converted to dates.
800
     *
801
     * @return array
802
     */
803
    public function getDates()
804
    {
805
        $defaults = [static::CREATED_AT, static::UPDATED_AT];
806
807
        return $this->usesTimestamps()
0 ignored issues
show
Bug introduced by
It seems like usesTimestamps() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
808
                    ? array_unique(array_merge($this->dates, $defaults))
809
                    : $this->dates;
810
    }
811
812
    /**
813
     * Get the format for database stored dates.
814
     *
815
     * @return string
816
     */
817
    public function getDateFormat()
818
    {
819
        return $this->dateFormat ?: $this->getConnection()->getQueryGrammar()->getDateFormat();
0 ignored issues
show
Bug introduced by
It seems like getConnection() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
820
    }
821
822
    /**
823
     * Set the date format used by the model.
824
     *
825
     * @param  string  $format
826
     * @return $this
827
     */
828
    public function setDateFormat($format)
829
    {
830
        $this->dateFormat = $format;
831
832
        return $this;
833
    }
834
835
    /**
836
     * Determine whether an attribute should be cast to a native type.
837
     *
838
     * @param  string  $key
839
     * @param  array|string|null  $types
840
     * @return bool
841
     */
842
    public function hasCast($key, $types = null)
843
    {
844
        if (array_key_exists($key, $this->getCasts())) {
845
            return $types ? in_array($this->getCastType($key), (array) $types, true) : true;
846
        }
847
848
        return false;
849
    }
850
851
    /**
852
     * Get the casts array.
853
     *
854
     * @return array
855
     */
856
    public function getCasts()
857
    {
858
        if ($this->getIncrementing()) {
0 ignored issues
show
Bug introduced by
It seems like getIncrementing() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
859
            return array_merge([$this->getKeyName() => $this->getKeyType()], $this->casts);
0 ignored issues
show
Bug introduced by
It seems like getKeyName() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
Bug introduced by
It seems like getKeyType() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
860
        }
861
862
        return $this->casts;
863
    }
864
865
    /**
866
     * Determine whether a value is Date / DateTime castable for inbound manipulation.
867
     *
868
     * @param  string  $key
869
     * @return bool
870
     */
871
    protected function isDateCastable($key)
872
    {
873
        return $this->hasCast($key, ['date', 'datetime']);
874
    }
875
876
    /**
877
     * Determine whether a value is JSON castable for inbound manipulation.
878
     *
879
     * @param  string  $key
880
     * @return bool
881
     */
882
    protected function isJsonCastable($key)
883
    {
884
        return $this->hasCast($key, ['array', 'json', 'object', 'collection']);
885
    }
886
887
    /**
888
     * Get all of the current attributes on the model.
889
     *
890
     * @return array
891
     */
892
    public function getAttributes()
893
    {
894
        return $this->attributes;
895
    }
896
897
    /**
898
     * Set the array of model attributes. No checking is done.
899
     *
900
     * @param  array  $attributes
901
     * @param  bool  $sync
902
     * @return $this
903
     */
904
    public function setRawAttributes(array $attributes, $sync = false)
905
    {
906
        $this->attributes = $attributes;
907
908
        if ($sync) {
909
            $this->syncOriginal();
910
        }
911
912
        return $this;
913
    }
914
915
    /**
916
     * Get the model's original attribute values.
917
     *
918
     * @param  string|null  $key
919
     * @param  mixed  $default
920
     * @return mixed|array
921
     */
922
    public function getOriginal($key = null, $default = null)
923
    {
924
        return Arr::get($this->original, $key, $default);
925
    }
926
927
    /**
928
     * Get a subset of the model's attributes.
929
     *
930
     * @param  array|mixed  $attributes
931
     * @return array
932
     */
933
    public function only($attributes)
934
    {
935
        $results = [];
936
937
        foreach (is_array($attributes) ? $attributes : func_get_args() as $attribute) {
938
            $results[$attribute] = $this->getAttribute($attribute);
939
        }
940
941
        return $results;
942
    }
943
944
    /**
945
     * Sync the original attributes with the current.
946
     *
947
     * @return $this
948
     */
949
    public function syncOriginal()
950
    {
951
        $this->original = $this->attributes;
952
953
        return $this;
954
    }
955
956
    /**
957
     * Sync a single original attribute with its current value.
958
     *
959
     * @param  string  $attribute
960
     * @return $this
961
     */
962
    public function syncOriginalAttribute($attribute)
963
    {
964
        $this->original[$attribute] = $this->attributes[$attribute];
965
966
        return $this;
967
    }
968
969
    /**
970
     * Sync the changed attributes.
971
     *
972
     * @return $this
973
     */
974
    public function syncChanges()
975
    {
976
        $this->changes = $this->getDirty();
977
978
        return $this;
979
    }
980
981
    /**
982
     * Determine if the model or given attribute(s) have been modified.
983
     *
984
     * @param  array|string|null  $attributes
985
     * @return bool
986
     */
987
    public function isDirty($attributes = null)
988
    {
989
        return $this->hasChanges(
990
            $this->getDirty(), is_array($attributes) ? $attributes : func_get_args()
991
        );
992
    }
993
994
    /**
995
     * Determine if the model or given attribute(s) have remained the same.
996
     *
997
     * @param  array|string|null  $attributes
998
     * @return bool
999
     */
1000
    public function isClean($attributes = null)
0 ignored issues
show
Unused Code introduced by
The parameter $attributes is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1001
    {
1002
        return ! $this->isDirty(...func_get_args());
1003
    }
1004
1005
    /**
1006
     * Determine if the model or given attribute(s) have been modified.
1007
     *
1008
     * @param  array|string|null  $attributes
1009
     * @return bool
1010
     */
1011
    public function wasChanged($attributes = null)
1012
    {
1013
        return $this->hasChanges(
1014
            $this->getChanges(), is_array($attributes) ? $attributes : func_get_args()
1015
        );
1016
    }
1017
1018
    /**
1019
     * Determine if the given attributes were changed.
1020
     *
1021
     * @param  array  $changes
1022
     * @param  array|string|null  $attributes
1023
     * @return bool
1024
     */
1025
    protected function hasChanges($changes, $attributes = null)
1026
    {
1027
        // If no specific attributes were provided, we will just see if the dirty array
1028
        // already contains any attributes. If it does we will just return that this
1029
        // count is greater than zero. Else, we need to check specific attributes.
1030
        if (empty($attributes)) {
1031
            return count($changes) > 0;
1032
        }
1033
1034
        // Here we will spin through every attribute and see if this is in the array of
1035
        // dirty attributes. If it is, we will return true and if we make it through
1036
        // all of the attributes for the entire array we will return false at end.
1037
        foreach (Arr::wrap($attributes) as $attribute) {
1038
            if (array_key_exists($attribute, $changes)) {
1039
                return true;
1040
            }
1041
        }
1042
1043
        return false;
1044
    }
1045
1046
    /**
1047
     * Get the attributes that have been changed since last sync.
1048
     *
1049
     * @return array
1050
     */
1051
    public function getDirty()
1052
    {
1053
        $dirty = [];
1054
1055
        foreach ($this->getAttributes() as $key => $value) {
1056
            if (! $this->originalIsEquivalent($key, $value)) {
1057
                $dirty[$key] = $value;
1058
            }
1059
        }
1060
1061
        return $dirty;
1062
    }
1063
1064
    /**
1065
     * Get the attributes that were changed.
1066
     *
1067
     * @return array
1068
     */
1069
    public function getChanges()
1070
    {
1071
        return $this->changes;
1072
    }
1073
1074
    /**
1075
     * Determine if the new and old values for a given key are equivalent.
1076
     *
1077
     * @param  string $key
1078
     * @param  mixed  $current
1079
     * @return bool
1080
     */
1081
    protected function originalIsEquivalent($key, $current)
1082
    {
1083
        if (! array_key_exists($key, $this->original)) {
1084
            return false;
1085
        }
1086
1087
        $original = $this->getOriginal($key);
1088
1089
        if ($current === $original) {
1090
            return true;
1091
        } elseif (is_null($current)) {
1092
            return false;
1093
        } elseif ($this->isDateAttribute($key)) {
1094
            return $this->fromDateTime($current) ===
1095
                   $this->fromDateTime($original);
1096
        } elseif ($this->hasCast($key)) {
1097
            return $this->castAttribute($key, $current) ===
1098
                   $this->castAttribute($key, $original);
1099
        }
1100
1101
        return is_numeric($current) && is_numeric($original)
1102
                && strcmp((string) $current, (string) $original) === 0;
1103
    }
1104
1105
    /**
1106
     * Append attributes to query when building a query.
1107
     *
1108
     * @param  array|string  $attributes
1109
     * @return $this
1110
     */
1111
    public function append($attributes)
1112
    {
1113
        $this->appends = array_unique(
1114
            array_merge($this->appends, is_string($attributes) ? func_get_args() : $attributes)
1115
        );
1116
1117
        return $this;
1118
    }
1119
1120
    /**
1121
     * Set the accessors to append to model arrays.
1122
     *
1123
     * @param  array  $appends
1124
     * @return $this
1125
     */
1126
    public function setAppends(array $appends)
1127
    {
1128
        $this->appends = $appends;
1129
1130
        return $this;
1131
    }
1132
1133
    /**
1134
     * Get the mutated attributes for a given instance.
1135
     *
1136
     * @return array
1137
     */
1138
    public function getMutatedAttributes()
1139
    {
1140
        $class = static::class;
1141
1142
        if (! isset(static::$mutatorCache[$class])) {
1143
            static::cacheMutatedAttributes($class);
1144
        }
1145
1146
        return static::$mutatorCache[$class];
1147
    }
1148
1149
    /**
1150
     * Extract and cache all the mutated attributes of a class.
1151
     *
1152
     * @param  string  $class
1153
     * @return void
1154
     */
1155
    public static function cacheMutatedAttributes($class)
1156
    {
1157
        static::$mutatorCache[$class] = collect(static::getMutatorMethods($class))->map(function ($match) {
1158
            return lcfirst(static::$snakeAttributes ? Str::snake($match) : $match);
1159
        })->all();
1160
    }
1161
1162
    /**
1163
     * Get all of the attribute mutator methods.
1164
     *
1165
     * @param  mixed  $class
1166
     * @return array
1167
     */
1168
    protected static function getMutatorMethods($class)
1169
    {
1170
        preg_match_all('/(?<=^|;)get([^;]+?)Attribute(;|$)/', implode(';', get_class_methods($class)), $matches);
1171
1172
        return $matches[1];
1173
    }
1174
}
1175