1<?php
2
3namespace Illuminate\Database\Eloquent\Concerns;
4
5use Carbon\CarbonInterface;
6use DateTimeInterface;
7use Illuminate\Contracts\Database\Eloquent\Castable;
8use Illuminate\Contracts\Database\Eloquent\CastsInboundAttributes;
9use Illuminate\Contracts\Support\Arrayable;
10use Illuminate\Database\Eloquent\InvalidCastException;
11use Illuminate\Database\Eloquent\JsonEncodingException;
12use Illuminate\Database\Eloquent\Relations\Relation;
13use Illuminate\Support\Arr;
14use Illuminate\Support\Carbon;
15use Illuminate\Support\Collection as BaseCollection;
16use Illuminate\Support\Facades\Crypt;
17use Illuminate\Support\Facades\Date;
18use Illuminate\Support\Str;
19use InvalidArgumentException;
20use LogicException;
21
22trait HasAttributes
23{
24    /**
25     * The model's attributes.
26     *
27     * @var array
28     */
29    protected $attributes = [];
30
31    /**
32     * The model attribute's original state.
33     *
34     * @var array
35     */
36    protected $original = [];
37
38    /**
39     * The changed model attributes.
40     *
41     * @var array
42     */
43    protected $changes = [];
44
45    /**
46     * The attributes that should be cast.
47     *
48     * @var array
49     */
50    protected $casts = [];
51
52    /**
53     * The attributes that have been cast using custom classes.
54     *
55     * @var array
56     */
57    protected $classCastCache = [];
58
59    /**
60     * The built-in, primitive cast types supported by Eloquent.
61     *
62     * @var string[]
63     */
64    protected static $primitiveCastTypes = [
65        'array',
66        'bool',
67        'boolean',
68        'collection',
69        'custom_datetime',
70        'date',
71        'datetime',
72        'decimal',
73        'double',
74        'encrypted',
75        'encrypted:array',
76        'encrypted:collection',
77        'encrypted:json',
78        'encrypted:object',
79        'float',
80        'int',
81        'integer',
82        'json',
83        'object',
84        'real',
85        'string',
86        'timestamp',
87    ];
88
89    /**
90     * The attributes that should be mutated to dates.
91     *
92     * @deprecated Use the "casts" property
93     *
94     * @var array
95     */
96    protected $dates = [];
97
98    /**
99     * The storage format of the model's date columns.
100     *
101     * @var string
102     */
103    protected $dateFormat;
104
105    /**
106     * The accessors to append to the model's array form.
107     *
108     * @var array
109     */
110    protected $appends = [];
111
112    /**
113     * Indicates whether attributes are snake cased on arrays.
114     *
115     * @var bool
116     */
117    public static $snakeAttributes = true;
118
119    /**
120     * The cache of the mutated attributes for each class.
121     *
122     * @var array
123     */
124    protected static $mutatorCache = [];
125
126    /**
127     * The encrypter instance that is used to encrypt attributes.
128     *
129     * @var \Illuminate\Contracts\Encryption\Encrypter
130     */
131    public static $encrypter;
132
133    /**
134     * Convert the model's attributes to an array.
135     *
136     * @return array
137     */
138    public function attributesToArray()
139    {
140        // If an attribute is a date, we will cast it to a string after converting it
141        // to a DateTime / Carbon instance. This is so we will get some consistent
142        // formatting while accessing attributes vs. arraying / JSONing a model.
143        $attributes = $this->addDateAttributesToArray(
144            $attributes = $this->getArrayableAttributes()
145        );
146
147        $attributes = $this->addMutatedAttributesToArray(
148            $attributes, $mutatedAttributes = $this->getMutatedAttributes()
149        );
150
151        // Next we will handle any casts that have been setup for this model and cast
152        // the values to their appropriate type. If the attribute has a mutator we
153        // will not perform the cast on those attributes to avoid any confusion.
154        $attributes = $this->addCastAttributesToArray(
155            $attributes, $mutatedAttributes
156        );
157
158        // Here we will grab all of the appended, calculated attributes to this model
159        // as these attributes are not really in the attributes array, but are run
160        // when we need to array or JSON the model for convenience to the coder.
161        foreach ($this->getArrayableAppends() as $key) {
162            $attributes[$key] = $this->mutateAttributeForArray($key, null);
163        }
164
165        return $attributes;
166    }
167
168    /**
169     * Add the date attributes to the attributes array.
170     *
171     * @param  array  $attributes
172     * @return array
173     */
174    protected function addDateAttributesToArray(array $attributes)
175    {
176        foreach ($this->getDates() as $key) {
177            if (! isset($attributes[$key])) {
178                continue;
179            }
180
181            $attributes[$key] = $this->serializeDate(
182                $this->asDateTime($attributes[$key])
183            );
184        }
185
186        return $attributes;
187    }
188
189    /**
190     * Add the mutated attributes to the attributes array.
191     *
192     * @param  array  $attributes
193     * @param  array  $mutatedAttributes
194     * @return array
195     */
196    protected function addMutatedAttributesToArray(array $attributes, array $mutatedAttributes)
197    {
198        foreach ($mutatedAttributes as $key) {
199            // We want to spin through all the mutated attributes for this model and call
200            // the mutator for the attribute. We cache off every mutated attributes so
201            // we don't have to constantly check on attributes that actually change.
202            if (! array_key_exists($key, $attributes)) {
203                continue;
204            }
205
206            // Next, we will call the mutator for this attribute so that we can get these
207            // mutated attribute's actual values. After we finish mutating each of the
208            // attributes we will return this final array of the mutated attributes.
209            $attributes[$key] = $this->mutateAttributeForArray(
210                $key, $attributes[$key]
211            );
212        }
213
214        return $attributes;
215    }
216
217    /**
218     * Add the casted attributes to the attributes array.
219     *
220     * @param  array  $attributes
221     * @param  array  $mutatedAttributes
222     * @return array
223     */
224    protected function addCastAttributesToArray(array $attributes, array $mutatedAttributes)
225    {
226        foreach ($this->getCasts() as $key => $value) {
227            if (! array_key_exists($key, $attributes) ||
228                in_array($key, $mutatedAttributes)) {
229                continue;
230            }
231
232            // Here we will cast the attribute. Then, if the cast is a date or datetime cast
233            // then we will serialize the date for the array. This will convert the dates
234            // to strings based on the date format specified for these Eloquent models.
235            $attributes[$key] = $this->castAttribute(
236                $key, $attributes[$key]
237            );
238
239            // If the attribute cast was a date or a datetime, we will serialize the date as
240            // a string. This allows the developers to customize how dates are serialized
241            // into an array without affecting how they are persisted into the storage.
242            if ($attributes[$key] &&
243                ($value === 'date' || $value === 'datetime')) {
244                $attributes[$key] = $this->serializeDate($attributes[$key]);
245            }
246
247            if ($attributes[$key] && $this->isCustomDateTimeCast($value)) {
248                $attributes[$key] = $attributes[$key]->format(explode(':', $value, 2)[1]);
249            }
250
251            if ($attributes[$key] && $attributes[$key] instanceof DateTimeInterface &&
252                $this->isClassCastable($key)) {
253                $attributes[$key] = $this->serializeDate($attributes[$key]);
254            }
255
256            if ($attributes[$key] && $this->isClassSerializable($key)) {
257                $attributes[$key] = $this->serializeClassCastableAttribute($key, $attributes[$key]);
258            }
259
260            if ($attributes[$key] instanceof Arrayable) {
261                $attributes[$key] = $attributes[$key]->toArray();
262            }
263        }
264
265        return $attributes;
266    }
267
268    /**
269     * Get an attribute array of all arrayable attributes.
270     *
271     * @return array
272     */
273    protected function getArrayableAttributes()
274    {
275        return $this->getArrayableItems($this->getAttributes());
276    }
277
278    /**
279     * Get all of the appendable values that are arrayable.
280     *
281     * @return array
282     */
283    protected function getArrayableAppends()
284    {
285        if (! count($this->appends)) {
286            return [];
287        }
288
289        return $this->getArrayableItems(
290            array_combine($this->appends, $this->appends)
291        );
292    }
293
294    /**
295     * Get the model's relationships in array form.
296     *
297     * @return array
298     */
299    public function relationsToArray()
300    {
301        $attributes = [];
302
303        foreach ($this->getArrayableRelations() as $key => $value) {
304            // If the values implements the Arrayable interface we can just call this
305            // toArray method on the instances which will convert both models and
306            // collections to their proper array form and we'll set the values.
307            if ($value instanceof Arrayable) {
308                $relation = $value->toArray();
309            }
310
311            // If the value is null, we'll still go ahead and set it in this list of
312            // attributes since null is used to represent empty relationships if
313            // if it a has one or belongs to type relationships on the models.
314            elseif (is_null($value)) {
315                $relation = $value;
316            }
317
318            // If the relationships snake-casing is enabled, we will snake case this
319            // key so that the relation attribute is snake cased in this returned
320            // array to the developers, making this consistent with attributes.
321            if (static::$snakeAttributes) {
322                $key = Str::snake($key);
323            }
324
325            // If the relation value has been set, we will set it on this attributes
326            // list for returning. If it was not arrayable or null, we'll not set
327            // the value on the array because it is some type of invalid value.
328            if (isset($relation) || is_null($value)) {
329                $attributes[$key] = $relation;
330            }
331
332            unset($relation);
333        }
334
335        return $attributes;
336    }
337
338    /**
339     * Get an attribute array of all arrayable relations.
340     *
341     * @return array
342     */
343    protected function getArrayableRelations()
344    {
345        return $this->getArrayableItems($this->relations);
346    }
347
348    /**
349     * Get an attribute array of all arrayable values.
350     *
351     * @param  array  $values
352     * @return array
353     */
354    protected function getArrayableItems(array $values)
355    {
356        if (count($this->getVisible()) > 0) {
357            $values = array_intersect_key($values, array_flip($this->getVisible()));
358        }
359
360        if (count($this->getHidden()) > 0) {
361            $values = array_diff_key($values, array_flip($this->getHidden()));
362        }
363
364        return $values;
365    }
366
367    /**
368     * Get an attribute from the model.
369     *
370     * @param  string  $key
371     * @return mixed
372     */
373    public function getAttribute($key)
374    {
375        if (! $key) {
376            return;
377        }
378
379        // If the attribute exists in the attribute array or has a "get" mutator we will
380        // get the attribute's value. Otherwise, we will proceed as if the developers
381        // are asking for a relationship's value. This covers both types of values.
382        if (array_key_exists($key, $this->attributes) ||
383            array_key_exists($key, $this->casts) ||
384            $this->hasGetMutator($key) ||
385            $this->isClassCastable($key)) {
386            return $this->getAttributeValue($key);
387        }
388
389        // Here we will determine if the model base class itself contains this given key
390        // since we don't want to treat any of those methods as relationships because
391        // they are all intended as helper methods and none of these are relations.
392        if (method_exists(self::class, $key)) {
393            return;
394        }
395
396        return $this->getRelationValue($key);
397    }
398
399    /**
400     * Get a plain attribute (not a relationship).
401     *
402     * @param  string  $key
403     * @return mixed
404     */
405    public function getAttributeValue($key)
406    {
407        return $this->transformModelValue($key, $this->getAttributeFromArray($key));
408    }
409
410    /**
411     * Get an attribute from the $attributes array.
412     *
413     * @param  string  $key
414     * @return mixed
415     */
416    protected function getAttributeFromArray($key)
417    {
418        return $this->getAttributes()[$key] ?? null;
419    }
420
421    /**
422     * Get a relationship.
423     *
424     * @param  string  $key
425     * @return mixed
426     */
427    public function getRelationValue($key)
428    {
429        // If the key already exists in the relationships array, it just means the
430        // relationship has already been loaded, so we'll just return it out of
431        // here because there is no need to query within the relations twice.
432        if ($this->relationLoaded($key)) {
433            return $this->relations[$key];
434        }
435
436        // If the "attribute" exists as a method on the model, we will just assume
437        // it is a relationship and will load and return results from the query
438        // and hydrate the relationship's value on the "relationships" array.
439        if (method_exists($this, $key) ||
440            (static::$relationResolvers[get_class($this)][$key] ?? null)) {
441            return $this->getRelationshipFromMethod($key);
442        }
443    }
444
445    /**
446     * Get a relationship value from a method.
447     *
448     * @param  string  $method
449     * @return mixed
450     *
451     * @throws \LogicException
452     */
453    protected function getRelationshipFromMethod($method)
454    {
455        $relation = $this->$method();
456
457        if (! $relation instanceof Relation) {
458            if (is_null($relation)) {
459                throw new LogicException(sprintf(
460                    '%s::%s must return a relationship instance, but "null" was returned. Was the "return" keyword used?', static::class, $method
461                ));
462            }
463
464            throw new LogicException(sprintf(
465                '%s::%s must return a relationship instance.', static::class, $method
466            ));
467        }
468
469        return tap($relation->getResults(), function ($results) use ($method) {
470            $this->setRelation($method, $results);
471        });
472    }
473
474    /**
475     * Determine if a get mutator exists for an attribute.
476     *
477     * @param  string  $key
478     * @return bool
479     */
480    public function hasGetMutator($key)
481    {
482        return method_exists($this, 'get'.Str::studly($key).'Attribute');
483    }
484
485    /**
486     * Get the value of an attribute using its mutator.
487     *
488     * @param  string  $key
489     * @param  mixed  $value
490     * @return mixed
491     */
492    protected function mutateAttribute($key, $value)
493    {
494        return $this->{'get'.Str::studly($key).'Attribute'}($value);
495    }
496
497    /**
498     * Get the value of an attribute using its mutator for array conversion.
499     *
500     * @param  string  $key
501     * @param  mixed  $value
502     * @return mixed
503     */
504    protected function mutateAttributeForArray($key, $value)
505    {
506        $value = $this->isClassCastable($key)
507                    ? $this->getClassCastableAttributeValue($key, $value)
508                    : $this->mutateAttribute($key, $value);
509
510        return $value instanceof Arrayable ? $value->toArray() : $value;
511    }
512
513    /**
514     * Merge new casts with existing casts on the model.
515     *
516     * @param  array  $casts
517     * @return $this
518     */
519    public function mergeCasts($casts)
520    {
521        $this->casts = array_merge($this->casts, $casts);
522
523        return $this;
524    }
525
526    /**
527     * Cast an attribute to a native PHP type.
528     *
529     * @param  string  $key
530     * @param  mixed  $value
531     * @return mixed
532     */
533    protected function castAttribute($key, $value)
534    {
535        $castType = $this->getCastType($key);
536
537        if (is_null($value) && in_array($castType, static::$primitiveCastTypes)) {
538            return $value;
539        }
540
541        // If the key is one of the encrypted castable types, we'll first decrypt
542        // the value and update the cast type so we may leverage the following
543        // logic for casting this value to any additionally specified types.
544        if ($this->isEncryptedCastable($key)) {
545            $value = $this->fromEncryptedString($value);
546
547            $castType = Str::after($castType, 'encrypted:');
548        }
549
550        switch ($castType) {
551            case 'int':
552            case 'integer':
553                return (int) $value;
554            case 'real':
555            case 'float':
556            case 'double':
557                return $this->fromFloat($value);
558            case 'decimal':
559                return $this->asDecimal($value, explode(':', $this->getCasts()[$key], 2)[1]);
560            case 'string':
561                return (string) $value;
562            case 'bool':
563            case 'boolean':
564                return (bool) $value;
565            case 'object':
566                return $this->fromJson($value, true);
567            case 'array':
568            case 'json':
569                return $this->fromJson($value);
570            case 'collection':
571                return new BaseCollection($this->fromJson($value));
572            case 'date':
573                return $this->asDate($value);
574            case 'datetime':
575            case 'custom_datetime':
576                return $this->asDateTime($value);
577            case 'timestamp':
578                return $this->asTimestamp($value);
579        }
580
581        if ($this->isClassCastable($key)) {
582            return $this->getClassCastableAttributeValue($key, $value);
583        }
584
585        return $value;
586    }
587
588    /**
589     * Cast the given attribute using a custom cast class.
590     *
591     * @param  string  $key
592     * @param  mixed  $value
593     * @return mixed
594     */
595    protected function getClassCastableAttributeValue($key, $value)
596    {
597        if (isset($this->classCastCache[$key])) {
598            return $this->classCastCache[$key];
599        } else {
600            $caster = $this->resolveCasterClass($key);
601
602            $value = $caster instanceof CastsInboundAttributes
603                        ? $value
604                        : $caster->get($this, $key, $value, $this->attributes);
605
606            if ($caster instanceof CastsInboundAttributes || ! is_object($value)) {
607                unset($this->classCastCache[$key]);
608            } else {
609                $this->classCastCache[$key] = $value;
610            }
611
612            return $value;
613        }
614    }
615
616    /**
617     * Get the type of cast for a model attribute.
618     *
619     * @param  string  $key
620     * @return string
621     */
622    protected function getCastType($key)
623    {
624        if ($this->isCustomDateTimeCast($this->getCasts()[$key])) {
625            return 'custom_datetime';
626        }
627
628        if ($this->isDecimalCast($this->getCasts()[$key])) {
629            return 'decimal';
630        }
631
632        return trim(strtolower($this->getCasts()[$key]));
633    }
634
635    /**
636     * Increment or decrement the given attribute using the custom cast class.
637     *
638     * @param  string  $method
639     * @param  string  $key
640     * @param  mixed  $value
641     * @return mixed
642     */
643    protected function deviateClassCastableAttribute($method, $key, $value)
644    {
645        return $this->resolveCasterClass($key)->{$method}(
646            $this, $key, $value, $this->attributes
647        );
648    }
649
650    /**
651     * Serialize the given attribute using the custom cast class.
652     *
653     * @param  string  $key
654     * @param  mixed  $value
655     * @return mixed
656     */
657    protected function serializeClassCastableAttribute($key, $value)
658    {
659        return $this->resolveCasterClass($key)->serialize(
660            $this, $key, $value, $this->attributes
661        );
662    }
663
664    /**
665     * Determine if the cast type is a custom date time cast.
666     *
667     * @param  string  $cast
668     * @return bool
669     */
670    protected function isCustomDateTimeCast($cast)
671    {
672        return strncmp($cast, 'date:', 5) === 0 ||
673               strncmp($cast, 'datetime:', 9) === 0;
674    }
675
676    /**
677     * Determine if the cast type is a decimal cast.
678     *
679     * @param  string  $cast
680     * @return bool
681     */
682    protected function isDecimalCast($cast)
683    {
684        return strncmp($cast, 'decimal:', 8) === 0;
685    }
686
687    /**
688     * Set a given attribute on the model.
689     *
690     * @param  string  $key
691     * @param  mixed  $value
692     * @return mixed
693     */
694    public function setAttribute($key, $value)
695    {
696        // First we will check for the presence of a mutator for the set operation
697        // which simply lets the developers tweak the attribute as it is set on
698        // this model, such as "json_encoding" a listing of data for storage.
699        if ($this->hasSetMutator($key)) {
700            return $this->setMutatedAttributeValue($key, $value);
701        }
702
703        // If an attribute is listed as a "date", we'll convert it from a DateTime
704        // instance into a form proper for storage on the database tables using
705        // the connection grammar's date format. We will auto set the values.
706        elseif ($value && $this->isDateAttribute($key)) {
707            $value = $this->fromDateTime($value);
708        }
709
710        if ($this->isClassCastable($key)) {
711            $this->setClassCastableAttribute($key, $value);
712
713            return $this;
714        }
715
716        if (! is_null($value) && $this->isJsonCastable($key)) {
717            $value = $this->castAttributeAsJson($key, $value);
718        }
719
720        // If this attribute contains a JSON ->, we'll set the proper value in the
721        // attribute's underlying array. This takes care of properly nesting an
722        // attribute in the array's value in the case of deeply nested items.
723        if (Str::contains($key, '->')) {
724            return $this->fillJsonAttribute($key, $value);
725        }
726
727        if (! is_null($value) && $this->isEncryptedCastable($key)) {
728            $value = $this->castAttributeAsEncryptedString($key, $value);
729        }
730
731        $this->attributes[$key] = $value;
732
733        return $this;
734    }
735
736    /**
737     * Determine if a set mutator exists for an attribute.
738     *
739     * @param  string  $key
740     * @return bool
741     */
742    public function hasSetMutator($key)
743    {
744        return method_exists($this, 'set'.Str::studly($key).'Attribute');
745    }
746
747    /**
748     * Set the value of an attribute using its mutator.
749     *
750     * @param  string  $key
751     * @param  mixed  $value
752     * @return mixed
753     */
754    protected function setMutatedAttributeValue($key, $value)
755    {
756        return $this->{'set'.Str::studly($key).'Attribute'}($value);
757    }
758
759    /**
760     * Determine if the given attribute is a date or date castable.
761     *
762     * @param  string  $key
763     * @return bool
764     */
765    protected function isDateAttribute($key)
766    {
767        return in_array($key, $this->getDates(), true) ||
768               $this->isDateCastable($key);
769    }
770
771    /**
772     * Set a given JSON attribute on the model.
773     *
774     * @param  string  $key
775     * @param  mixed  $value
776     * @return $this
777     */
778    public function fillJsonAttribute($key, $value)
779    {
780        [$key, $path] = explode('->', $key, 2);
781
782        $value = $this->asJson($this->getArrayAttributeWithValue(
783            $path, $key, $value
784        ));
785
786        $this->attributes[$key] = $this->isEncryptedCastable($key)
787                    ? $this->castAttributeAsEncryptedString($key, $value)
788                    : $value;
789
790        return $this;
791    }
792
793    /**
794     * Set the value of a class castable attribute.
795     *
796     * @param  string  $key
797     * @param  mixed  $value
798     * @return void
799     */
800    protected function setClassCastableAttribute($key, $value)
801    {
802        $caster = $this->resolveCasterClass($key);
803
804        if (is_null($value)) {
805            $this->attributes = array_merge($this->attributes, array_map(
806                function () {
807                },
808                $this->normalizeCastClassResponse($key, $caster->set(
809                    $this, $key, $this->{$key}, $this->attributes
810                ))
811            ));
812        } else {
813            $this->attributes = array_merge(
814                $this->attributes,
815                $this->normalizeCastClassResponse($key, $caster->set(
816                    $this, $key, $value, $this->attributes
817                ))
818            );
819        }
820
821        if ($caster instanceof CastsInboundAttributes || ! is_object($value)) {
822            unset($this->classCastCache[$key]);
823        } else {
824            $this->classCastCache[$key] = $value;
825        }
826    }
827
828    /**
829     * Get an array attribute with the given key and value set.
830     *
831     * @param  string  $path
832     * @param  string  $key
833     * @param  mixed  $value
834     * @return $this
835     */
836    protected function getArrayAttributeWithValue($path, $key, $value)
837    {
838        return tap($this->getArrayAttributeByKey($key), function (&$array) use ($path, $value) {
839            Arr::set($array, str_replace('->', '.', $path), $value);
840        });
841    }
842
843    /**
844     * Get an array attribute or return an empty array if it is not set.
845     *
846     * @param  string  $key
847     * @return array
848     */
849    protected function getArrayAttributeByKey($key)
850    {
851        if (! isset($this->attributes[$key])) {
852            return [];
853        }
854
855        return $this->fromJson(
856            $this->isEncryptedCastable($key)
857                    ? $this->fromEncryptedString($this->attributes[$key])
858                    : $this->attributes[$key]
859        );
860    }
861
862    /**
863     * Cast the given attribute to JSON.
864     *
865     * @param  string  $key
866     * @param  mixed  $value
867     * @return string
868     */
869    protected function castAttributeAsJson($key, $value)
870    {
871        $value = $this->asJson($value);
872
873        if ($value === false) {
874            throw JsonEncodingException::forAttribute(
875                $this, $key, json_last_error_msg()
876            );
877        }
878
879        return $value;
880    }
881
882    /**
883     * Encode the given value as JSON.
884     *
885     * @param  mixed  $value
886     * @return string
887     */
888    protected function asJson($value)
889    {
890        return json_encode($value);
891    }
892
893    /**
894     * Decode the given JSON back into an array or object.
895     *
896     * @param  string  $value
897     * @param  bool  $asObject
898     * @return mixed
899     */
900    public function fromJson($value, $asObject = false)
901    {
902        return json_decode($value, ! $asObject);
903    }
904
905    /**
906     * Decrypt the given encrypted string.
907     *
908     * @param  string  $value
909     * @return mixed
910     */
911    public function fromEncryptedString($value)
912    {
913        return (static::$encrypter ?? Crypt::getFacadeRoot())->decrypt($value, false);
914    }
915
916    /**
917     * Cast the given attribute to an encrypted string.
918     *
919     * @param  string  $key
920     * @param  mixed  $value
921     * @return string
922     */
923    protected function castAttributeAsEncryptedString($key, $value)
924    {
925        return (static::$encrypter ?? Crypt::getFacadeRoot())->encrypt($value, false);
926    }
927
928    /**
929     * Set the encrypter instance that will be used to encrypt attributes.
930     *
931     * @param  \Illuminate\Contracts\Encryption\Encrypter  $encrypter
932     * @return void
933     */
934    public static function encryptUsing($encrypter)
935    {
936        static::$encrypter = $encrypter;
937    }
938
939    /**
940     * Decode the given float.
941     *
942     * @param  mixed  $value
943     * @return mixed
944     */
945    public function fromFloat($value)
946    {
947        switch ((string) $value) {
948            case 'Infinity':
949                return INF;
950            case '-Infinity':
951                return -INF;
952            case 'NaN':
953                return NAN;
954            default:
955                return (float) $value;
956        }
957    }
958
959    /**
960     * Return a decimal as string.
961     *
962     * @param  float  $value
963     * @param  int  $decimals
964     * @return string
965     */
966    protected function asDecimal($value, $decimals)
967    {
968        return number_format($value, $decimals, '.', '');
969    }
970
971    /**
972     * Return a timestamp as DateTime object with time set to 00:00:00.
973     *
974     * @param  mixed  $value
975     * @return \Illuminate\Support\Carbon
976     */
977    protected function asDate($value)
978    {
979        return $this->asDateTime($value)->startOfDay();
980    }
981
982    /**
983     * Return a timestamp as DateTime object.
984     *
985     * @param  mixed  $value
986     * @return \Illuminate\Support\Carbon
987     */
988    protected function asDateTime($value)
989    {
990        // If this value is already a Carbon instance, we shall just return it as is.
991        // This prevents us having to re-instantiate a Carbon instance when we know
992        // it already is one, which wouldn't be fulfilled by the DateTime check.
993        if ($value instanceof CarbonInterface) {
994            return Date::instance($value);
995        }
996
997        // If the value is already a DateTime instance, we will just skip the rest of
998        // these checks since they will be a waste of time, and hinder performance
999        // when checking the field. We will just return the DateTime right away.
1000        if ($value instanceof DateTimeInterface) {
1001            return Date::parse(
1002                $value->format('Y-m-d H:i:s.u'), $value->getTimezone()
1003            );
1004        }
1005
1006        // If this value is an integer, we will assume it is a UNIX timestamp's value
1007        // and format a Carbon object from this timestamp. This allows flexibility
1008        // when defining your date fields as they might be UNIX timestamps here.
1009        if (is_numeric($value)) {
1010            return Date::createFromTimestamp($value);
1011        }
1012
1013        // If the value is in simply year, month, day format, we will instantiate the
1014        // Carbon instances from that format. Again, this provides for simple date
1015        // fields on the database, while still supporting Carbonized conversion.
1016        if ($this->isStandardDateFormat($value)) {
1017            return Date::instance(Carbon::createFromFormat('Y-m-d', $value)->startOfDay());
1018        }
1019
1020        $format = $this->getDateFormat();
1021
1022        // Finally, we will just assume this date is in the format used by default on
1023        // the database connection and use that format to create the Carbon object
1024        // that is returned back out to the developers after we convert it here.
1025        try {
1026            $date = Date::createFromFormat($format, $value);
1027        } catch (InvalidArgumentException $e) {
1028            $date = false;
1029        }
1030
1031        return $date ?: Date::parse($value);
1032    }
1033
1034    /**
1035     * Determine if the given value is a standard date format.
1036     *
1037     * @param  string  $value
1038     * @return bool
1039     */
1040    protected function isStandardDateFormat($value)
1041    {
1042        return preg_match('/^(\d{4})-(\d{1,2})-(\d{1,2})$/', $value);
1043    }
1044
1045    /**
1046     * Convert a DateTime to a storable string.
1047     *
1048     * @param  mixed  $value
1049     * @return string|null
1050     */
1051    public function fromDateTime($value)
1052    {
1053        return empty($value) ? $value : $this->asDateTime($value)->format(
1054            $this->getDateFormat()
1055        );
1056    }
1057
1058    /**
1059     * Return a timestamp as unix timestamp.
1060     *
1061     * @param  mixed  $value
1062     * @return int
1063     */
1064    protected function asTimestamp($value)
1065    {
1066        return $this->asDateTime($value)->getTimestamp();
1067    }
1068
1069    /**
1070     * Prepare a date for array / JSON serialization.
1071     *
1072     * @param  \DateTimeInterface  $date
1073     * @return string
1074     */
1075    protected function serializeDate(DateTimeInterface $date)
1076    {
1077        return Carbon::instance($date)->toJSON();
1078    }
1079
1080    /**
1081     * Get the attributes that should be converted to dates.
1082     *
1083     * @return array
1084     */
1085    public function getDates()
1086    {
1087        if (! $this->usesTimestamps()) {
1088            return $this->dates;
1089        }
1090
1091        $defaults = [
1092            $this->getCreatedAtColumn(),
1093            $this->getUpdatedAtColumn(),
1094        ];
1095
1096        return array_unique(array_merge($this->dates, $defaults));
1097    }
1098
1099    /**
1100     * Get the format for database stored dates.
1101     *
1102     * @return string
1103     */
1104    public function getDateFormat()
1105    {
1106        return $this->dateFormat ?: $this->getConnection()->getQueryGrammar()->getDateFormat();
1107    }
1108
1109    /**
1110     * Set the date format used by the model.
1111     *
1112     * @param  string  $format
1113     * @return $this
1114     */
1115    public function setDateFormat($format)
1116    {
1117        $this->dateFormat = $format;
1118
1119        return $this;
1120    }
1121
1122    /**
1123     * Determine whether an attribute should be cast to a native type.
1124     *
1125     * @param  string  $key
1126     * @param  array|string|null  $types
1127     * @return bool
1128     */
1129    public function hasCast($key, $types = null)
1130    {
1131        if (array_key_exists($key, $this->getCasts())) {
1132            return $types ? in_array($this->getCastType($key), (array) $types, true) : true;
1133        }
1134
1135        return false;
1136    }
1137
1138    /**
1139     * Get the casts array.
1140     *
1141     * @return array
1142     */
1143    public function getCasts()
1144    {
1145        if ($this->getIncrementing()) {
1146            return array_merge([$this->getKeyName() => $this->getKeyType()], $this->casts);
1147        }
1148
1149        return $this->casts;
1150    }
1151
1152    /**
1153     * Determine whether a value is Date / DateTime castable for inbound manipulation.
1154     *
1155     * @param  string  $key
1156     * @return bool
1157     */
1158    protected function isDateCastable($key)
1159    {
1160        return $this->hasCast($key, ['date', 'datetime']);
1161    }
1162
1163    /**
1164     * Determine whether a value is JSON castable for inbound manipulation.
1165     *
1166     * @param  string  $key
1167     * @return bool
1168     */
1169    protected function isJsonCastable($key)
1170    {
1171        return $this->hasCast($key, ['array', 'json', 'object', 'collection', 'encrypted:array', 'encrypted:collection', 'encrypted:json', 'encrypted:object']);
1172    }
1173
1174    /**
1175     * Determine whether a value is an encrypted castable for inbound manipulation.
1176     *
1177     * @param  string  $key
1178     * @return bool
1179     */
1180    protected function isEncryptedCastable($key)
1181    {
1182        return $this->hasCast($key, ['encrypted', 'encrypted:array', 'encrypted:collection', 'encrypted:json', 'encrypted:object']);
1183    }
1184
1185    /**
1186     * Determine if the given key is cast using a custom class.
1187     *
1188     * @param  string  $key
1189     * @return bool
1190     */
1191    protected function isClassCastable($key)
1192    {
1193        if (! array_key_exists($key, $this->getCasts())) {
1194            return false;
1195        }
1196
1197        $castType = $this->parseCasterClass($this->getCasts()[$key]);
1198
1199        if (in_array($castType, static::$primitiveCastTypes)) {
1200            return false;
1201        }
1202
1203        if (class_exists($castType)) {
1204            return true;
1205        }
1206
1207        throw new InvalidCastException($this->getModel(), $key, $castType);
1208    }
1209
1210    /**
1211     * Determine if the key is deviable using a custom class.
1212     *
1213     * @param  string  $key
1214     * @return bool
1215     *
1216     * @throws \Illuminate\Database\Eloquent\InvalidCastException
1217     */
1218    protected function isClassDeviable($key)
1219    {
1220        return $this->isClassCastable($key) &&
1221            method_exists($castType = $this->parseCasterClass($this->getCasts()[$key]), 'increment') &&
1222            method_exists($castType, 'decrement');
1223    }
1224
1225    /**
1226     * Determine if the key is serializable using a custom class.
1227     *
1228     * @param  string  $key
1229     * @return bool
1230     *
1231     * @throws \Illuminate\Database\Eloquent\InvalidCastException
1232     */
1233    protected function isClassSerializable($key)
1234    {
1235        return $this->isClassCastable($key) &&
1236               method_exists($this->parseCasterClass($this->getCasts()[$key]), 'serialize');
1237    }
1238
1239    /**
1240     * Resolve the custom caster class for a given key.
1241     *
1242     * @param  string  $key
1243     * @return mixed
1244     */
1245    protected function resolveCasterClass($key)
1246    {
1247        $castType = $this->getCasts()[$key];
1248
1249        $arguments = [];
1250
1251        if (is_string($castType) && strpos($castType, ':') !== false) {
1252            $segments = explode(':', $castType, 2);
1253
1254            $castType = $segments[0];
1255            $arguments = explode(',', $segments[1]);
1256        }
1257
1258        if (is_subclass_of($castType, Castable::class)) {
1259            $castType = $castType::castUsing($arguments);
1260        }
1261
1262        if (is_object($castType)) {
1263            return $castType;
1264        }
1265
1266        return new $castType(...$arguments);
1267    }
1268
1269    /**
1270     * Parse the given caster class, removing any arguments.
1271     *
1272     * @param  string  $class
1273     * @return string
1274     */
1275    protected function parseCasterClass($class)
1276    {
1277        return strpos($class, ':') === false
1278                        ? $class
1279                        : explode(':', $class, 2)[0];
1280    }
1281
1282    /**
1283     * Merge the cast class attributes back into the model.
1284     *
1285     * @return void
1286     */
1287    protected function mergeAttributesFromClassCasts()
1288    {
1289        foreach ($this->classCastCache as $key => $value) {
1290            $caster = $this->resolveCasterClass($key);
1291
1292            $this->attributes = array_merge(
1293                $this->attributes,
1294                $caster instanceof CastsInboundAttributes
1295                       ? [$key => $value]
1296                       : $this->normalizeCastClassResponse($key, $caster->set($this, $key, $value, $this->attributes))
1297            );
1298        }
1299    }
1300
1301    /**
1302     * Normalize the response from a custom class caster.
1303     *
1304     * @param  string  $key
1305     * @param  mixed  $value
1306     * @return array
1307     */
1308    protected function normalizeCastClassResponse($key, $value)
1309    {
1310        return is_array($value) ? $value : [$key => $value];
1311    }
1312
1313    /**
1314     * Get all of the current attributes on the model.
1315     *
1316     * @return array
1317     */
1318    public function getAttributes()
1319    {
1320        $this->mergeAttributesFromClassCasts();
1321
1322        return $this->attributes;
1323    }
1324
1325    /**
1326     * Get all of the current attributes on the model for an insert operation.
1327     *
1328     * @return array
1329     */
1330    protected function getAttributesForInsert()
1331    {
1332        return $this->getAttributes();
1333    }
1334
1335    /**
1336     * Set the array of model attributes. No checking is done.
1337     *
1338     * @param  array  $attributes
1339     * @param  bool  $sync
1340     * @return $this
1341     */
1342    public function setRawAttributes(array $attributes, $sync = false)
1343    {
1344        $this->attributes = $attributes;
1345
1346        if ($sync) {
1347            $this->syncOriginal();
1348        }
1349
1350        $this->classCastCache = [];
1351
1352        return $this;
1353    }
1354
1355    /**
1356     * Get the model's original attribute values.
1357     *
1358     * @param  string|null  $key
1359     * @param  mixed  $default
1360     * @return mixed|array
1361     */
1362    public function getOriginal($key = null, $default = null)
1363    {
1364        return (new static)->setRawAttributes(
1365            $this->original, $sync = true
1366        )->getOriginalWithoutRewindingModel($key, $default);
1367    }
1368
1369    /**
1370     * Get the model's original attribute values.
1371     *
1372     * @param  string|null  $key
1373     * @param  mixed  $default
1374     * @return mixed|array
1375     */
1376    protected function getOriginalWithoutRewindingModel($key = null, $default = null)
1377    {
1378        if ($key) {
1379            return $this->transformModelValue(
1380                $key, Arr::get($this->original, $key, $default)
1381            );
1382        }
1383
1384        return collect($this->original)->mapWithKeys(function ($value, $key) {
1385            return [$key => $this->transformModelValue($key, $value)];
1386        })->all();
1387    }
1388
1389    /**
1390     * Get the model's raw original attribute values.
1391     *
1392     * @param  string|null  $key
1393     * @param  mixed  $default
1394     * @return mixed|array
1395     */
1396    public function getRawOriginal($key = null, $default = null)
1397    {
1398        return Arr::get($this->original, $key, $default);
1399    }
1400
1401    /**
1402     * Get a subset of the model's attributes.
1403     *
1404     * @param  array|mixed  $attributes
1405     * @return array
1406     */
1407    public function only($attributes)
1408    {
1409        $results = [];
1410
1411        foreach (is_array($attributes) ? $attributes : func_get_args() as $attribute) {
1412            $results[$attribute] = $this->getAttribute($attribute);
1413        }
1414
1415        return $results;
1416    }
1417
1418    /**
1419     * Sync the original attributes with the current.
1420     *
1421     * @return $this
1422     */
1423    public function syncOriginal()
1424    {
1425        $this->original = $this->getAttributes();
1426
1427        return $this;
1428    }
1429
1430    /**
1431     * Sync a single original attribute with its current value.
1432     *
1433     * @param  string  $attribute
1434     * @return $this
1435     */
1436    public function syncOriginalAttribute($attribute)
1437    {
1438        return $this->syncOriginalAttributes($attribute);
1439    }
1440
1441    /**
1442     * Sync multiple original attribute with their current values.
1443     *
1444     * @param  array|string  $attributes
1445     * @return $this
1446     */
1447    public function syncOriginalAttributes($attributes)
1448    {
1449        $attributes = is_array($attributes) ? $attributes : func_get_args();
1450
1451        $modelAttributes = $this->getAttributes();
1452
1453        foreach ($attributes as $attribute) {
1454            $this->original[$attribute] = $modelAttributes[$attribute];
1455        }
1456
1457        return $this;
1458    }
1459
1460    /**
1461     * Sync the changed attributes.
1462     *
1463     * @return $this
1464     */
1465    public function syncChanges()
1466    {
1467        $this->changes = $this->getDirty();
1468
1469        return $this;
1470    }
1471
1472    /**
1473     * Determine if the model or any of the given attribute(s) have been modified.
1474     *
1475     * @param  array|string|null  $attributes
1476     * @return bool
1477     */
1478    public function isDirty($attributes = null)
1479    {
1480        return $this->hasChanges(
1481            $this->getDirty(), is_array($attributes) ? $attributes : func_get_args()
1482        );
1483    }
1484
1485    /**
1486     * Determine if the model and all the given attribute(s) have remained the same.
1487     *
1488     * @param  array|string|null  $attributes
1489     * @return bool
1490     */
1491    public function isClean($attributes = null)
1492    {
1493        return ! $this->isDirty(...func_get_args());
1494    }
1495
1496    /**
1497     * Determine if the model or any of the given attribute(s) have been modified.
1498     *
1499     * @param  array|string|null  $attributes
1500     * @return bool
1501     */
1502    public function wasChanged($attributes = null)
1503    {
1504        return $this->hasChanges(
1505            $this->getChanges(), is_array($attributes) ? $attributes : func_get_args()
1506        );
1507    }
1508
1509    /**
1510     * Determine if any of the given attributes were changed.
1511     *
1512     * @param  array  $changes
1513     * @param  array|string|null  $attributes
1514     * @return bool
1515     */
1516    protected function hasChanges($changes, $attributes = null)
1517    {
1518        // If no specific attributes were provided, we will just see if the dirty array
1519        // already contains any attributes. If it does we will just return that this
1520        // count is greater than zero. Else, we need to check specific attributes.
1521        if (empty($attributes)) {
1522            return count($changes) > 0;
1523        }
1524
1525        // Here we will spin through every attribute and see if this is in the array of
1526        // dirty attributes. If it is, we will return true and if we make it through
1527        // all of the attributes for the entire array we will return false at end.
1528        foreach (Arr::wrap($attributes) as $attribute) {
1529            if (array_key_exists($attribute, $changes)) {
1530                return true;
1531            }
1532        }
1533
1534        return false;
1535    }
1536
1537    /**
1538     * Get the attributes that have been changed since the last sync.
1539     *
1540     * @return array
1541     */
1542    public function getDirty()
1543    {
1544        $dirty = [];
1545
1546        foreach ($this->getAttributes() as $key => $value) {
1547            if (! $this->originalIsEquivalent($key)) {
1548                $dirty[$key] = $value;
1549            }
1550        }
1551
1552        return $dirty;
1553    }
1554
1555    /**
1556     * Get the attributes that were changed.
1557     *
1558     * @return array
1559     */
1560    public function getChanges()
1561    {
1562        return $this->changes;
1563    }
1564
1565    /**
1566     * Determine if the new and old values for a given key are equivalent.
1567     *
1568     * @param  string  $key
1569     * @return bool
1570     */
1571    public function originalIsEquivalent($key)
1572    {
1573        if (! array_key_exists($key, $this->original)) {
1574            return false;
1575        }
1576
1577        $attribute = Arr::get($this->attributes, $key);
1578        $original = Arr::get($this->original, $key);
1579
1580        if ($attribute === $original) {
1581            return true;
1582        } elseif (is_null($attribute)) {
1583            return false;
1584        } elseif ($this->isDateAttribute($key)) {
1585            return $this->fromDateTime($attribute) ===
1586                   $this->fromDateTime($original);
1587        } elseif ($this->hasCast($key, ['object', 'collection'])) {
1588            return $this->castAttribute($key, $attribute) ==
1589                $this->castAttribute($key, $original);
1590        } elseif ($this->hasCast($key, ['real', 'float', 'double'])) {
1591            if (($attribute === null && $original !== null) || ($attribute !== null && $original === null)) {
1592                return false;
1593            }
1594
1595            return abs($this->castAttribute($key, $attribute) - $this->castAttribute($key, $original)) < PHP_FLOAT_EPSILON * 4;
1596        } elseif ($this->hasCast($key, static::$primitiveCastTypes)) {
1597            return $this->castAttribute($key, $attribute) ===
1598                   $this->castAttribute($key, $original);
1599        }
1600
1601        return is_numeric($attribute) && is_numeric($original)
1602               && strcmp((string) $attribute, (string) $original) === 0;
1603    }
1604
1605    /**
1606     * Transform a raw model value using mutators, casts, etc.
1607     *
1608     * @param  string  $key
1609     * @param  mixed  $value
1610     * @return mixed
1611     */
1612    protected function transformModelValue($key, $value)
1613    {
1614        // If the attribute has a get mutator, we will call that then return what
1615        // it returns as the value, which is useful for transforming values on
1616        // retrieval from the model to a form that is more useful for usage.
1617        if ($this->hasGetMutator($key)) {
1618            return $this->mutateAttribute($key, $value);
1619        }
1620
1621        // If the attribute exists within the cast array, we will convert it to
1622        // an appropriate native PHP type dependent upon the associated value
1623        // given with the key in the pair. Dayle made this comment line up.
1624        if ($this->hasCast($key)) {
1625            return $this->castAttribute($key, $value);
1626        }
1627
1628        // If the attribute is listed as a date, we will convert it to a DateTime
1629        // instance on retrieval, which makes it quite convenient to work with
1630        // date fields without having to create a mutator for each property.
1631        if ($value !== null
1632            && \in_array($key, $this->getDates(), false)) {
1633            return $this->asDateTime($value);
1634        }
1635
1636        return $value;
1637    }
1638
1639    /**
1640     * Append attributes to query when building a query.
1641     *
1642     * @param  array|string  $attributes
1643     * @return $this
1644     */
1645    public function append($attributes)
1646    {
1647        $this->appends = array_unique(
1648            array_merge($this->appends, is_string($attributes) ? func_get_args() : $attributes)
1649        );
1650
1651        return $this;
1652    }
1653
1654    /**
1655     * Set the accessors to append to model arrays.
1656     *
1657     * @param  array  $appends
1658     * @return $this
1659     */
1660    public function setAppends(array $appends)
1661    {
1662        $this->appends = $appends;
1663
1664        return $this;
1665    }
1666
1667    /**
1668     * Return whether the accessor attribute has been appended.
1669     *
1670     * @param  string  $attribute
1671     * @return bool
1672     */
1673    public function hasAppended($attribute)
1674    {
1675        return in_array($attribute, $this->appends);
1676    }
1677
1678    /**
1679     * Get the mutated attributes for a given instance.
1680     *
1681     * @return array
1682     */
1683    public function getMutatedAttributes()
1684    {
1685        $class = static::class;
1686
1687        if (! isset(static::$mutatorCache[$class])) {
1688            static::cacheMutatedAttributes($class);
1689        }
1690
1691        return static::$mutatorCache[$class];
1692    }
1693
1694    /**
1695     * Extract and cache all the mutated attributes of a class.
1696     *
1697     * @param  string  $class
1698     * @return void
1699     */
1700    public static function cacheMutatedAttributes($class)
1701    {
1702        static::$mutatorCache[$class] = collect(static::getMutatorMethods($class))->map(function ($match) {
1703            return lcfirst(static::$snakeAttributes ? Str::snake($match) : $match);
1704        })->all();
1705    }
1706
1707    /**
1708     * Get all of the attribute mutator methods.
1709     *
1710     * @param  mixed  $class
1711     * @return array
1712     */
1713    protected static function getMutatorMethods($class)
1714    {
1715        preg_match_all('/(?<=^|;)get([^;]+?)Attribute(;|$)/', implode(';', get_class_methods($class)), $matches);
1716
1717        return $matches[1];
1718    }
1719}
1720