Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 148 | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Illuminate\Database\Eloquent\Concerns;
4
 
5
use BackedEnum;
150 lars 6
use Brick\Math\BigDecimal;
7
use Brick\Math\Exception\MathException as BrickMathException;
8
use Brick\Math\RoundingMode;
148 lars 9
use Carbon\CarbonImmutable;
10
use Carbon\CarbonInterface;
11
use DateTimeImmutable;
12
use DateTimeInterface;
13
use Illuminate\Contracts\Database\Eloquent\Castable;
14
use Illuminate\Contracts\Database\Eloquent\CastsInboundAttributes;
15
use Illuminate\Contracts\Support\Arrayable;
16
use Illuminate\Database\Eloquent\Casts\AsArrayObject;
17
use Illuminate\Database\Eloquent\Casts\AsCollection;
18
use Illuminate\Database\Eloquent\Casts\AsEncryptedArrayObject;
19
use Illuminate\Database\Eloquent\Casts\AsEncryptedCollection;
20
use Illuminate\Database\Eloquent\Casts\Attribute;
21
use Illuminate\Database\Eloquent\InvalidCastException;
22
use Illuminate\Database\Eloquent\JsonEncodingException;
23
use Illuminate\Database\Eloquent\MissingAttributeException;
24
use Illuminate\Database\Eloquent\Relations\Relation;
25
use Illuminate\Database\LazyLoadingViolationException;
26
use Illuminate\Support\Arr;
27
use Illuminate\Support\Carbon;
28
use Illuminate\Support\Collection as BaseCollection;
150 lars 29
use Illuminate\Support\Exceptions\MathException;
148 lars 30
use Illuminate\Support\Facades\Crypt;
31
use Illuminate\Support\Facades\Date;
32
use Illuminate\Support\Str;
33
use InvalidArgumentException;
34
use LogicException;
35
use ReflectionClass;
36
use ReflectionMethod;
37
use ReflectionNamedType;
38
 
39
trait HasAttributes
40
{
41
    /**
42
     * The model's attributes.
43
     *
44
     * @var array
45
     */
46
    protected $attributes = [];
47
 
48
    /**
49
     * The model attribute's original state.
50
     *
51
     * @var array
52
     */
53
    protected $original = [];
54
 
55
    /**
56
     * The changed model attributes.
57
     *
58
     * @var array
59
     */
60
    protected $changes = [];
61
 
62
    /**
63
     * The attributes that should be cast.
64
     *
65
     * @var array
66
     */
67
    protected $casts = [];
68
 
69
    /**
70
     * The attributes that have been cast using custom classes.
71
     *
72
     * @var array
73
     */
74
    protected $classCastCache = [];
75
 
76
    /**
77
     * The attributes that have been cast using "Attribute" return type mutators.
78
     *
79
     * @var array
80
     */
81
    protected $attributeCastCache = [];
82
 
83
    /**
84
     * The built-in, primitive cast types supported by Eloquent.
85
     *
86
     * @var string[]
87
     */
88
    protected static $primitiveCastTypes = [
89
        'array',
90
        'bool',
91
        'boolean',
92
        'collection',
93
        'custom_datetime',
94
        'date',
95
        'datetime',
96
        'decimal',
97
        'double',
98
        'encrypted',
99
        'encrypted:array',
100
        'encrypted:collection',
101
        'encrypted:json',
102
        'encrypted:object',
103
        'float',
104
        'immutable_date',
105
        'immutable_datetime',
106
        'immutable_custom_datetime',
107
        'int',
108
        'integer',
109
        'json',
110
        'object',
111
        'real',
112
        'string',
113
        'timestamp',
114
    ];
115
 
116
    /**
117
     * The attributes that should be mutated to dates.
118
     *
119
     * @deprecated Use the "casts" property
120
     *
121
     * @var array
122
     */
123
    protected $dates = [];
124
 
125
    /**
126
     * The storage format of the model's date columns.
127
     *
128
     * @var string
129
     */
130
    protected $dateFormat;
131
 
132
    /**
133
     * The accessors to append to the model's array form.
134
     *
135
     * @var array
136
     */
137
    protected $appends = [];
138
 
139
    /**
140
     * Indicates whether attributes are snake cased on arrays.
141
     *
142
     * @var bool
143
     */
144
    public static $snakeAttributes = true;
145
 
146
    /**
147
     * The cache of the mutated attributes for each class.
148
     *
149
     * @var array
150
     */
151
    protected static $mutatorCache = [];
152
 
153
    /**
154
     * The cache of the "Attribute" return type marked mutated attributes for each class.
155
     *
156
     * @var array
157
     */
158
    protected static $attributeMutatorCache = [];
159
 
160
    /**
161
     * The cache of the "Attribute" return type marked mutated, gettable attributes for each class.
162
     *
163
     * @var array
164
     */
165
    protected static $getAttributeMutatorCache = [];
166
 
167
    /**
168
     * The cache of the "Attribute" return type marked mutated, settable attributes for each class.
169
     *
170
     * @var array
171
     */
172
    protected static $setAttributeMutatorCache = [];
173
 
174
    /**
175
     * The cache of the converted cast types.
176
     *
177
     * @var array
178
     */
179
    protected static $castTypeCache = [];
180
 
181
    /**
182
     * The encrypter instance that is used to encrypt attributes.
183
     *
184
     * @var \Illuminate\Contracts\Encryption\Encrypter|null
185
     */
186
    public static $encrypter;
187
 
188
    /**
189
     * Convert the model's attributes to an array.
190
     *
191
     * @return array
192
     */
193
    public function attributesToArray()
194
    {
195
        // If an attribute is a date, we will cast it to a string after converting it
196
        // to a DateTime / Carbon instance. This is so we will get some consistent
197
        // formatting while accessing attributes vs. arraying / JSONing a model.
198
        $attributes = $this->addDateAttributesToArray(
199
            $attributes = $this->getArrayableAttributes()
200
        );
201
 
202
        $attributes = $this->addMutatedAttributesToArray(
203
            $attributes, $mutatedAttributes = $this->getMutatedAttributes()
204
        );
205
 
206
        // Next we will handle any casts that have been setup for this model and cast
207
        // the values to their appropriate type. If the attribute has a mutator we
208
        // will not perform the cast on those attributes to avoid any confusion.
209
        $attributes = $this->addCastAttributesToArray(
210
            $attributes, $mutatedAttributes
211
        );
212
 
213
        // Here we will grab all of the appended, calculated attributes to this model
214
        // as these attributes are not really in the attributes array, but are run
215
        // when we need to array or JSON the model for convenience to the coder.
216
        foreach ($this->getArrayableAppends() as $key) {
217
            $attributes[$key] = $this->mutateAttributeForArray($key, null);
218
        }
219
 
220
        return $attributes;
221
    }
222
 
223
    /**
224
     * Add the date attributes to the attributes array.
225
     *
226
     * @param  array  $attributes
227
     * @return array
228
     */
229
    protected function addDateAttributesToArray(array $attributes)
230
    {
231
        foreach ($this->getDates() as $key) {
232
            if (! isset($attributes[$key])) {
233
                continue;
234
            }
235
 
236
            $attributes[$key] = $this->serializeDate(
237
                $this->asDateTime($attributes[$key])
238
            );
239
        }
240
 
241
        return $attributes;
242
    }
243
 
244
    /**
245
     * Add the mutated attributes to the attributes array.
246
     *
247
     * @param  array  $attributes
248
     * @param  array  $mutatedAttributes
249
     * @return array
250
     */
251
    protected function addMutatedAttributesToArray(array $attributes, array $mutatedAttributes)
252
    {
253
        foreach ($mutatedAttributes as $key) {
254
            // We want to spin through all the mutated attributes for this model and call
255
            // the mutator for the attribute. We cache off every mutated attributes so
256
            // we don't have to constantly check on attributes that actually change.
257
            if (! array_key_exists($key, $attributes)) {
258
                continue;
259
            }
260
 
261
            // Next, we will call the mutator for this attribute so that we can get these
262
            // mutated attribute's actual values. After we finish mutating each of the
263
            // attributes we will return this final array of the mutated attributes.
264
            $attributes[$key] = $this->mutateAttributeForArray(
265
                $key, $attributes[$key]
266
            );
267
        }
268
 
269
        return $attributes;
270
    }
271
 
272
    /**
273
     * Add the casted attributes to the attributes array.
274
     *
275
     * @param  array  $attributes
276
     * @param  array  $mutatedAttributes
277
     * @return array
278
     */
279
    protected function addCastAttributesToArray(array $attributes, array $mutatedAttributes)
280
    {
281
        foreach ($this->getCasts() as $key => $value) {
282
            if (! array_key_exists($key, $attributes) ||
283
                in_array($key, $mutatedAttributes)) {
284
                continue;
285
            }
286
 
287
            // Here we will cast the attribute. Then, if the cast is a date or datetime cast
288
            // then we will serialize the date for the array. This will convert the dates
289
            // to strings based on the date format specified for these Eloquent models.
290
            $attributes[$key] = $this->castAttribute(
291
                $key, $attributes[$key]
292
            );
293
 
294
            // If the attribute cast was a date or a datetime, we will serialize the date as
295
            // a string. This allows the developers to customize how dates are serialized
296
            // into an array without affecting how they are persisted into the storage.
297
            if (isset($attributes[$key]) && in_array($value, ['date', 'datetime', 'immutable_date', 'immutable_datetime'])) {
298
                $attributes[$key] = $this->serializeDate($attributes[$key]);
299
            }
300
 
301
            if (isset($attributes[$key]) && ($this->isCustomDateTimeCast($value) ||
302
                $this->isImmutableCustomDateTimeCast($value))) {
303
                $attributes[$key] = $attributes[$key]->format(explode(':', $value, 2)[1]);
304
            }
305
 
306
            if ($attributes[$key] instanceof DateTimeInterface &&
307
                $this->isClassCastable($key)) {
308
                $attributes[$key] = $this->serializeDate($attributes[$key]);
309
            }
310
 
311
            if (isset($attributes[$key]) && $this->isClassSerializable($key)) {
312
                $attributes[$key] = $this->serializeClassCastableAttribute($key, $attributes[$key]);
313
            }
314
 
315
            if ($this->isEnumCastable($key) && (! ($attributes[$key] ?? null) instanceof Arrayable)) {
316
                $attributes[$key] = isset($attributes[$key]) ? $this->getStorableEnumValue($attributes[$key]) : null;
317
            }
318
 
319
            if ($attributes[$key] instanceof Arrayable) {
320
                $attributes[$key] = $attributes[$key]->toArray();
321
            }
322
        }
323
 
324
        return $attributes;
325
    }
326
 
327
    /**
328
     * Get an attribute array of all arrayable attributes.
329
     *
330
     * @return array
331
     */
332
    protected function getArrayableAttributes()
333
    {
334
        return $this->getArrayableItems($this->getAttributes());
335
    }
336
 
337
    /**
338
     * Get all of the appendable values that are arrayable.
339
     *
340
     * @return array
341
     */
342
    protected function getArrayableAppends()
343
    {
344
        if (! count($this->appends)) {
345
            return [];
346
        }
347
 
348
        return $this->getArrayableItems(
349
            array_combine($this->appends, $this->appends)
350
        );
351
    }
352
 
353
    /**
354
     * Get the model's relationships in array form.
355
     *
356
     * @return array
357
     */
358
    public function relationsToArray()
359
    {
360
        $attributes = [];
361
 
362
        foreach ($this->getArrayableRelations() as $key => $value) {
363
            // If the values implement the Arrayable interface we can just call this
364
            // toArray method on the instances which will convert both models and
365
            // collections to their proper array form and we'll set the values.
366
            if ($value instanceof Arrayable) {
367
                $relation = $value->toArray();
368
            }
369
 
370
            // If the value is null, we'll still go ahead and set it in this list of
371
            // attributes, since null is used to represent empty relationships if
372
            // it has a has one or belongs to type relationships on the models.
373
            elseif (is_null($value)) {
374
                $relation = $value;
375
            }
376
 
377
            // If the relationships snake-casing is enabled, we will snake case this
378
            // key so that the relation attribute is snake cased in this returned
379
            // array to the developers, making this consistent with attributes.
380
            if (static::$snakeAttributes) {
381
                $key = Str::snake($key);
382
            }
383
 
384
            // If the relation value has been set, we will set it on this attributes
385
            // list for returning. If it was not arrayable or null, we'll not set
386
            // the value on the array because it is some type of invalid value.
387
            if (isset($relation) || is_null($value)) {
388
                $attributes[$key] = $relation;
389
            }
390
 
391
            unset($relation);
392
        }
393
 
394
        return $attributes;
395
    }
396
 
397
    /**
398
     * Get an attribute array of all arrayable relations.
399
     *
400
     * @return array
401
     */
402
    protected function getArrayableRelations()
403
    {
404
        return $this->getArrayableItems($this->relations);
405
    }
406
 
407
    /**
408
     * Get an attribute array of all arrayable values.
409
     *
410
     * @param  array  $values
411
     * @return array
412
     */
413
    protected function getArrayableItems(array $values)
414
    {
415
        if (count($this->getVisible()) > 0) {
416
            $values = array_intersect_key($values, array_flip($this->getVisible()));
417
        }
418
 
419
        if (count($this->getHidden()) > 0) {
420
            $values = array_diff_key($values, array_flip($this->getHidden()));
421
        }
422
 
423
        return $values;
424
    }
425
 
426
    /**
427
     * Get an attribute from the model.
428
     *
429
     * @param  string  $key
430
     * @return mixed
431
     */
432
    public function getAttribute($key)
433
    {
434
        if (! $key) {
435
            return;
436
        }
437
 
438
        // If the attribute exists in the attribute array or has a "get" mutator we will
439
        // get the attribute's value. Otherwise, we will proceed as if the developers
440
        // are asking for a relationship's value. This covers both types of values.
441
        if (array_key_exists($key, $this->attributes) ||
442
            array_key_exists($key, $this->casts) ||
443
            $this->hasGetMutator($key) ||
444
            $this->hasAttributeMutator($key) ||
445
            $this->isClassCastable($key)) {
446
            return $this->getAttributeValue($key);
447
        }
448
 
449
        // Here we will determine if the model base class itself contains this given key
450
        // since we don't want to treat any of those methods as relationships because
451
        // they are all intended as helper methods and none of these are relations.
452
        if (method_exists(self::class, $key)) {
453
            return $this->throwMissingAttributeExceptionIfApplicable($key);
454
        }
455
 
456
        return $this->isRelation($key) || $this->relationLoaded($key)
457
                    ? $this->getRelationValue($key)
458
                    : $this->throwMissingAttributeExceptionIfApplicable($key);
459
    }
460
 
461
    /**
462
     * Either throw a missing attribute exception or return null depending on Eloquent's configuration.
463
     *
464
     * @param  string  $key
465
     * @return null
466
     *
467
     * @throws \Illuminate\Database\Eloquent\MissingAttributeException
468
     */
469
    protected function throwMissingAttributeExceptionIfApplicable($key)
470
    {
471
        if ($this->exists &&
472
            ! $this->wasRecentlyCreated &&
473
            static::preventsAccessingMissingAttributes()) {
474
            if (isset(static::$missingAttributeViolationCallback)) {
475
                return call_user_func(static::$missingAttributeViolationCallback, $this, $key);
476
            }
477
 
478
            throw new MissingAttributeException($this, $key);
479
        }
480
 
481
        return null;
482
    }
483
 
484
    /**
485
     * Get a plain attribute (not a relationship).
486
     *
487
     * @param  string  $key
488
     * @return mixed
489
     */
490
    public function getAttributeValue($key)
491
    {
492
        return $this->transformModelValue($key, $this->getAttributeFromArray($key));
493
    }
494
 
495
    /**
496
     * Get an attribute from the $attributes array.
497
     *
498
     * @param  string  $key
499
     * @return mixed
500
     */
501
    protected function getAttributeFromArray($key)
502
    {
503
        return $this->getAttributes()[$key] ?? null;
504
    }
505
 
506
    /**
507
     * Get a relationship.
508
     *
509
     * @param  string  $key
510
     * @return mixed
511
     */
512
    public function getRelationValue($key)
513
    {
514
        // If the key already exists in the relationships array, it just means the
515
        // relationship has already been loaded, so we'll just return it out of
516
        // here because there is no need to query within the relations twice.
517
        if ($this->relationLoaded($key)) {
518
            return $this->relations[$key];
519
        }
520
 
521
        if (! $this->isRelation($key)) {
522
            return;
523
        }
524
 
525
        if ($this->preventsLazyLoading) {
526
            $this->handleLazyLoadingViolation($key);
527
        }
528
 
529
        // If the "attribute" exists as a method on the model, we will just assume
530
        // it is a relationship and will load and return results from the query
531
        // and hydrate the relationship's value on the "relationships" array.
532
        return $this->getRelationshipFromMethod($key);
533
    }
534
 
535
    /**
536
     * Determine if the given key is a relationship method on the model.
537
     *
538
     * @param  string  $key
539
     * @return bool
540
     */
541
    public function isRelation($key)
542
    {
543
        if ($this->hasAttributeMutator($key)) {
544
            return false;
545
        }
546
 
547
        return method_exists($this, $key) ||
548
               $this->relationResolver(static::class, $key);
549
    }
550
 
551
    /**
552
     * Handle a lazy loading violation.
553
     *
554
     * @param  string  $key
555
     * @return mixed
556
     */
557
    protected function handleLazyLoadingViolation($key)
558
    {
559
        if (isset(static::$lazyLoadingViolationCallback)) {
560
            return call_user_func(static::$lazyLoadingViolationCallback, $this, $key);
561
        }
562
 
563
        if (! $this->exists || $this->wasRecentlyCreated) {
564
            return;
565
        }
566
 
567
        throw new LazyLoadingViolationException($this, $key);
568
    }
569
 
570
    /**
571
     * Get a relationship value from a method.
572
     *
573
     * @param  string  $method
574
     * @return mixed
575
     *
576
     * @throws \LogicException
577
     */
578
    protected function getRelationshipFromMethod($method)
579
    {
580
        $relation = $this->$method();
581
 
582
        if (! $relation instanceof Relation) {
583
            if (is_null($relation)) {
584
                throw new LogicException(sprintf(
585
                    '%s::%s must return a relationship instance, but "null" was returned. Was the "return" keyword used?', static::class, $method
586
                ));
587
            }
588
 
589
            throw new LogicException(sprintf(
590
                '%s::%s must return a relationship instance.', static::class, $method
591
            ));
592
        }
593
 
594
        return tap($relation->getResults(), function ($results) use ($method) {
595
            $this->setRelation($method, $results);
596
        });
597
    }
598
 
599
    /**
600
     * Determine if a get mutator exists for an attribute.
601
     *
602
     * @param  string  $key
603
     * @return bool
604
     */
605
    public function hasGetMutator($key)
606
    {
607
        return method_exists($this, 'get'.Str::studly($key).'Attribute');
608
    }
609
 
610
    /**
611
     * Determine if a "Attribute" return type marked mutator exists for an attribute.
612
     *
613
     * @param  string  $key
614
     * @return bool
615
     */
616
    public function hasAttributeMutator($key)
617
    {
618
        if (isset(static::$attributeMutatorCache[get_class($this)][$key])) {
619
            return static::$attributeMutatorCache[get_class($this)][$key];
620
        }
621
 
622
        if (! method_exists($this, $method = Str::camel($key))) {
623
            return static::$attributeMutatorCache[get_class($this)][$key] = false;
624
        }
625
 
626
        $returnType = (new ReflectionMethod($this, $method))->getReturnType();
627
 
628
        return static::$attributeMutatorCache[get_class($this)][$key] =
629
                    $returnType instanceof ReflectionNamedType &&
630
                    $returnType->getName() === Attribute::class;
631
    }
632
 
633
    /**
634
     * Determine if a "Attribute" return type marked get mutator exists for an attribute.
635
     *
636
     * @param  string  $key
637
     * @return bool
638
     */
639
    public function hasAttributeGetMutator($key)
640
    {
641
        if (isset(static::$getAttributeMutatorCache[get_class($this)][$key])) {
642
            return static::$getAttributeMutatorCache[get_class($this)][$key];
643
        }
644
 
645
        if (! $this->hasAttributeMutator($key)) {
646
            return static::$getAttributeMutatorCache[get_class($this)][$key] = false;
647
        }
648
 
649
        return static::$getAttributeMutatorCache[get_class($this)][$key] = is_callable($this->{Str::camel($key)}()->get);
650
    }
651
 
652
    /**
653
     * Get the value of an attribute using its mutator.
654
     *
655
     * @param  string  $key
656
     * @param  mixed  $value
657
     * @return mixed
658
     */
659
    protected function mutateAttribute($key, $value)
660
    {
661
        return $this->{'get'.Str::studly($key).'Attribute'}($value);
662
    }
663
 
664
    /**
665
     * Get the value of an "Attribute" return type marked attribute using its mutator.
666
     *
667
     * @param  string  $key
668
     * @param  mixed  $value
669
     * @return mixed
670
     */
671
    protected function mutateAttributeMarkedAttribute($key, $value)
672
    {
673
        if (array_key_exists($key, $this->attributeCastCache)) {
674
            return $this->attributeCastCache[$key];
675
        }
676
 
677
        $attribute = $this->{Str::camel($key)}();
678
 
679
        $value = call_user_func($attribute->get ?: function ($value) {
680
            return $value;
681
        }, $value, $this->attributes);
682
 
683
        if ($attribute->withCaching || (is_object($value) && $attribute->withObjectCaching)) {
684
            $this->attributeCastCache[$key] = $value;
685
        } else {
686
            unset($this->attributeCastCache[$key]);
687
        }
688
 
689
        return $value;
690
    }
691
 
692
    /**
693
     * Get the value of an attribute using its mutator for array conversion.
694
     *
695
     * @param  string  $key
696
     * @param  mixed  $value
697
     * @return mixed
698
     */
699
    protected function mutateAttributeForArray($key, $value)
700
    {
701
        if ($this->isClassCastable($key)) {
702
            $value = $this->getClassCastableAttributeValue($key, $value);
703
        } elseif (isset(static::$getAttributeMutatorCache[get_class($this)][$key]) &&
704
                  static::$getAttributeMutatorCache[get_class($this)][$key] === true) {
705
            $value = $this->mutateAttributeMarkedAttribute($key, $value);
706
 
707
            $value = $value instanceof DateTimeInterface
708
                        ? $this->serializeDate($value)
709
                        : $value;
710
        } else {
711
            $value = $this->mutateAttribute($key, $value);
712
        }
713
 
714
        return $value instanceof Arrayable ? $value->toArray() : $value;
715
    }
716
 
717
    /**
718
     * Merge new casts with existing casts on the model.
719
     *
720
     * @param  array  $casts
721
     * @return $this
722
     */
723
    public function mergeCasts($casts)
724
    {
725
        $this->casts = array_merge($this->casts, $casts);
726
 
727
        return $this;
728
    }
729
 
730
    /**
731
     * Cast an attribute to a native PHP type.
732
     *
733
     * @param  string  $key
734
     * @param  mixed  $value
735
     * @return mixed
736
     */
737
    protected function castAttribute($key, $value)
738
    {
739
        $castType = $this->getCastType($key);
740
 
741
        if (is_null($value) && in_array($castType, static::$primitiveCastTypes)) {
742
            return $value;
743
        }
744
 
745
        // If the key is one of the encrypted castable types, we'll first decrypt
746
        // the value and update the cast type so we may leverage the following
747
        // logic for casting this value to any additionally specified types.
748
        if ($this->isEncryptedCastable($key)) {
749
            $value = $this->fromEncryptedString($value);
750
 
751
            $castType = Str::after($castType, 'encrypted:');
752
        }
753
 
754
        switch ($castType) {
755
            case 'int':
756
            case 'integer':
757
                return (int) $value;
758
            case 'real':
759
            case 'float':
760
            case 'double':
761
                return $this->fromFloat($value);
762
            case 'decimal':
763
                return $this->asDecimal($value, explode(':', $this->getCasts()[$key], 2)[1]);
764
            case 'string':
765
                return (string) $value;
766
            case 'bool':
767
            case 'boolean':
768
                return (bool) $value;
769
            case 'object':
770
                return $this->fromJson($value, true);
771
            case 'array':
772
            case 'json':
773
                return $this->fromJson($value);
774
            case 'collection':
775
                return new BaseCollection($this->fromJson($value));
776
            case 'date':
777
                return $this->asDate($value);
778
            case 'datetime':
779
            case 'custom_datetime':
780
                return $this->asDateTime($value);
781
            case 'immutable_date':
782
                return $this->asDate($value)->toImmutable();
783
            case 'immutable_custom_datetime':
784
            case 'immutable_datetime':
785
                return $this->asDateTime($value)->toImmutable();
786
            case 'timestamp':
787
                return $this->asTimestamp($value);
788
        }
789
 
790
        if ($this->isEnumCastable($key)) {
791
            return $this->getEnumCastableAttributeValue($key, $value);
792
        }
793
 
794
        if ($this->isClassCastable($key)) {
795
            return $this->getClassCastableAttributeValue($key, $value);
796
        }
797
 
798
        return $value;
799
    }
800
 
801
    /**
802
     * Cast the given attribute using a custom cast class.
803
     *
804
     * @param  string  $key
805
     * @param  mixed  $value
806
     * @return mixed
807
     */
808
    protected function getClassCastableAttributeValue($key, $value)
809
    {
810
        if (isset($this->classCastCache[$key])) {
811
            return $this->classCastCache[$key];
812
        } else {
813
            $caster = $this->resolveCasterClass($key);
814
 
815
            $value = $caster instanceof CastsInboundAttributes
816
                ? $value
817
                : $caster->get($this, $key, $value, $this->attributes);
818
 
819
            if ($caster instanceof CastsInboundAttributes || ! is_object($value)) {
820
                unset($this->classCastCache[$key]);
821
            } else {
822
                $this->classCastCache[$key] = $value;
823
            }
824
 
825
            return $value;
826
        }
827
    }
828
 
829
    /**
830
     * Cast the given attribute to an enum.
831
     *
832
     * @param  string  $key
833
     * @param  mixed  $value
834
     * @return mixed
835
     */
836
    protected function getEnumCastableAttributeValue($key, $value)
837
    {
838
        if (is_null($value)) {
839
            return;
840
        }
841
 
842
        $castType = $this->getCasts()[$key];
843
 
844
        if ($value instanceof $castType) {
845
            return $value;
846
        }
847
 
848
        return $this->getEnumCaseFromValue($castType, $value);
849
    }
850
 
851
    /**
852
     * Get the type of cast for a model attribute.
853
     *
854
     * @param  string  $key
855
     * @return string
856
     */
857
    protected function getCastType($key)
858
    {
859
        $castType = $this->getCasts()[$key];
860
 
861
        if (isset(static::$castTypeCache[$castType])) {
862
            return static::$castTypeCache[$castType];
863
        }
864
 
865
        if ($this->isCustomDateTimeCast($castType)) {
866
            $convertedCastType = 'custom_datetime';
867
        } elseif ($this->isImmutableCustomDateTimeCast($castType)) {
868
            $convertedCastType = 'immutable_custom_datetime';
869
        } elseif ($this->isDecimalCast($castType)) {
870
            $convertedCastType = 'decimal';
871
        } else {
872
            $convertedCastType = trim(strtolower($castType));
873
        }
874
 
875
        return static::$castTypeCache[$castType] = $convertedCastType;
876
    }
877
 
878
    /**
879
     * Increment or decrement the given attribute using the custom cast class.
880
     *
881
     * @param  string  $method
882
     * @param  string  $key
883
     * @param  mixed  $value
884
     * @return mixed
885
     */
886
    protected function deviateClassCastableAttribute($method, $key, $value)
887
    {
888
        return $this->resolveCasterClass($key)->{$method}(
889
            $this, $key, $value, $this->attributes
890
        );
891
    }
892
 
893
    /**
894
     * Serialize the given attribute using the custom cast class.
895
     *
896
     * @param  string  $key
897
     * @param  mixed  $value
898
     * @return mixed
899
     */
900
    protected function serializeClassCastableAttribute($key, $value)
901
    {
902
        return $this->resolveCasterClass($key)->serialize(
903
            $this, $key, $value, $this->attributes
904
        );
905
    }
906
 
907
    /**
908
     * Determine if the cast type is a custom date time cast.
909
     *
910
     * @param  string  $cast
911
     * @return bool
912
     */
913
    protected function isCustomDateTimeCast($cast)
914
    {
915
        return str_starts_with($cast, 'date:') ||
916
                str_starts_with($cast, 'datetime:');
917
    }
918
 
919
    /**
920
     * Determine if the cast type is an immutable custom date time cast.
921
     *
922
     * @param  string  $cast
923
     * @return bool
924
     */
925
    protected function isImmutableCustomDateTimeCast($cast)
926
    {
927
        return str_starts_with($cast, 'immutable_date:') ||
928
                str_starts_with($cast, 'immutable_datetime:');
929
    }
930
 
931
    /**
932
     * Determine if the cast type is a decimal cast.
933
     *
934
     * @param  string  $cast
935
     * @return bool
936
     */
937
    protected function isDecimalCast($cast)
938
    {
939
        return str_starts_with($cast, 'decimal:');
940
    }
941
 
942
    /**
943
     * Set a given attribute on the model.
944
     *
945
     * @param  string  $key
946
     * @param  mixed  $value
947
     * @return mixed
948
     */
949
    public function setAttribute($key, $value)
950
    {
951
        // First we will check for the presence of a mutator for the set operation
952
        // which simply lets the developers tweak the attribute as it is set on
953
        // this model, such as "json_encoding" a listing of data for storage.
954
        if ($this->hasSetMutator($key)) {
955
            return $this->setMutatedAttributeValue($key, $value);
956
        } elseif ($this->hasAttributeSetMutator($key)) {
957
            return $this->setAttributeMarkedMutatedAttributeValue($key, $value);
958
        }
959
 
960
        // If an attribute is listed as a "date", we'll convert it from a DateTime
961
        // instance into a form proper for storage on the database tables using
962
        // the connection grammar's date format. We will auto set the values.
963
        elseif (! is_null($value) && $this->isDateAttribute($key)) {
964
            $value = $this->fromDateTime($value);
965
        }
966
 
967
        if ($this->isEnumCastable($key)) {
968
            $this->setEnumCastableAttribute($key, $value);
969
 
970
            return $this;
971
        }
972
 
973
        if ($this->isClassCastable($key)) {
974
            $this->setClassCastableAttribute($key, $value);
975
 
976
            return $this;
977
        }
978
 
979
        if (! is_null($value) && $this->isJsonCastable($key)) {
980
            $value = $this->castAttributeAsJson($key, $value);
981
        }
982
 
983
        // If this attribute contains a JSON ->, we'll set the proper value in the
984
        // attribute's underlying array. This takes care of properly nesting an
985
        // attribute in the array's value in the case of deeply nested items.
986
        if (str_contains($key, '->')) {
987
            return $this->fillJsonAttribute($key, $value);
988
        }
989
 
990
        if (! is_null($value) && $this->isEncryptedCastable($key)) {
991
            $value = $this->castAttributeAsEncryptedString($key, $value);
992
        }
993
 
994
        $this->attributes[$key] = $value;
995
 
996
        return $this;
997
    }
998
 
999
    /**
1000
     * Determine if a set mutator exists for an attribute.
1001
     *
1002
     * @param  string  $key
1003
     * @return bool
1004
     */
1005
    public function hasSetMutator($key)
1006
    {
1007
        return method_exists($this, 'set'.Str::studly($key).'Attribute');
1008
    }
1009
 
1010
    /**
1011
     * Determine if an "Attribute" return type marked set mutator exists for an attribute.
1012
     *
1013
     * @param  string  $key
1014
     * @return bool
1015
     */
1016
    public function hasAttributeSetMutator($key)
1017
    {
1018
        $class = get_class($this);
1019
 
1020
        if (isset(static::$setAttributeMutatorCache[$class][$key])) {
1021
            return static::$setAttributeMutatorCache[$class][$key];
1022
        }
1023
 
1024
        if (! method_exists($this, $method = Str::camel($key))) {
1025
            return static::$setAttributeMutatorCache[$class][$key] = false;
1026
        }
1027
 
1028
        $returnType = (new ReflectionMethod($this, $method))->getReturnType();
1029
 
1030
        return static::$setAttributeMutatorCache[$class][$key] =
1031
                    $returnType instanceof ReflectionNamedType &&
1032
                    $returnType->getName() === Attribute::class &&
1033
                    is_callable($this->{$method}()->set);
1034
    }
1035
 
1036
    /**
1037
     * Set the value of an attribute using its mutator.
1038
     *
1039
     * @param  string  $key
1040
     * @param  mixed  $value
1041
     * @return mixed
1042
     */
1043
    protected function setMutatedAttributeValue($key, $value)
1044
    {
1045
        return $this->{'set'.Str::studly($key).'Attribute'}($value);
1046
    }
1047
 
1048
    /**
1049
     * Set the value of a "Attribute" return type marked attribute using its mutator.
1050
     *
1051
     * @param  string  $key
1052
     * @param  mixed  $value
1053
     * @return mixed
1054
     */
1055
    protected function setAttributeMarkedMutatedAttributeValue($key, $value)
1056
    {
1057
        $attribute = $this->{Str::camel($key)}();
1058
 
1059
        $callback = $attribute->set ?: function ($value) use ($key) {
1060
            $this->attributes[$key] = $value;
1061
        };
1062
 
1063
        $this->attributes = array_merge(
1064
            $this->attributes,
1065
            $this->normalizeCastClassResponse(
1066
                $key, $callback($value, $this->attributes)
1067
            )
1068
        );
1069
 
1070
        if ($attribute->withCaching || (is_object($value) && $attribute->withObjectCaching)) {
1071
            $this->attributeCastCache[$key] = $value;
1072
        } else {
1073
            unset($this->attributeCastCache[$key]);
1074
        }
1075
 
1076
        return $this;
1077
    }
1078
 
1079
    /**
1080
     * Determine if the given attribute is a date or date castable.
1081
     *
1082
     * @param  string  $key
1083
     * @return bool
1084
     */
1085
    protected function isDateAttribute($key)
1086
    {
1087
        return in_array($key, $this->getDates(), true) ||
1088
            $this->isDateCastable($key);
1089
    }
1090
 
1091
    /**
1092
     * Set a given JSON attribute on the model.
1093
     *
1094
     * @param  string  $key
1095
     * @param  mixed  $value
1096
     * @return $this
1097
     */
1098
    public function fillJsonAttribute($key, $value)
1099
    {
1100
        [$key, $path] = explode('->', $key, 2);
1101
 
1102
        $value = $this->asJson($this->getArrayAttributeWithValue(
1103
            $path, $key, $value
1104
        ));
1105
 
1106
        $this->attributes[$key] = $this->isEncryptedCastable($key)
1107
            ? $this->castAttributeAsEncryptedString($key, $value)
1108
            : $value;
1109
 
1110
        if ($this->isClassCastable($key)) {
1111
            unset($this->classCastCache[$key]);
1112
        }
1113
 
1114
        return $this;
1115
    }
1116
 
1117
    /**
1118
     * Set the value of a class castable attribute.
1119
     *
1120
     * @param  string  $key
1121
     * @param  mixed  $value
1122
     * @return void
1123
     */
1124
    protected function setClassCastableAttribute($key, $value)
1125
    {
1126
        $caster = $this->resolveCasterClass($key);
1127
 
1128
        $this->attributes = array_replace(
1129
            $this->attributes,
1130
            $this->normalizeCastClassResponse($key, $caster->set(
1131
                $this, $key, $value, $this->attributes
1132
            ))
1133
        );
1134
 
1135
        if ($caster instanceof CastsInboundAttributes || ! is_object($value)) {
1136
            unset($this->classCastCache[$key]);
1137
        } else {
1138
            $this->classCastCache[$key] = $value;
1139
        }
1140
    }
1141
 
1142
    /**
1143
     * Set the value of an enum castable attribute.
1144
     *
1145
     * @param  string  $key
1146
     * @param  \UnitEnum|string|int  $value
1147
     * @return void
1148
     */
1149
    protected function setEnumCastableAttribute($key, $value)
1150
    {
1151
        $enumClass = $this->getCasts()[$key];
1152
 
1153
        if (! isset($value)) {
1154
            $this->attributes[$key] = null;
1155
        } elseif (is_object($value)) {
1156
            $this->attributes[$key] = $this->getStorableEnumValue($value);
1157
        } else {
1158
            $this->attributes[$key] = $this->getStorableEnumValue(
1159
                $this->getEnumCaseFromValue($enumClass, $value)
1160
            );
1161
        }
1162
    }
1163
 
1164
    /**
1165
     * Get an enum case instance from a given class and value.
1166
     *
1167
     * @param  string  $enumClass
1168
     * @param  string|int  $value
1169
     * @return \UnitEnum|\BackedEnum
1170
     */
1171
    protected function getEnumCaseFromValue($enumClass, $value)
1172
    {
1173
        return is_subclass_of($enumClass, BackedEnum::class)
1174
                ? $enumClass::from($value)
1175
                : constant($enumClass.'::'.$value);
1176
    }
1177
 
1178
    /**
1179
     * Get the storable value from the given enum.
1180
     *
1181
     * @param  \UnitEnum|\BackedEnum  $value
1182
     * @return string|int
1183
     */
1184
    protected function getStorableEnumValue($value)
1185
    {
1186
        return $value instanceof BackedEnum
1187
                ? $value->value
1188
                : $value->name;
1189
    }
1190
 
1191
    /**
1192
     * Get an array attribute with the given key and value set.
1193
     *
1194
     * @param  string  $path
1195
     * @param  string  $key
1196
     * @param  mixed  $value
1197
     * @return $this
1198
     */
1199
    protected function getArrayAttributeWithValue($path, $key, $value)
1200
    {
1201
        return tap($this->getArrayAttributeByKey($key), function (&$array) use ($path, $value) {
1202
            Arr::set($array, str_replace('->', '.', $path), $value);
1203
        });
1204
    }
1205
 
1206
    /**
1207
     * Get an array attribute or return an empty array if it is not set.
1208
     *
1209
     * @param  string  $key
1210
     * @return array
1211
     */
1212
    protected function getArrayAttributeByKey($key)
1213
    {
1214
        if (! isset($this->attributes[$key])) {
1215
            return [];
1216
        }
1217
 
1218
        return $this->fromJson(
1219
            $this->isEncryptedCastable($key)
1220
                ? $this->fromEncryptedString($this->attributes[$key])
1221
                : $this->attributes[$key]
1222
        );
1223
    }
1224
 
1225
    /**
1226
     * Cast the given attribute to JSON.
1227
     *
1228
     * @param  string  $key
1229
     * @param  mixed  $value
1230
     * @return string
1231
     */
1232
    protected function castAttributeAsJson($key, $value)
1233
    {
1234
        $value = $this->asJson($value);
1235
 
1236
        if ($value === false) {
1237
            throw JsonEncodingException::forAttribute(
1238
                $this, $key, json_last_error_msg()
1239
            );
1240
        }
1241
 
1242
        return $value;
1243
    }
1244
 
1245
    /**
1246
     * Encode the given value as JSON.
1247
     *
1248
     * @param  mixed  $value
1249
     * @return string
1250
     */
1251
    protected function asJson($value)
1252
    {
1253
        return json_encode($value);
1254
    }
1255
 
1256
    /**
1257
     * Decode the given JSON back into an array or object.
1258
     *
1259
     * @param  string  $value
1260
     * @param  bool  $asObject
1261
     * @return mixed
1262
     */
1263
    public function fromJson($value, $asObject = false)
1264
    {
1265
        return json_decode($value ?? '', ! $asObject);
1266
    }
1267
 
1268
    /**
1269
     * Decrypt the given encrypted string.
1270
     *
1271
     * @param  string  $value
1272
     * @return mixed
1273
     */
1274
    public function fromEncryptedString($value)
1275
    {
1276
        return (static::$encrypter ?? Crypt::getFacadeRoot())->decrypt($value, false);
1277
    }
1278
 
1279
    /**
1280
     * Cast the given attribute to an encrypted string.
1281
     *
1282
     * @param  string  $key
1283
     * @param  mixed  $value
1284
     * @return string
1285
     */
1286
    protected function castAttributeAsEncryptedString($key, $value)
1287
    {
1288
        return (static::$encrypter ?? Crypt::getFacadeRoot())->encrypt($value, false);
1289
    }
1290
 
1291
    /**
1292
     * Set the encrypter instance that will be used to encrypt attributes.
1293
     *
1294
     * @param  \Illuminate\Contracts\Encryption\Encrypter|null  $encrypter
1295
     * @return void
1296
     */
1297
    public static function encryptUsing($encrypter)
1298
    {
1299
        static::$encrypter = $encrypter;
1300
    }
1301
 
1302
    /**
1303
     * Decode the given float.
1304
     *
1305
     * @param  mixed  $value
1306
     * @return mixed
1307
     */
1308
    public function fromFloat($value)
1309
    {
1310
        return match ((string) $value) {
1311
            'Infinity' => INF,
1312
            '-Infinity' => -INF,
1313
            'NaN' => NAN,
1314
            default => (float) $value,
1315
        };
1316
    }
1317
 
1318
    /**
1319
     * Return a decimal as string.
1320
     *
1321
     * @param  float|string  $value
1322
     * @param  int  $decimals
1323
     * @return string
1324
     */
1325
    protected function asDecimal($value, $decimals)
1326
    {
150 lars 1327
        try {
1328
            return (string) BigDecimal::of($value)->toScale($decimals, RoundingMode::HALF_UP);
1329
        } catch (BrickMathException $e) {
1330
            throw new MathException('Unable to cast value to a decimal.', previous: $e);
148 lars 1331
        }
1332
    }
1333
 
1334
    /**
1335
     * Return a timestamp as DateTime object with time set to 00:00:00.
1336
     *
1337
     * @param  mixed  $value
1338
     * @return \Illuminate\Support\Carbon
1339
     */
1340
    protected function asDate($value)
1341
    {
1342
        return $this->asDateTime($value)->startOfDay();
1343
    }
1344
 
1345
    /**
1346
     * Return a timestamp as DateTime object.
1347
     *
1348
     * @param  mixed  $value
1349
     * @return \Illuminate\Support\Carbon
1350
     */
1351
    protected function asDateTime($value)
1352
    {
1353
        // If this value is already a Carbon instance, we shall just return it as is.
1354
        // This prevents us having to re-instantiate a Carbon instance when we know
1355
        // it already is one, which wouldn't be fulfilled by the DateTime check.
1356
        if ($value instanceof CarbonInterface) {
1357
            return Date::instance($value);
1358
        }
1359
 
1360
        // If the value is already a DateTime instance, we will just skip the rest of
1361
        // these checks since they will be a waste of time, and hinder performance
1362
        // when checking the field. We will just return the DateTime right away.
1363
        if ($value instanceof DateTimeInterface) {
1364
            return Date::parse(
1365
                $value->format('Y-m-d H:i:s.u'), $value->getTimezone()
1366
            );
1367
        }
1368
 
1369
        // If this value is an integer, we will assume it is a UNIX timestamp's value
1370
        // and format a Carbon object from this timestamp. This allows flexibility
1371
        // when defining your date fields as they might be UNIX timestamps here.
1372
        if (is_numeric($value)) {
1373
            return Date::createFromTimestamp($value);
1374
        }
1375
 
1376
        // If the value is in simply year, month, day format, we will instantiate the
1377
        // Carbon instances from that format. Again, this provides for simple date
1378
        // fields on the database, while still supporting Carbonized conversion.
1379
        if ($this->isStandardDateFormat($value)) {
1380
            return Date::instance(Carbon::createFromFormat('Y-m-d', $value)->startOfDay());
1381
        }
1382
 
1383
        $format = $this->getDateFormat();
1384
 
1385
        // Finally, we will just assume this date is in the format used by default on
1386
        // the database connection and use that format to create the Carbon object
1387
        // that is returned back out to the developers after we convert it here.
1388
        try {
1389
            $date = Date::createFromFormat($format, $value);
1390
        } catch (InvalidArgumentException $e) {
1391
            $date = false;
1392
        }
1393
 
1394
        return $date ?: Date::parse($value);
1395
    }
1396
 
1397
    /**
1398
     * Determine if the given value is a standard date format.
1399
     *
1400
     * @param  string  $value
1401
     * @return bool
1402
     */
1403
    protected function isStandardDateFormat($value)
1404
    {
1405
        return preg_match('/^(\d{4})-(\d{1,2})-(\d{1,2})$/', $value);
1406
    }
1407
 
1408
    /**
1409
     * Convert a DateTime to a storable string.
1410
     *
1411
     * @param  mixed  $value
1412
     * @return string|null
1413
     */
1414
    public function fromDateTime($value)
1415
    {
1416
        return empty($value) ? $value : $this->asDateTime($value)->format(
1417
            $this->getDateFormat()
1418
        );
1419
    }
1420
 
1421
    /**
1422
     * Return a timestamp as unix timestamp.
1423
     *
1424
     * @param  mixed  $value
1425
     * @return int
1426
     */
1427
    protected function asTimestamp($value)
1428
    {
1429
        return $this->asDateTime($value)->getTimestamp();
1430
    }
1431
 
1432
    /**
1433
     * Prepare a date for array / JSON serialization.
1434
     *
1435
     * @param  \DateTimeInterface  $date
1436
     * @return string
1437
     */
1438
    protected function serializeDate(DateTimeInterface $date)
1439
    {
1440
        return $date instanceof DateTimeImmutable ?
1441
            CarbonImmutable::instance($date)->toJSON() :
1442
            Carbon::instance($date)->toJSON();
1443
    }
1444
 
1445
    /**
1446
     * Get the attributes that should be converted to dates.
1447
     *
1448
     * @return array
1449
     */
1450
    public function getDates()
1451
    {
1452
        if (! $this->usesTimestamps()) {
1453
            return $this->dates;
1454
        }
1455
 
1456
        $defaults = [
1457
            $this->getCreatedAtColumn(),
1458
            $this->getUpdatedAtColumn(),
1459
        ];
1460
 
1461
        return array_unique(array_merge($this->dates, $defaults));
1462
    }
1463
 
1464
    /**
1465
     * Get the format for database stored dates.
1466
     *
1467
     * @return string
1468
     */
1469
    public function getDateFormat()
1470
    {
1471
        return $this->dateFormat ?: $this->getConnection()->getQueryGrammar()->getDateFormat();
1472
    }
1473
 
1474
    /**
1475
     * Set the date format used by the model.
1476
     *
1477
     * @param  string  $format
1478
     * @return $this
1479
     */
1480
    public function setDateFormat($format)
1481
    {
1482
        $this->dateFormat = $format;
1483
 
1484
        return $this;
1485
    }
1486
 
1487
    /**
1488
     * Determine whether an attribute should be cast to a native type.
1489
     *
1490
     * @param  string  $key
1491
     * @param  array|string|null  $types
1492
     * @return bool
1493
     */
1494
    public function hasCast($key, $types = null)
1495
    {
1496
        if (array_key_exists($key, $this->getCasts())) {
1497
            return $types ? in_array($this->getCastType($key), (array) $types, true) : true;
1498
        }
1499
 
1500
        return false;
1501
    }
1502
 
1503
    /**
1504
     * Get the casts array.
1505
     *
1506
     * @return array
1507
     */
1508
    public function getCasts()
1509
    {
1510
        if ($this->getIncrementing()) {
1511
            return array_merge([$this->getKeyName() => $this->getKeyType()], $this->casts);
1512
        }
1513
 
1514
        return $this->casts;
1515
    }
1516
 
1517
    /**
1518
     * Determine whether a value is Date / DateTime castable for inbound manipulation.
1519
     *
1520
     * @param  string  $key
1521
     * @return bool
1522
     */
1523
    protected function isDateCastable($key)
1524
    {
1525
        return $this->hasCast($key, ['date', 'datetime', 'immutable_date', 'immutable_datetime']);
1526
    }
1527
 
1528
    /**
1529
     * Determine whether a value is Date / DateTime custom-castable for inbound manipulation.
1530
     *
1531
     * @param  string  $key
1532
     * @return bool
1533
     */
1534
    protected function isDateCastableWithCustomFormat($key)
1535
    {
1536
        return $this->hasCast($key, ['custom_datetime', 'immutable_custom_datetime']);
1537
    }
1538
 
1539
    /**
1540
     * Determine whether a value is JSON castable for inbound manipulation.
1541
     *
1542
     * @param  string  $key
1543
     * @return bool
1544
     */
1545
    protected function isJsonCastable($key)
1546
    {
1547
        return $this->hasCast($key, ['array', 'json', 'object', 'collection', 'encrypted:array', 'encrypted:collection', 'encrypted:json', 'encrypted:object']);
1548
    }
1549
 
1550
    /**
1551
     * Determine whether a value is an encrypted castable for inbound manipulation.
1552
     *
1553
     * @param  string  $key
1554
     * @return bool
1555
     */
1556
    protected function isEncryptedCastable($key)
1557
    {
1558
        return $this->hasCast($key, ['encrypted', 'encrypted:array', 'encrypted:collection', 'encrypted:json', 'encrypted:object']);
1559
    }
1560
 
1561
    /**
1562
     * Determine if the given key is cast using a custom class.
1563
     *
1564
     * @param  string  $key
1565
     * @return bool
1566
     *
1567
     * @throws \Illuminate\Database\Eloquent\InvalidCastException
1568
     */
1569
    protected function isClassCastable($key)
1570
    {
1571
        $casts = $this->getCasts();
1572
 
1573
        if (! array_key_exists($key, $casts)) {
1574
            return false;
1575
        }
1576
 
1577
        $castType = $this->parseCasterClass($casts[$key]);
1578
 
1579
        if (in_array($castType, static::$primitiveCastTypes)) {
1580
            return false;
1581
        }
1582
 
1583
        if (class_exists($castType)) {
1584
            return true;
1585
        }
1586
 
1587
        throw new InvalidCastException($this->getModel(), $key, $castType);
1588
    }
1589
 
1590
    /**
1591
     * Determine if the given key is cast using an enum.
1592
     *
1593
     * @param  string  $key
1594
     * @return bool
1595
     */
1596
    protected function isEnumCastable($key)
1597
    {
1598
        $casts = $this->getCasts();
1599
 
1600
        if (! array_key_exists($key, $casts)) {
1601
            return false;
1602
        }
1603
 
1604
        $castType = $casts[$key];
1605
 
1606
        if (in_array($castType, static::$primitiveCastTypes)) {
1607
            return false;
1608
        }
1609
 
1610
        if (function_exists('enum_exists') && enum_exists($castType)) {
1611
            return true;
1612
        }
1613
    }
1614
 
1615
    /**
1616
     * Determine if the key is deviable using a custom class.
1617
     *
1618
     * @param  string  $key
1619
     * @return bool
1620
     *
1621
     * @throws \Illuminate\Database\Eloquent\InvalidCastException
1622
     */
1623
    protected function isClassDeviable($key)
1624
    {
1625
        if (! $this->isClassCastable($key)) {
1626
            return false;
1627
        }
1628
 
1629
        $castType = $this->resolveCasterClass($key);
1630
 
1631
        return method_exists($castType::class, 'increment') && method_exists($castType::class, 'decrement');
1632
    }
1633
 
1634
    /**
1635
     * Determine if the key is serializable using a custom class.
1636
     *
1637
     * @param  string  $key
1638
     * @return bool
1639
     *
1640
     * @throws \Illuminate\Database\Eloquent\InvalidCastException
1641
     */
1642
    protected function isClassSerializable($key)
1643
    {
1644
        return ! $this->isEnumCastable($key) &&
1645
            $this->isClassCastable($key) &&
1646
            method_exists($this->resolveCasterClass($key), 'serialize');
1647
    }
1648
 
1649
    /**
1650
     * Resolve the custom caster class for a given key.
1651
     *
1652
     * @param  string  $key
1653
     * @return mixed
1654
     */
1655
    protected function resolveCasterClass($key)
1656
    {
1657
        $castType = $this->getCasts()[$key];
1658
 
1659
        $arguments = [];
1660
 
1661
        if (is_string($castType) && str_contains($castType, ':')) {
1662
            $segments = explode(':', $castType, 2);
1663
 
1664
            $castType = $segments[0];
1665
            $arguments = explode(',', $segments[1]);
1666
        }
1667
 
1668
        if (is_subclass_of($castType, Castable::class)) {
1669
            $castType = $castType::castUsing($arguments);
1670
        }
1671
 
1672
        if (is_object($castType)) {
1673
            return $castType;
1674
        }
1675
 
1676
        return new $castType(...$arguments);
1677
    }
1678
 
1679
    /**
1680
     * Parse the given caster class, removing any arguments.
1681
     *
1682
     * @param  string  $class
1683
     * @return string
1684
     */
1685
    protected function parseCasterClass($class)
1686
    {
1687
        return ! str_contains($class, ':')
1688
            ? $class
1689
            : explode(':', $class, 2)[0];
1690
    }
1691
 
1692
    /**
1693
     * Merge the cast class and attribute cast attributes back into the model.
1694
     *
1695
     * @return void
1696
     */
1697
    protected function mergeAttributesFromCachedCasts()
1698
    {
1699
        $this->mergeAttributesFromClassCasts();
1700
        $this->mergeAttributesFromAttributeCasts();
1701
    }
1702
 
1703
    /**
1704
     * Merge the cast class attributes back into the model.
1705
     *
1706
     * @return void
1707
     */
1708
    protected function mergeAttributesFromClassCasts()
1709
    {
1710
        foreach ($this->classCastCache as $key => $value) {
1711
            $caster = $this->resolveCasterClass($key);
1712
 
1713
            $this->attributes = array_merge(
1714
                $this->attributes,
1715
                $caster instanceof CastsInboundAttributes
1716
                    ? [$key => $value]
1717
                    : $this->normalizeCastClassResponse($key, $caster->set($this, $key, $value, $this->attributes))
1718
            );
1719
        }
1720
    }
1721
 
1722
    /**
1723
     * Merge the cast class attributes back into the model.
1724
     *
1725
     * @return void
1726
     */
1727
    protected function mergeAttributesFromAttributeCasts()
1728
    {
1729
        foreach ($this->attributeCastCache as $key => $value) {
1730
            $attribute = $this->{Str::camel($key)}();
1731
 
1732
            if ($attribute->get && ! $attribute->set) {
1733
                continue;
1734
            }
1735
 
1736
            $callback = $attribute->set ?: function ($value) use ($key) {
1737
                $this->attributes[$key] = $value;
1738
            };
1739
 
1740
            $this->attributes = array_merge(
1741
                $this->attributes,
1742
                $this->normalizeCastClassResponse(
1743
                    $key, $callback($value, $this->attributes)
1744
                )
1745
            );
1746
        }
1747
    }
1748
 
1749
    /**
1750
     * Normalize the response from a custom class caster.
1751
     *
1752
     * @param  string  $key
1753
     * @param  mixed  $value
1754
     * @return array
1755
     */
1756
    protected function normalizeCastClassResponse($key, $value)
1757
    {
1758
        return is_array($value) ? $value : [$key => $value];
1759
    }
1760
 
1761
    /**
1762
     * Get all of the current attributes on the model.
1763
     *
1764
     * @return array
1765
     */
1766
    public function getAttributes()
1767
    {
1768
        $this->mergeAttributesFromCachedCasts();
1769
 
1770
        return $this->attributes;
1771
    }
1772
 
1773
    /**
1774
     * Get all of the current attributes on the model for an insert operation.
1775
     *
1776
     * @return array
1777
     */
1778
    protected function getAttributesForInsert()
1779
    {
1780
        return $this->getAttributes();
1781
    }
1782
 
1783
    /**
1784
     * Set the array of model attributes. No checking is done.
1785
     *
1786
     * @param  array  $attributes
1787
     * @param  bool  $sync
1788
     * @return $this
1789
     */
1790
    public function setRawAttributes(array $attributes, $sync = false)
1791
    {
1792
        $this->attributes = $attributes;
1793
 
1794
        if ($sync) {
1795
            $this->syncOriginal();
1796
        }
1797
 
1798
        $this->classCastCache = [];
1799
        $this->attributeCastCache = [];
1800
 
1801
        return $this;
1802
    }
1803
 
1804
    /**
1805
     * Get the model's original attribute values.
1806
     *
1807
     * @param  string|null  $key
1808
     * @param  mixed  $default
1809
     * @return mixed|array
1810
     */
1811
    public function getOriginal($key = null, $default = null)
1812
    {
1813
        return (new static)->setRawAttributes(
1814
            $this->original, $sync = true
1815
        )->getOriginalWithoutRewindingModel($key, $default);
1816
    }
1817
 
1818
    /**
1819
     * Get the model's original attribute values.
1820
     *
1821
     * @param  string|null  $key
1822
     * @param  mixed  $default
1823
     * @return mixed|array
1824
     */
1825
    protected function getOriginalWithoutRewindingModel($key = null, $default = null)
1826
    {
1827
        if ($key) {
1828
            return $this->transformModelValue(
1829
                $key, Arr::get($this->original, $key, $default)
1830
            );
1831
        }
1832
 
1833
        return collect($this->original)->mapWithKeys(function ($value, $key) {
1834
            return [$key => $this->transformModelValue($key, $value)];
1835
        })->all();
1836
    }
1837
 
1838
    /**
1839
     * Get the model's raw original attribute values.
1840
     *
1841
     * @param  string|null  $key
1842
     * @param  mixed  $default
1843
     * @return mixed|array
1844
     */
1845
    public function getRawOriginal($key = null, $default = null)
1846
    {
1847
        return Arr::get($this->original, $key, $default);
1848
    }
1849
 
1850
    /**
1851
     * Get a subset of the model's attributes.
1852
     *
1853
     * @param  array|mixed  $attributes
1854
     * @return array
1855
     */
1856
    public function only($attributes)
1857
    {
1858
        $results = [];
1859
 
1860
        foreach (is_array($attributes) ? $attributes : func_get_args() as $attribute) {
1861
            $results[$attribute] = $this->getAttribute($attribute);
1862
        }
1863
 
1864
        return $results;
1865
    }
1866
 
1867
    /**
1868
     * Sync the original attributes with the current.
1869
     *
1870
     * @return $this
1871
     */
1872
    public function syncOriginal()
1873
    {
1874
        $this->original = $this->getAttributes();
1875
 
1876
        return $this;
1877
    }
1878
 
1879
    /**
1880
     * Sync a single original attribute with its current value.
1881
     *
1882
     * @param  string  $attribute
1883
     * @return $this
1884
     */
1885
    public function syncOriginalAttribute($attribute)
1886
    {
1887
        return $this->syncOriginalAttributes($attribute);
1888
    }
1889
 
1890
    /**
1891
     * Sync multiple original attribute with their current values.
1892
     *
1893
     * @param  array|string  $attributes
1894
     * @return $this
1895
     */
1896
    public function syncOriginalAttributes($attributes)
1897
    {
1898
        $attributes = is_array($attributes) ? $attributes : func_get_args();
1899
 
1900
        $modelAttributes = $this->getAttributes();
1901
 
1902
        foreach ($attributes as $attribute) {
1903
            $this->original[$attribute] = $modelAttributes[$attribute];
1904
        }
1905
 
1906
        return $this;
1907
    }
1908
 
1909
    /**
1910
     * Sync the changed attributes.
1911
     *
1912
     * @return $this
1913
     */
1914
    public function syncChanges()
1915
    {
1916
        $this->changes = $this->getDirty();
1917
 
1918
        return $this;
1919
    }
1920
 
1921
    /**
1922
     * Determine if the model or any of the given attribute(s) have been modified.
1923
     *
1924
     * @param  array|string|null  $attributes
1925
     * @return bool
1926
     */
1927
    public function isDirty($attributes = null)
1928
    {
1929
        return $this->hasChanges(
1930
            $this->getDirty(), is_array($attributes) ? $attributes : func_get_args()
1931
        );
1932
    }
1933
 
1934
    /**
1935
     * Determine if the model or all the given attribute(s) have remained the same.
1936
     *
1937
     * @param  array|string|null  $attributes
1938
     * @return bool
1939
     */
1940
    public function isClean($attributes = null)
1941
    {
1942
        return ! $this->isDirty(...func_get_args());
1943
    }
1944
 
1945
    /**
1946
     * Discard attribute changes and reset the attributes to their original state.
1947
     *
1948
     * @return $this
1949
     */
1950
    public function discardChanges()
1951
    {
1952
        [$this->attributes, $this->changes] = [$this->original, []];
1953
 
1954
        return $this;
1955
    }
1956
 
1957
    /**
1958
     * Determine if the model or any of the given attribute(s) were changed when the model was last saved.
1959
     *
1960
     * @param  array|string|null  $attributes
1961
     * @return bool
1962
     */
1963
    public function wasChanged($attributes = null)
1964
    {
1965
        return $this->hasChanges(
1966
            $this->getChanges(), is_array($attributes) ? $attributes : func_get_args()
1967
        );
1968
    }
1969
 
1970
    /**
1971
     * Determine if any of the given attributes were changed when the model was last saved.
1972
     *
1973
     * @param  array  $changes
1974
     * @param  array|string|null  $attributes
1975
     * @return bool
1976
     */
1977
    protected function hasChanges($changes, $attributes = null)
1978
    {
1979
        // If no specific attributes were provided, we will just see if the dirty array
1980
        // already contains any attributes. If it does we will just return that this
1981
        // count is greater than zero. Else, we need to check specific attributes.
1982
        if (empty($attributes)) {
1983
            return count($changes) > 0;
1984
        }
1985
 
1986
        // Here we will spin through every attribute and see if this is in the array of
1987
        // dirty attributes. If it is, we will return true and if we make it through
1988
        // all of the attributes for the entire array we will return false at end.
1989
        foreach (Arr::wrap($attributes) as $attribute) {
1990
            if (array_key_exists($attribute, $changes)) {
1991
                return true;
1992
            }
1993
        }
1994
 
1995
        return false;
1996
    }
1997
 
1998
    /**
1999
     * Get the attributes that have been changed since the last sync.
2000
     *
2001
     * @return array
2002
     */
2003
    public function getDirty()
2004
    {
2005
        $dirty = [];
2006
 
2007
        foreach ($this->getAttributes() as $key => $value) {
2008
            if (! $this->originalIsEquivalent($key)) {
2009
                $dirty[$key] = $value;
2010
            }
2011
        }
2012
 
2013
        return $dirty;
2014
    }
2015
 
2016
    /**
2017
     * Get the attributes that were changed when the model was last saved.
2018
     *
2019
     * @return array
2020
     */
2021
    public function getChanges()
2022
    {
2023
        return $this->changes;
2024
    }
2025
 
2026
    /**
2027
     * Determine if the new and old values for a given key are equivalent.
2028
     *
2029
     * @param  string  $key
2030
     * @return bool
2031
     */
2032
    public function originalIsEquivalent($key)
2033
    {
2034
        if (! array_key_exists($key, $this->original)) {
2035
            return false;
2036
        }
2037
 
2038
        $attribute = Arr::get($this->attributes, $key);
2039
        $original = Arr::get($this->original, $key);
2040
 
2041
        if ($attribute === $original) {
2042
            return true;
2043
        } elseif (is_null($attribute)) {
2044
            return false;
2045
        } elseif ($this->isDateAttribute($key) || $this->isDateCastableWithCustomFormat($key)) {
2046
            return $this->fromDateTime($attribute) ===
2047
                $this->fromDateTime($original);
2048
        } elseif ($this->hasCast($key, ['object', 'collection'])) {
2049
            return $this->fromJson($attribute) ===
2050
                $this->fromJson($original);
2051
        } elseif ($this->hasCast($key, ['real', 'float', 'double'])) {
2052
            if ($original === null) {
2053
                return false;
2054
            }
2055
 
2056
            return abs($this->castAttribute($key, $attribute) - $this->castAttribute($key, $original)) < PHP_FLOAT_EPSILON * 4;
2057
        } elseif ($this->hasCast($key, static::$primitiveCastTypes)) {
2058
            return $this->castAttribute($key, $attribute) ===
2059
                $this->castAttribute($key, $original);
2060
        } elseif ($this->isClassCastable($key) && in_array($this->getCasts()[$key], [AsArrayObject::class, AsCollection::class])) {
2061
            return $this->fromJson($attribute) === $this->fromJson($original);
2062
        } elseif ($this->isClassCastable($key) && $original !== null && in_array($this->getCasts()[$key], [AsEncryptedArrayObject::class, AsEncryptedCollection::class])) {
2063
            return $this->fromEncryptedString($attribute) === $this->fromEncryptedString($original);
2064
        }
2065
 
2066
        return is_numeric($attribute) && is_numeric($original)
2067
            && strcmp((string) $attribute, (string) $original) === 0;
2068
    }
2069
 
2070
    /**
2071
     * Transform a raw model value using mutators, casts, etc.
2072
     *
2073
     * @param  string  $key
2074
     * @param  mixed  $value
2075
     * @return mixed
2076
     */
2077
    protected function transformModelValue($key, $value)
2078
    {
2079
        // If the attribute has a get mutator, we will call that then return what
2080
        // it returns as the value, which is useful for transforming values on
2081
        // retrieval from the model to a form that is more useful for usage.
2082
        if ($this->hasGetMutator($key)) {
2083
            return $this->mutateAttribute($key, $value);
2084
        } elseif ($this->hasAttributeGetMutator($key)) {
2085
            return $this->mutateAttributeMarkedAttribute($key, $value);
2086
        }
2087
 
2088
        // If the attribute exists within the cast array, we will convert it to
2089
        // an appropriate native PHP type dependent upon the associated value
2090
        // given with the key in the pair. Dayle made this comment line up.
2091
        if ($this->hasCast($key)) {
2092
            return $this->castAttribute($key, $value);
2093
        }
2094
 
2095
        // If the attribute is listed as a date, we will convert it to a DateTime
2096
        // instance on retrieval, which makes it quite convenient to work with
2097
        // date fields without having to create a mutator for each property.
2098
        if ($value !== null
2099
            && \in_array($key, $this->getDates(), false)) {
2100
            return $this->asDateTime($value);
2101
        }
2102
 
2103
        return $value;
2104
    }
2105
 
2106
    /**
2107
     * Append attributes to query when building a query.
2108
     *
2109
     * @param  array|string  $attributes
2110
     * @return $this
2111
     */
2112
    public function append($attributes)
2113
    {
2114
        $this->appends = array_unique(
2115
            array_merge($this->appends, is_string($attributes) ? func_get_args() : $attributes)
2116
        );
2117
 
2118
        return $this;
2119
    }
2120
 
2121
    /**
2122
     * Get the accessors that are being appended to model arrays.
2123
     *
2124
     * @return array
2125
     */
2126
    public function getAppends()
2127
    {
2128
        return $this->appends;
2129
    }
2130
 
2131
    /**
2132
     * Set the accessors to append to model arrays.
2133
     *
2134
     * @param  array  $appends
2135
     * @return $this
2136
     */
2137
    public function setAppends(array $appends)
2138
    {
2139
        $this->appends = $appends;
2140
 
2141
        return $this;
2142
    }
2143
 
2144
    /**
2145
     * Return whether the accessor attribute has been appended.
2146
     *
2147
     * @param  string  $attribute
2148
     * @return bool
2149
     */
2150
    public function hasAppended($attribute)
2151
    {
2152
        return in_array($attribute, $this->appends);
2153
    }
2154
 
2155
    /**
2156
     * Get the mutated attributes for a given instance.
2157
     *
2158
     * @return array
2159
     */
2160
    public function getMutatedAttributes()
2161
    {
2162
        if (! isset(static::$mutatorCache[static::class])) {
2163
            static::cacheMutatedAttributes($this);
2164
        }
2165
 
2166
        return static::$mutatorCache[static::class];
2167
    }
2168
 
2169
    /**
2170
     * Extract and cache all the mutated attributes of a class.
2171
     *
2172
     * @param  object|string  $classOrInstance
2173
     * @return void
2174
     */
2175
    public static function cacheMutatedAttributes($classOrInstance)
2176
    {
2177
        $reflection = new ReflectionClass($classOrInstance);
2178
 
2179
        $class = $reflection->getName();
2180
 
2181
        static::$getAttributeMutatorCache[$class] =
2182
            collect($attributeMutatorMethods = static::getAttributeMarkedMutatorMethods($classOrInstance))
2183
                    ->mapWithKeys(function ($match) {
2184
                        return [lcfirst(static::$snakeAttributes ? Str::snake($match) : $match) => true];
2185
                    })->all();
2186
 
2187
        static::$mutatorCache[$class] = collect(static::getMutatorMethods($class))
2188
                ->merge($attributeMutatorMethods)
2189
                ->map(function ($match) {
2190
                    return lcfirst(static::$snakeAttributes ? Str::snake($match) : $match);
2191
                })->all();
2192
    }
2193
 
2194
    /**
2195
     * Get all of the attribute mutator methods.
2196
     *
2197
     * @param  mixed  $class
2198
     * @return array
2199
     */
2200
    protected static function getMutatorMethods($class)
2201
    {
2202
        preg_match_all('/(?<=^|;)get([^;]+?)Attribute(;|$)/', implode(';', get_class_methods($class)), $matches);
2203
 
2204
        return $matches[1];
2205
    }
2206
 
2207
    /**
2208
     * Get all of the "Attribute" return typed attribute mutator methods.
2209
     *
2210
     * @param  mixed  $class
2211
     * @return array
2212
     */
2213
    protected static function getAttributeMarkedMutatorMethods($class)
2214
    {
2215
        $instance = is_object($class) ? $class : new $class;
2216
 
2217
        return collect((new ReflectionClass($instance))->getMethods())->filter(function ($method) use ($instance) {
2218
            $returnType = $method->getReturnType();
2219
 
2220
            if ($returnType instanceof ReflectionNamedType &&
2221
                $returnType->getName() === Attribute::class) {
2222
                $method->setAccessible(true);
2223
 
2224
                if (is_callable($method->invoke($instance)->get)) {
2225
                    return true;
2226
                }
2227
            }
2228
 
2229
            return false;
2230
        })->map->name->values()->all();
2231
    }
2232
}