12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175 |
- <?php
-
- namespace Illuminate\Database\Eloquent\Concerns;
-
- use LogicException;
- use DateTimeInterface;
- use Illuminate\Support\Arr;
- use Illuminate\Support\Str;
- use Illuminate\Support\Carbon;
- use Illuminate\Contracts\Support\Arrayable;
- use Illuminate\Database\Eloquent\Relations\Relation;
- use Illuminate\Support\Collection as BaseCollection;
- use Illuminate\Database\Eloquent\JsonEncodingException;
-
- trait HasAttributes
- {
- /**
- * The model's attributes.
- *
- * @var array
- */
- protected $attributes = [];
-
- /**
- * The model attribute's original state.
- *
- * @var array
- */
- protected $original = [];
-
- /**
- * The changed model attributes.
- *
- * @var array
- */
- protected $changes = [];
-
- /**
- * The attributes that should be cast to native types.
- *
- * @var array
- */
- protected $casts = [];
-
- /**
- * The attributes that should be mutated to dates.
- *
- * @var array
- */
- protected $dates = [];
-
- /**
- * The storage format of the model's date columns.
- *
- * @var string
- */
- protected $dateFormat;
-
- /**
- * The accessors to append to the model's array form.
- *
- * @var array
- */
- protected $appends = [];
-
- /**
- * Indicates whether attributes are snake cased on arrays.
- *
- * @var bool
- */
- public static $snakeAttributes = true;
-
- /**
- * The cache of the mutated attributes for each class.
- *
- * @var array
- */
- protected static $mutatorCache = [];
-
- /**
- * Convert the model's attributes to an array.
- *
- * @return array
- */
- public function attributesToArray()
- {
- // If an attribute is a date, we will cast it to a string after converting it
- // to a DateTime / Carbon instance. This is so we will get some consistent
- // formatting while accessing attributes vs. arraying / JSONing a model.
- $attributes = $this->addDateAttributesToArray(
- $attributes = $this->getArrayableAttributes()
- );
-
- $attributes = $this->addMutatedAttributesToArray(
- $attributes, $mutatedAttributes = $this->getMutatedAttributes()
- );
-
- // Next we will handle any casts that have been setup for this model and cast
- // the values to their appropriate type. If the attribute has a mutator we
- // will not perform the cast on those attributes to avoid any confusion.
- $attributes = $this->addCastAttributesToArray(
- $attributes, $mutatedAttributes
- );
-
- // Here we will grab all of the appended, calculated attributes to this model
- // as these attributes are not really in the attributes array, but are run
- // when we need to array or JSON the model for convenience to the coder.
- foreach ($this->getArrayableAppends() as $key) {
- $attributes[$key] = $this->mutateAttributeForArray($key, null);
- }
-
- return $attributes;
- }
-
- /**
- * Add the date attributes to the attributes array.
- *
- * @param array $attributes
- * @return array
- */
- protected function addDateAttributesToArray(array $attributes)
- {
- foreach ($this->getDates() as $key) {
- if (! isset($attributes[$key])) {
- continue;
- }
-
- $attributes[$key] = $this->serializeDate(
- $this->asDateTime($attributes[$key])
- );
- }
-
- return $attributes;
- }
-
- /**
- * Add the mutated attributes to the attributes array.
- *
- * @param array $attributes
- * @param array $mutatedAttributes
- * @return array
- */
- protected function addMutatedAttributesToArray(array $attributes, array $mutatedAttributes)
- {
- foreach ($mutatedAttributes as $key) {
- // We want to spin through all the mutated attributes for this model and call
- // the mutator for the attribute. We cache off every mutated attributes so
- // we don't have to constantly check on attributes that actually change.
- if (! array_key_exists($key, $attributes)) {
- continue;
- }
-
- // Next, we will call the mutator for this attribute so that we can get these
- // mutated attribute's actual values. After we finish mutating each of the
- // attributes we will return this final array of the mutated attributes.
- $attributes[$key] = $this->mutateAttributeForArray(
- $key, $attributes[$key]
- );
- }
-
- return $attributes;
- }
-
- /**
- * Add the casted attributes to the attributes array.
- *
- * @param array $attributes
- * @param array $mutatedAttributes
- * @return array
- */
- protected function addCastAttributesToArray(array $attributes, array $mutatedAttributes)
- {
- foreach ($this->getCasts() as $key => $value) {
- if (! array_key_exists($key, $attributes) || in_array($key, $mutatedAttributes)) {
- continue;
- }
-
- // Here we will cast the attribute. Then, if the cast is a date or datetime cast
- // then we will serialize the date for the array. This will convert the dates
- // to strings based on the date format specified for these Eloquent models.
- $attributes[$key] = $this->castAttribute(
- $key, $attributes[$key]
- );
-
- // If the attribute cast was a date or a datetime, we will serialize the date as
- // a string. This allows the developers to customize how dates are serialized
- // into an array without affecting how they are persisted into the storage.
- if ($attributes[$key] &&
- ($value === 'date' || $value === 'datetime')) {
- $attributes[$key] = $this->serializeDate($attributes[$key]);
- }
-
- if ($attributes[$key] && $this->isCustomDateTimeCast($value)) {
- $attributes[$key] = $attributes[$key]->format(explode(':', $value, 2)[1]);
- }
- }
-
- return $attributes;
- }
-
- /**
- * Get an attribute array of all arrayable attributes.
- *
- * @return array
- */
- protected function getArrayableAttributes()
- {
- return $this->getArrayableItems($this->attributes);
- }
-
- /**
- * Get all of the appendable values that are arrayable.
- *
- * @return array
- */
- protected function getArrayableAppends()
- {
- if (! count($this->appends)) {
- return [];
- }
-
- return $this->getArrayableItems(
- array_combine($this->appends, $this->appends)
- );
- }
-
- /**
- * Get the model's relationships in array form.
- *
- * @return array
- */
- public function relationsToArray()
- {
- $attributes = [];
-
- foreach ($this->getArrayableRelations() as $key => $value) {
- // If the values implements the Arrayable interface we can just call this
- // toArray method on the instances which will convert both models and
- // collections to their proper array form and we'll set the values.
- if ($value instanceof Arrayable) {
- $relation = $value->toArray();
- }
-
- // If the value is null, we'll still go ahead and set it in this list of
- // attributes since null is used to represent empty relationships if
- // if it a has one or belongs to type relationships on the models.
- elseif (is_null($value)) {
- $relation = $value;
- }
-
- // If the relationships snake-casing is enabled, we will snake case this
- // key so that the relation attribute is snake cased in this returned
- // array to the developers, making this consistent with attributes.
- if (static::$snakeAttributes) {
- $key = Str::snake($key);
- }
-
- // If the relation value has been set, we will set it on this attributes
- // list for returning. If it was not arrayable or null, we'll not set
- // the value on the array because it is some type of invalid value.
- if (isset($relation) || is_null($value)) {
- $attributes[$key] = $relation;
- }
-
- unset($relation);
- }
-
- return $attributes;
- }
-
- /**
- * Get an attribute array of all arrayable relations.
- *
- * @return array
- */
- protected function getArrayableRelations()
- {
- return $this->getArrayableItems($this->relations);
- }
-
- /**
- * Get an attribute array of all arrayable values.
- *
- * @param array $values
- * @return array
- */
- protected function getArrayableItems(array $values)
- {
- if (count($this->getVisible()) > 0) {
- $values = array_intersect_key($values, array_flip($this->getVisible()));
- }
-
- if (count($this->getHidden()) > 0) {
- $values = array_diff_key($values, array_flip($this->getHidden()));
- }
-
- return $values;
- }
-
- /**
- * Get an attribute from the model.
- *
- * @param string $key
- * @return mixed
- */
- public function getAttribute($key)
- {
- if (! $key) {
- return;
- }
-
- // If the attribute exists in the attribute array or has a "get" mutator we will
- // get the attribute's value. Otherwise, we will proceed as if the developers
- // are asking for a relationship's value. This covers both types of values.
- if (array_key_exists($key, $this->attributes) ||
- $this->hasGetMutator($key)) {
- return $this->getAttributeValue($key);
- }
-
- // Here we will determine if the model base class itself contains this given key
- // since we don't want to treat any of those methods as relationships because
- // they are all intended as helper methods and none of these are relations.
- if (method_exists(self::class, $key)) {
- return;
- }
-
- return $this->getRelationValue($key);
- }
-
- /**
- * Get a plain attribute (not a relationship).
- *
- * @param string $key
- * @return mixed
- */
- public function getAttributeValue($key)
- {
- $value = $this->getAttributeFromArray($key);
-
- // 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);
- }
-
- // If the attribute exists within the cast array, we will convert it to
- // an appropriate native PHP type dependant 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 (in_array($key, $this->getDates()) &&
- ! is_null($value)) {
- return $this->asDateTime($value);
- }
-
- return $value;
- }
-
- /**
- * Get an attribute from the $attributes array.
- *
- * @param string $key
- * @return mixed
- */
- protected function getAttributeFromArray($key)
- {
- if (isset($this->attributes[$key])) {
- return $this->attributes[$key];
- }
- }
-
- /**
- * Get a relationship.
- *
- * @param string $key
- * @return mixed
- */
- public function getRelationValue($key)
- {
- // If the key already exists in the relationships array, it just means the
- // relationship has already been loaded, so we'll just return it out of
- // here because there is no need to query within the relations twice.
- if ($this->relationLoaded($key)) {
- return $this->relations[$key];
- }
-
- // If the "attribute" exists as a method on the model, we will just assume
- // it is a relationship and will load and return results from the query
- // and hydrate the relationship's value on the "relationships" array.
- if (method_exists($this, $key)) {
- return $this->getRelationshipFromMethod($key);
- }
- }
-
- /**
- * Get a relationship value from a method.
- *
- * @param string $method
- * @return mixed
- *
- * @throws \LogicException
- */
- protected function getRelationshipFromMethod($method)
- {
- $relation = $this->$method();
-
- if (! $relation instanceof Relation) {
- throw new LogicException(sprintf(
- '%s::%s must return a relationship instance.', static::class, $method
- ));
- }
-
- return tap($relation->getResults(), function ($results) use ($method) {
- $this->setRelation($method, $results);
- });
- }
-
- /**
- * Determine if a get mutator exists for an attribute.
- *
- * @param string $key
- * @return bool
- */
- public function hasGetMutator($key)
- {
- return method_exists($this, 'get'.Str::studly($key).'Attribute');
- }
-
- /**
- * Get the value of an attribute using its mutator.
- *
- * @param string $key
- * @param mixed $value
- * @return mixed
- */
- protected function mutateAttribute($key, $value)
- {
- return $this->{'get'.Str::studly($key).'Attribute'}($value);
- }
-
- /**
- * Get the value of an attribute using its mutator for array conversion.
- *
- * @param string $key
- * @param mixed $value
- * @return mixed
- */
- protected function mutateAttributeForArray($key, $value)
- {
- $value = $this->mutateAttribute($key, $value);
-
- return $value instanceof Arrayable ? $value->toArray() : $value;
- }
-
- /**
- * Cast an attribute to a native PHP type.
- *
- * @param string $key
- * @param mixed $value
- * @return mixed
- */
- protected function castAttribute($key, $value)
- {
- if (is_null($value)) {
- return $value;
- }
-
- switch ($this->getCastType($key)) {
- case 'int':
- case 'integer':
- return (int) $value;
- case 'real':
- case 'float':
- case 'double':
- return (float) $value;
- case 'string':
- return (string) $value;
- case 'bool':
- case 'boolean':
- return (bool) $value;
- case 'object':
- return $this->fromJson($value, true);
- case 'array':
- case 'json':
- return $this->fromJson($value);
- case 'collection':
- return new BaseCollection($this->fromJson($value));
- case 'date':
- return $this->asDate($value);
- case 'datetime':
- case 'custom_datetime':
- return $this->asDateTime($value);
- case 'timestamp':
- return $this->asTimestamp($value);
- default:
- return $value;
- }
- }
-
- /**
- * Get the type of cast for a model attribute.
- *
- * @param string $key
- * @return string
- */
- protected function getCastType($key)
- {
- if ($this->isCustomDateTimeCast($this->getCasts()[$key])) {
- return 'custom_datetime';
- }
-
- return trim(strtolower($this->getCasts()[$key]));
- }
-
- /**
- * Determine if the cast type is a custom date time cast.
- *
- * @param string $cast
- * @return bool
- */
- protected function isCustomDateTimeCast($cast)
- {
- return strncmp($cast, 'date:', 5) === 0 ||
- strncmp($cast, 'datetime:', 9) === 0;
- }
-
- /**
- * Set a given attribute on the model.
- *
- * @param string $key
- * @param mixed $value
- * @return mixed
- */
- public function setAttribute($key, $value)
- {
- // First we will check for the presence of a mutator for the set operation
- // which simply lets the developers tweak the attribute as it is set on
- // the model, such as "json_encoding" an listing of data for storage.
- if ($this->hasSetMutator($key)) {
- return $this->setMutatedAttributeValue($key, $value);
- }
-
- // If an attribute is listed as a "date", we'll convert it from a DateTime
- // instance into a form proper for storage on the database tables using
- // the connection grammar's date format. We will auto set the values.
- elseif ($value && $this->isDateAttribute($key)) {
- $value = $this->fromDateTime($value);
- }
-
- if ($this->isJsonCastable($key) && ! is_null($value)) {
- $value = $this->castAttributeAsJson($key, $value);
- }
-
- // If this attribute contains a JSON ->, we'll set the proper value in the
- // attribute's underlying array. This takes care of properly nesting an
- // attribute in the array's value in the case of deeply nested items.
- if (Str::contains($key, '->')) {
- return $this->fillJsonAttribute($key, $value);
- }
-
- $this->attributes[$key] = $value;
-
- return $this;
- }
-
- /**
- * Determine if a set mutator exists for an attribute.
- *
- * @param string $key
- * @return bool
- */
- public function hasSetMutator($key)
- {
- return method_exists($this, 'set'.Str::studly($key).'Attribute');
- }
-
- /**
- * Set the value of an attribute using its mutator.
- *
- * @param string $key
- * @param mixed $value
- * @return mixed
- */
- protected function setMutatedAttributeValue($key, $value)
- {
- return $this->{'set'.Str::studly($key).'Attribute'}($value);
- }
-
- /**
- * Determine if the given attribute is a date or date castable.
- *
- * @param string $key
- * @return bool
- */
- protected function isDateAttribute($key)
- {
- return in_array($key, $this->getDates()) ||
- $this->isDateCastable($key);
- }
-
- /**
- * Set a given JSON attribute on the model.
- *
- * @param string $key
- * @param mixed $value
- * @return $this
- */
- public function fillJsonAttribute($key, $value)
- {
- list($key, $path) = explode('->', $key, 2);
-
- $this->attributes[$key] = $this->asJson($this->getArrayAttributeWithValue(
- $path, $key, $value
- ));
-
- return $this;
- }
-
- /**
- * Get an array attribute with the given key and value set.
- *
- * @param string $path
- * @param string $key
- * @param mixed $value
- * @return $this
- */
- protected function getArrayAttributeWithValue($path, $key, $value)
- {
- return tap($this->getArrayAttributeByKey($key), function (&$array) use ($path, $value) {
- Arr::set($array, str_replace('->', '.', $path), $value);
- });
- }
-
- /**
- * Get an array attribute or return an empty array if it is not set.
- *
- * @param string $key
- * @return array
- */
- protected function getArrayAttributeByKey($key)
- {
- return isset($this->attributes[$key]) ?
- $this->fromJson($this->attributes[$key]) : [];
- }
-
- /**
- * Cast the given attribute to JSON.
- *
- * @param string $key
- * @param mixed $value
- * @return string
- */
- protected function castAttributeAsJson($key, $value)
- {
- $value = $this->asJson($value);
-
- if ($value === false) {
- throw JsonEncodingException::forAttribute(
- $this, $key, json_last_error_msg()
- );
- }
-
- return $value;
- }
-
- /**
- * Encode the given value as JSON.
- *
- * @param mixed $value
- * @return string
- */
- protected function asJson($value)
- {
- return json_encode($value);
- }
-
- /**
- * Decode the given JSON back into an array or object.
- *
- * @param string $value
- * @param bool $asObject
- * @return mixed
- */
- public function fromJson($value, $asObject = false)
- {
- return json_decode($value, ! $asObject);
- }
-
- /**
- * Return a timestamp as DateTime object with time set to 00:00:00.
- *
- * @param mixed $value
- * @return \Illuminate\Support\Carbon
- */
- protected function asDate($value)
- {
- return $this->asDateTime($value)->startOfDay();
- }
-
- /**
- * Return a timestamp as DateTime object.
- *
- * @param mixed $value
- * @return \Illuminate\Support\Carbon
- */
- protected function asDateTime($value)
- {
- // If this value is already a Carbon instance, we shall just return it as is.
- // This prevents us having to re-instantiate a Carbon instance when we know
- // it already is one, which wouldn't be fulfilled by the DateTime check.
- if ($value instanceof Carbon) {
- return $value;
- }
-
- // If the value is already a DateTime instance, we will just skip the rest of
- // these checks since they will be a waste of time, and hinder performance
- // when checking the field. We will just return the DateTime right away.
- if ($value instanceof DateTimeInterface) {
- return new Carbon(
- $value->format('Y-m-d H:i:s.u'), $value->getTimezone()
- );
- }
-
- // If this value is an integer, we will assume it is a UNIX timestamp's value
- // and format a Carbon object from this timestamp. This allows flexibility
- // when defining your date fields as they might be UNIX timestamps here.
- if (is_numeric($value)) {
- return Carbon::createFromTimestamp($value);
- }
-
- // If the value is in simply year, month, day format, we will instantiate the
- // Carbon instances from that format. Again, this provides for simple date
- // fields on the database, while still supporting Carbonized conversion.
- if ($this->isStandardDateFormat($value)) {
- return Carbon::createFromFormat('Y-m-d', $value)->startOfDay();
- }
-
- // Finally, we will just assume this date is in the format used by default on
- // the database connection and use that format to create the Carbon object
- // that is returned back out to the developers after we convert it here.
- return Carbon::createFromFormat(
- str_replace('.v', '.u', $this->getDateFormat()), $value
- );
- }
-
- /**
- * Determine if the given value is a standard date format.
- *
- * @param string $value
- * @return bool
- */
- protected function isStandardDateFormat($value)
- {
- return preg_match('/^(\d{4})-(\d{1,2})-(\d{1,2})$/', $value);
- }
-
- /**
- * Convert a DateTime to a storable string.
- *
- * @param \DateTime|int $value
- * @return string
- */
- public function fromDateTime($value)
- {
- return empty($value) ? $value : $this->asDateTime($value)->format(
- $this->getDateFormat()
- );
- }
-
- /**
- * Return a timestamp as unix timestamp.
- *
- * @param mixed $value
- * @return int
- */
- protected function asTimestamp($value)
- {
- return $this->asDateTime($value)->getTimestamp();
- }
-
- /**
- * Prepare a date for array / JSON serialization.
- *
- * @param \DateTimeInterface $date
- * @return string
- */
- protected function serializeDate(DateTimeInterface $date)
- {
- return $date->format($this->getDateFormat());
- }
-
- /**
- * Get the attributes that should be converted to dates.
- *
- * @return array
- */
- public function getDates()
- {
- $defaults = [static::CREATED_AT, static::UPDATED_AT];
-
- return $this->usesTimestamps()
- ? array_unique(array_merge($this->dates, $defaults))
- : $this->dates;
- }
-
- /**
- * Get the format for database stored dates.
- *
- * @return string
- */
- public function getDateFormat()
- {
- return $this->dateFormat ?: $this->getConnection()->getQueryGrammar()->getDateFormat();
- }
-
- /**
- * Set the date format used by the model.
- *
- * @param string $format
- * @return $this
- */
- public function setDateFormat($format)
- {
- $this->dateFormat = $format;
-
- return $this;
- }
-
- /**
- * Determine whether an attribute should be cast to a native type.
- *
- * @param string $key
- * @param array|string|null $types
- * @return bool
- */
- public function hasCast($key, $types = null)
- {
- if (array_key_exists($key, $this->getCasts())) {
- return $types ? in_array($this->getCastType($key), (array) $types, true) : true;
- }
-
- return false;
- }
-
- /**
- * Get the casts array.
- *
- * @return array
- */
- public function getCasts()
- {
- if ($this->getIncrementing()) {
- return array_merge([$this->getKeyName() => $this->getKeyType()], $this->casts);
- }
-
- return $this->casts;
- }
-
- /**
- * Determine whether a value is Date / DateTime castable for inbound manipulation.
- *
- * @param string $key
- * @return bool
- */
- protected function isDateCastable($key)
- {
- return $this->hasCast($key, ['date', 'datetime']);
- }
-
- /**
- * Determine whether a value is JSON castable for inbound manipulation.
- *
- * @param string $key
- * @return bool
- */
- protected function isJsonCastable($key)
- {
- return $this->hasCast($key, ['array', 'json', 'object', 'collection']);
- }
-
- /**
- * Get all of the current attributes on the model.
- *
- * @return array
- */
- public function getAttributes()
- {
- return $this->attributes;
- }
-
- /**
- * 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();
- }
-
- 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 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->attributes;
-
- return $this;
- }
-
- /**
- * Sync a single original attribute with its current value.
- *
- * @param string $attribute
- * @return $this
- */
- public function syncOriginalAttribute($attribute)
- {
- $this->original[$attribute] = $this->attributes[$attribute];
-
- return $this;
- }
-
- /**
- * Sync the changed attributes.
- *
- * @return $this
- */
- public function syncChanges()
- {
- $this->changes = $this->getDirty();
-
- return $this;
- }
-
- /**
- * Determine if the model or 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 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 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 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 last sync.
- *
- * @return array
- */
- public function getDirty()
- {
- $dirty = [];
-
- foreach ($this->getAttributes() as $key => $value) {
- if (! $this->originalIsEquivalent($key, $value)) {
- $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
- * @param mixed $current
- * @return bool
- */
- protected function originalIsEquivalent($key, $current)
- {
- if (! array_key_exists($key, $this->original)) {
- return false;
- }
-
- $original = $this->getOriginal($key);
-
- if ($current === $original) {
- return true;
- } elseif (is_null($current)) {
- return false;
- } elseif ($this->isDateAttribute($key)) {
- return $this->fromDateTime($current) ===
- $this->fromDateTime($original);
- } elseif ($this->hasCast($key)) {
- return $this->castAttribute($key, $current) ===
- $this->castAttribute($key, $original);
- }
-
- return is_numeric($current) && is_numeric($original)
- && strcmp((string) $current, (string) $original) === 0;
- }
-
- /**
- * 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;
- }
-
- /**
- * 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::$mutatorCache[$class] = collect(static::getMutatorMethods($class))->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];
- }
- }
|