Subversion-Projekte lars-tiefland.laravel_shop

Revision

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