butes) ) ); } } /** * Normalize the response from a custom class caster. * * @param string $key * @param mixed $value * @return array */ protected function normalizeCastClassResponse($key, $value) { return is_array($value) ? $value : [$key => $value]; } /** * Get all of the current attributes on the model. * * @return array */ public function getAttributes() { $this->mergeAttributesFromCachedCasts(); return $this->attributes; } /** * Get all of the current attributes on the model for an insert operation. * * @return array */ protected function getAttributesForInsert() { return $this->getAttributes(); } /** * Set the array of model attributes. No checking is done. * * @param array $attributes * @param bool $sync * @return $this */ public function setRawAttributes(array $attributes, $sync = false) { $this->attributes = $attributes; if ($sync) { $this->syncOriginal(); } $this->classCastCache = []; $this->attributeCastCache = []; return $this; } /** * Get the model's original attribute values. * * @param string|null $key * @param mixed $default * @return mixed|array */ public function getOriginal($key = null, $default = null) { return (new static)->setRawAttributes( $this->original, $sync = true )->getOriginalWithoutRewindingModel($key, $default); } /** * Get the model's original attribute values. * * @param string|null $key * @param mixed $default * @return mixed|array */ protected function getOriginalWithoutRewindingModel($key = null, $default = null) { if ($key) { return $this->transformModelValue( $key, Arr::get($this->original, $key, $default) ); } return Helper::collect($this->original)->mapWithKeys(function ($value, $key) { return [$key => $this->transformModelValue($key, $value)]; })->all(); } /** * Get the model's raw original attribute values. * * @param string|null $key * @param mixed $default * @return mixed|array */ public function getRawOriginal($key = null, $default = null) { return Arr::get($this->original, $key, $default); } /** * Get a subset of the model's attributes. * * @param array|mixed $attributes * @return array */ public function only($attributes) { $results = []; foreach (is_array($attributes) ? $attributes : func_get_args() as $attribute) { $results[$attribute] = $this->getAttribute($attribute); } return $results; } /** * Sync the original attributes with the current. * * @return $this */ public function syncOriginal() { $this->original = $this->getAttributes(); return $this; } /** * Sync a single original attribute with its current value. * * @param string $attribute * @return $this */ public function syncOriginalAttribute($attribute) { return $this->syncOriginalAttributes($attribute); } /** * Sync multiple original attribute with their current values. * * @param array|string $attributes * @return $this */ public function syncOriginalAttributes($attributes) { $attributes = is_array($attributes) ? $attributes : func_get_args(); $modelAttributes = $this->getAttributes(); foreach ($attributes as $attribute) { $this->original[$attribute] = $modelAttributes[$attribute]; } return $this; } /** * Sync the changed attributes. * * @return $this */ public function syncChanges() { $this->changes = $this->getDirty(); return $this; } /** * Determine if the model or any of the given attribute(s) have been modified. * * @param array|string|null $attributes * @return bool */ public function isDirty($attributes = null) { return $this->hasChanges( $this->getDirty(), is_array($attributes) ? $attributes : func_get_args() ); } /** * Determine if the model or all the given attribute(s) have remained the same. * * @param array|string|null $attributes * @return bool */ public function isClean($attributes = null) { return ! $this->isDirty(...func_get_args()); } /** * Determine if the model or any of the given attribute(s) have been modified. * * @param array|string|null $attributes * @return bool */ public function wasChanged($attributes = null) { return $this->hasChanges( $this->getChanges(), is_array($attributes) ? $attributes : func_get_args() ); } /** * Determine if any of the given attributes were changed. * * @param array $changes * @param array|string|null $attributes * @return bool */ protected function hasChanges($changes, $attributes = null) { // If no specific attributes were provided, we will just see if the dirty array // already contains any attributes. If it does we will just return that this // count is greater than zero. Else, we need to check specific attributes. if (empty($attributes)) { return count($changes) > 0; } // Here we will spin through every attribute and see if this is in the array of // dirty attributes. If it is, we will return true and if we make it through // all of the attributes for the entire array we will return false at end. foreach (Arr::wrap($attributes) as $attribute) { if (array_key_exists($attribute, $changes)) { return true; } } return false; } /** * Get the attributes that have been changed since the last sync. * * @return array */ public function getDirty() { $dirty = []; foreach ($this->getAttributes() as $key => $value) { if (! $this->originalIsEquivalent($key)) { $dirty[$key] = $value; } } return $dirty; } /** * Get the attributes that were changed. * * @return array */ public function getChanges() { return $this->changes; } /** * Determine if the new and old values for a given key are equivalent. * * @param string $key * @return bool */ public function originalIsEquivalent($key) { if (! array_key_exists($key, $this->original)) { return false; } $attribute = Arr::get($this->attributes, $key); $original = Arr::get($this->original, $key); if ($attribute === $original) { return true; } elseif (is_null($attribute)) { return false; } elseif ($this->isDateAttribute($key) || $this->isDateCastableWithCustomFormat($key)) { return $this->fromDateTime($attribute) === $this->fromDateTime($original); } elseif ($this->hasCast($key, ['object', 'collection'])) { return $this->fromJson($attribute) === $this->fromJson($original); } elseif ($this->hasCast($key, ['real', 'float', 'double'])) { if (($attribute === null && $original !== null) || ($attribute !== null && $original === null)) { return false; } return abs($this->castAttribute($key, $attribute) - $this->castAttribute($key, $original)) < PHP_FLOAT_EPSILON * 4; } elseif ($this->hasCast($key, static::$primitiveCastTypes)) { return $this->castAttribute($key, $attribute) === $this->castAttribute($key, $original); } elseif ($this->isClassCastable($key) && in_array($this->getCasts()[$key], [AsArrayObject::class, AsCollection::class])) { return $this->fromJson($attribute) === $this->fromJson($original); } return is_numeric($attribute) && is_numeric($original) && strcmp((string) $attribute, (string) $original) === 0; } /** * Transform a raw model value using mutators, casts, etc. * * @param string $key * @param mixed $value * @return mixed */ protected function transformModelValue($key, $value) { // If the attribute has a get mutator, we will call that then return what // it returns as the value, which is useful for transforming values on // retrieval from the model to a form that is more useful for usage. if ($this->hasGetMutator($key)) { return $this->mutateAttribute($key, $value); } elseif ($this->hasAttributeGetMutator($key)) { return $this->mutateAttributeMarkedAttribute($key, $value); } // If the attribute exists within the cast array, we will convert it to // an appropriate native PHP type dependent upon the associated value // given with the key in the pair. Dayle made this comment line up. if ($this->hasCast($key)) { return $this->castAttribute($key, $value); } // If the attribute is listed as a date, we will convert it to a DateTime // instance on retrieval, which makes it quite convenient to work with // date fields without having to create a mutator for each property. if ($value !== null && \in_array($key, $this->getDates(), false)) { return $this->asDateTime($value); } return $value; } /** * Append attributes to query when building a query. * * @param array|string $attributes * @return $this */ public function append($attributes) { $this->appends = array_unique( array_merge($this->appends, is_string($attributes) ? func_get_args() : $attributes) ); return $this; } /** * Set the accessors to append to model arrays. * * @param array $appends * @return $this */ public function setAppends(array $appends) { $this->appends = $appends; return $this; } /** * Return whether the accessor attribute has been appended. * * @param string $attribute * @return bool */ public function hasAppended($attribute) { return in_array($attribute, $this->appends); } /** * Get the mutated attributes for a given instance. * * @return array */ public function getMutatedAttributes() { $class = static::class; if (! isset(static::$mutatorCache[$class])) { static::cacheMutatedAttributes($class); } return static::$mutatorCache[$class]; } /** * Extract and cache all the mutated attributes of a class. * * @param string $class * @return void */ public static function cacheMutatedAttributes($class) { static::$getAttributeMutatorCache[$class] = Helper::collect($attributeMutatorMethods = static::getAttributeMarkedMutatorMethods($class)) ->mapWithKeys(function ($match) { return [lcfirst(static::$snakeAttributes ? Str::snake($match) : $match) => true]; })->all(); static::$mutatorCache[$class] = Helper::collect(static::getMutatorMethods($class)) ->merge($attributeMutatorMethods) ->map(function ($match) { return lcfirst(static::$snakeAttributes ? Str::snake($match) : $match); })->all(); } /** * Get all of the attribute mutator methods. * * @param mixed $class * @return array */ protected static function getMutatorMethods($class) { preg_match_all('/(?<=^|;)get([^;]+?)Attribute(;|$)/', implode(';', get_class_methods($class)), $matches); return $matches[1]; } /** * Get all of the "Attribute" return typed attribute mutator methods. * * @param mixed $class * @return array */ protected static function getAttributeMarkedMutatorMethods($class) { $instance = is_object($class) ? $class : new $class; return Helper::collect((new ReflectionClass($instance))->getMethods())->filter(function ($method) use ($instance) { $returnType = $method->getReturnType(); if ($returnType && $returnType instanceof ReflectionNamedType && $returnType->getName() === Attribute::class) { $method->setAccessible(true); if (is_callable($method->invoke($instance)->get)) { return true; } } return false; })->map->name->values()->all(); } }