| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361 |
- <?php
- namespace Illuminate\Database\Eloquent\Concerns;
- use BackedEnum;
- use Brick\Math\BigDecimal;
- use Brick\Math\Exception\MathException as BrickMathException;
- use Brick\Math\RoundingMode;
- use Carbon\CarbonImmutable;
- use Carbon\CarbonInterface;
- use DateTimeImmutable;
- use DateTimeInterface;
- use Illuminate\Contracts\Database\Eloquent\Castable;
- use Illuminate\Contracts\Database\Eloquent\CastsInboundAttributes;
- use Illuminate\Contracts\Support\Arrayable;
- use Illuminate\Database\Eloquent\Casts\AsArrayObject;
- use Illuminate\Database\Eloquent\Casts\AsCollection;
- use Illuminate\Database\Eloquent\Casts\AsEncryptedArrayObject;
- use Illuminate\Database\Eloquent\Casts\AsEncryptedCollection;
- use Illuminate\Database\Eloquent\Casts\AsEnumArrayObject;
- use Illuminate\Database\Eloquent\Casts\AsEnumCollection;
- use Illuminate\Database\Eloquent\Casts\Attribute;
- use Illuminate\Database\Eloquent\Casts\Json;
- use Illuminate\Database\Eloquent\InvalidCastException;
- use Illuminate\Database\Eloquent\JsonEncodingException;
- use Illuminate\Database\Eloquent\MissingAttributeException;
- use Illuminate\Database\Eloquent\Relations\Relation;
- use Illuminate\Database\LazyLoadingViolationException;
- use Illuminate\Support\Arr;
- use Illuminate\Support\Carbon;
- use Illuminate\Support\Collection as BaseCollection;
- use Illuminate\Support\Exceptions\MathException;
- use Illuminate\Support\Facades\Crypt;
- use Illuminate\Support\Facades\Date;
- use Illuminate\Support\Facades\Hash;
- use Illuminate\Support\Str;
- use InvalidArgumentException;
- use LogicException;
- use ReflectionClass;
- use ReflectionMethod;
- use ReflectionNamedType;
- use RuntimeException;
- use ValueError;
- 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.
- *
- * @var array
- */
- protected $casts = [];
- /**
- * The attributes that have been cast using custom classes.
- *
- * @var array
- */
- protected $classCastCache = [];
- /**
- * The attributes that have been cast using "Attribute" return type mutators.
- *
- * @var array
- */
- protected $attributeCastCache = [];
- /**
- * The built-in, primitive cast types supported by Eloquent.
- *
- * @var string[]
- */
- protected static $primitiveCastTypes = [
- 'array',
- 'bool',
- 'boolean',
- 'collection',
- 'custom_datetime',
- 'date',
- 'datetime',
- 'decimal',
- 'double',
- 'encrypted',
- 'encrypted:array',
- 'encrypted:collection',
- 'encrypted:json',
- 'encrypted:object',
- 'float',
- 'hashed',
- 'immutable_date',
- 'immutable_datetime',
- 'immutable_custom_datetime',
- 'int',
- 'integer',
- 'json',
- 'object',
- 'real',
- 'string',
- 'timestamp',
- ];
- /**
- * 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 = [];
- /**
- * The cache of the "Attribute" return type marked mutated attributes for each class.
- *
- * @var array
- */
- protected static $attributeMutatorCache = [];
- /**
- * The cache of the "Attribute" return type marked mutated, gettable attributes for each class.
- *
- * @var array
- */
- protected static $getAttributeMutatorCache = [];
- /**
- * The cache of the "Attribute" return type marked mutated, settable attributes for each class.
- *
- * @var array
- */
- protected static $setAttributeMutatorCache = [];
- /**
- * The cache of the converted cast types.
- *
- * @var array
- */
- protected static $castTypeCache = [];
- /**
- * The encrypter instance that is used to encrypt attributes.
- *
- * @var \Illuminate\Contracts\Encryption\Encrypter|null
- */
- public static $encrypter;
- /**
- * Initialize the trait.
- *
- * @return void
- */
- protected function initializeHasAttributes()
- {
- $this->casts = $this->ensureCastsAreStringValues(
- array_merge($this->casts, $this->casts()),
- );
- }
- /**
- * 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 (isset($attributes[$key]) && in_array($value, ['date', 'datetime', 'immutable_date', 'immutable_datetime'])) {
- $attributes[$key] = $this->serializeDate($attributes[$key]);
- }
- if (isset($attributes[$key]) && ($this->isCustomDateTimeCast($value) ||
- $this->isImmutableCustomDateTimeCast($value))) {
- $attributes[$key] = $attributes[$key]->format(explode(':', $value, 2)[1]);
- }
- if ($attributes[$key] instanceof DateTimeInterface &&
- $this->isClassCastable($key)) {
- $attributes[$key] = $this->serializeDate($attributes[$key]);
- }
- if (isset($attributes[$key]) && $this->isClassSerializable($key)) {
- $attributes[$key] = $this->serializeClassCastableAttribute($key, $attributes[$key]);
- }
- if ($this->isEnumCastable($key) && (! ($attributes[$key] ?? null) instanceof Arrayable)) {
- $attributes[$key] = isset($attributes[$key]) ? $this->getStorableEnumValue($this->getCasts()[$key], $attributes[$key]) : null;
- }
- if ($attributes[$key] instanceof Arrayable) {
- $attributes[$key] = $attributes[$key]->toArray();
- }
- }
- return $attributes;
- }
- /**
- * Get an attribute array of all arrayable attributes.
- *
- * @return array
- */
- protected function getArrayableAttributes()
- {
- return $this->getArrayableItems($this->getAttributes());
- }
- /**
- * 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 implement 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
- // it has 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;
- }
- /**
- * Determine whether an attribute exists on the model.
- *
- * @param string $key
- * @return bool
- */
- public function hasAttribute($key)
- {
- if (! $key) {
- return false;
- }
- return array_key_exists($key, $this->attributes) ||
- array_key_exists($key, $this->casts) ||
- $this->hasGetMutator($key) ||
- $this->hasAttributeMutator($key) ||
- $this->isClassCastable($key);
- }
- /**
- * 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 ($this->hasAttribute($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 $this->throwMissingAttributeExceptionIfApplicable($key);
- }
- return $this->isRelation($key) || $this->relationLoaded($key)
- ? $this->getRelationValue($key)
- : $this->throwMissingAttributeExceptionIfApplicable($key);
- }
- /**
- * Either throw a missing attribute exception or return null depending on Eloquent's configuration.
- *
- * @param string $key
- * @return null
- *
- * @throws \Illuminate\Database\Eloquent\MissingAttributeException
- */
- protected function throwMissingAttributeExceptionIfApplicable($key)
- {
- if ($this->exists &&
- ! $this->wasRecentlyCreated &&
- static::preventsAccessingMissingAttributes()) {
- if (isset(static::$missingAttributeViolationCallback)) {
- return call_user_func(static::$missingAttributeViolationCallback, $this, $key);
- }
- throw new MissingAttributeException($this, $key);
- }
- return null;
- }
- /**
- * Get a plain attribute (not a relationship).
- *
- * @param string $key
- * @return mixed
- */
- public function getAttributeValue($key)
- {
- return $this->transformModelValue($key, $this->getAttributeFromArray($key));
- }
- /**
- * Get an attribute from the $attributes array.
- *
- * @param string $key
- * @return mixed
- */
- protected function getAttributeFromArray($key)
- {
- return $this->getAttributes()[$key] ?? null;
- }
- /**
- * 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 (! $this->isRelation($key)) {
- return;
- }
- if ($this->preventsLazyLoading) {
- $this->handleLazyLoadingViolation($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.
- return $this->getRelationshipFromMethod($key);
- }
- /**
- * Determine if the given key is a relationship method on the model.
- *
- * @param string $key
- * @return bool
- */
- public function isRelation($key)
- {
- if ($this->hasAttributeMutator($key)) {
- return false;
- }
- return method_exists($this, $key) ||
- $this->relationResolver(static::class, $key);
- }
- /**
- * Handle a lazy loading violation.
- *
- * @param string $key
- * @return mixed
- */
- protected function handleLazyLoadingViolation($key)
- {
- if (isset(static::$lazyLoadingViolationCallback)) {
- return call_user_func(static::$lazyLoadingViolationCallback, $this, $key);
- }
- if (! $this->exists || $this->wasRecentlyCreated) {
- return;
- }
- throw new LazyLoadingViolationException($this, $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) {
- if (is_null($relation)) {
- throw new LogicException(sprintf(
- '%s::%s must return a relationship instance, but "null" was returned. Was the "return" keyword used?', static::class, $method
- ));
- }
- 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');
- }
- /**
- * Determine if a "Attribute" return type marked mutator exists for an attribute.
- *
- * @param string $key
- * @return bool
- */
- public function hasAttributeMutator($key)
- {
- if (isset(static::$attributeMutatorCache[get_class($this)][$key])) {
- return static::$attributeMutatorCache[get_class($this)][$key];
- }
- if (! method_exists($this, $method = Str::camel($key))) {
- return static::$attributeMutatorCache[get_class($this)][$key] = false;
- }
- $returnType = (new ReflectionMethod($this, $method))->getReturnType();
- return static::$attributeMutatorCache[get_class($this)][$key] =
- $returnType instanceof ReflectionNamedType &&
- $returnType->getName() === Attribute::class;
- }
- /**
- * Determine if a "Attribute" return type marked get mutator exists for an attribute.
- *
- * @param string $key
- * @return bool
- */
- public function hasAttributeGetMutator($key)
- {
- if (isset(static::$getAttributeMutatorCache[get_class($this)][$key])) {
- return static::$getAttributeMutatorCache[get_class($this)][$key];
- }
- if (! $this->hasAttributeMutator($key)) {
- return static::$getAttributeMutatorCache[get_class($this)][$key] = false;
- }
- return static::$getAttributeMutatorCache[get_class($this)][$key] = is_callable($this->{Str::camel($key)}()->get);
- }
- /**
- * 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" return type marked attribute using its mutator.
- *
- * @param string $key
- * @param mixed $value
- * @return mixed
- */
- protected function mutateAttributeMarkedAttribute($key, $value)
- {
- if (array_key_exists($key, $this->attributeCastCache)) {
- return $this->attributeCastCache[$key];
- }
- $attribute = $this->{Str::camel($key)}();
- $value = call_user_func($attribute->get ?: function ($value) {
- return $value;
- }, $value, $this->attributes);
- if ($attribute->withCaching || (is_object($value) && $attribute->withObjectCaching)) {
- $this->attributeCastCache[$key] = $value;
- } else {
- unset($this->attributeCastCache[$key]);
- }
- return $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)
- {
- if ($this->isClassCastable($key)) {
- $value = $this->getClassCastableAttributeValue($key, $value);
- } elseif (isset(static::$getAttributeMutatorCache[get_class($this)][$key]) &&
- static::$getAttributeMutatorCache[get_class($this)][$key] === true) {
- $value = $this->mutateAttributeMarkedAttribute($key, $value);
- $value = $value instanceof DateTimeInterface
- ? $this->serializeDate($value)
- : $value;
- } else {
- $value = $this->mutateAttribute($key, $value);
- }
- return $value instanceof Arrayable ? $value->toArray() : $value;
- }
- /**
- * Merge new casts with existing casts on the model.
- *
- * @param array $casts
- * @return $this
- */
- public function mergeCasts($casts)
- {
- $casts = $this->ensureCastsAreStringValues($casts);
- $this->casts = array_merge($this->casts, $casts);
- return $this;
- }
- /**
- * Ensure that the given casts are strings.
- *
- * @param array $casts
- * @return array
- */
- protected function ensureCastsAreStringValues($casts)
- {
- foreach ($casts as $attribute => $cast) {
- $casts[$attribute] = match (true) {
- is_array($cast) => value(function () use ($cast) {
- if (count($cast) === 1) {
- return $cast[0];
- }
- [$cast, $arguments] = [array_shift($cast), $cast];
- return $cast.':'.implode(',', $arguments);
- }),
- default => $cast,
- };
- }
- return $casts;
- }
- /**
- * Cast an attribute to a native PHP type.
- *
- * @param string $key
- * @param mixed $value
- * @return mixed
- */
- protected function castAttribute($key, $value)
- {
- $castType = $this->getCastType($key);
- if (is_null($value) && in_array($castType, static::$primitiveCastTypes)) {
- return $value;
- }
- // If the key is one of the encrypted castable types, we'll first decrypt
- // the value and update the cast type so we may leverage the following
- // logic for casting this value to any additionally specified types.
- if ($this->isEncryptedCastable($key)) {
- $value = $this->fromEncryptedString($value);
- $castType = Str::after($castType, 'encrypted:');
- }
- switch ($castType) {
- case 'int':
- case 'integer':
- return (int) $value;
- case 'real':
- case 'float':
- case 'double':
- return $this->fromFloat($value);
- case 'decimal':
- return $this->asDecimal($value, explode(':', $this->getCasts()[$key], 2)[1]);
- 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 'immutable_date':
- return $this->asDate($value)->toImmutable();
- case 'immutable_custom_datetime':
- case 'immutable_datetime':
- return $this->asDateTime($value)->toImmutable();
- case 'timestamp':
- return $this->asTimestamp($value);
- }
- if ($this->isEnumCastable($key)) {
- return $this->getEnumCastableAttributeValue($key, $value);
- }
- if ($this->isClassCastable($key)) {
- return $this->getClassCastableAttributeValue($key, $value);
- }
- return $value;
- }
- /**
- * Cast the given attribute using a custom cast class.
- *
- * @param string $key
- * @param mixed $value
- * @return mixed
- */
- protected function getClassCastableAttributeValue($key, $value)
- {
- $caster = $this->resolveCasterClass($key);
- $objectCachingDisabled = $caster->withoutObjectCaching ?? false;
- if (isset($this->classCastCache[$key]) && ! $objectCachingDisabled) {
- return $this->classCastCache[$key];
- } else {
- $value = $caster instanceof CastsInboundAttributes
- ? $value
- : $caster->get($this, $key, $value, $this->attributes);
- if ($caster instanceof CastsInboundAttributes ||
- ! is_object($value) ||
- $objectCachingDisabled) {
- unset($this->classCastCache[$key]);
- } else {
- $this->classCastCache[$key] = $value;
- }
- return $value;
- }
- }
- /**
- * Cast the given attribute to an enum.
- *
- * @param string $key
- * @param mixed $value
- * @return mixed
- */
- protected function getEnumCastableAttributeValue($key, $value)
- {
- if (is_null($value)) {
- return;
- }
- $castType = $this->getCasts()[$key];
- if ($value instanceof $castType) {
- return $value;
- }
- return $this->getEnumCaseFromValue($castType, $value);
- }
- /**
- * Get the type of cast for a model attribute.
- *
- * @param string $key
- * @return string
- */
- protected function getCastType($key)
- {
- $castType = $this->getCasts()[$key];
- if (isset(static::$castTypeCache[$castType])) {
- return static::$castTypeCache[$castType];
- }
- if ($this->isCustomDateTimeCast($castType)) {
- $convertedCastType = 'custom_datetime';
- } elseif ($this->isImmutableCustomDateTimeCast($castType)) {
- $convertedCastType = 'immutable_custom_datetime';
- } elseif ($this->isDecimalCast($castType)) {
- $convertedCastType = 'decimal';
- } elseif (class_exists($castType)) {
- $convertedCastType = $castType;
- } else {
- $convertedCastType = trim(strtolower($castType));
- }
- return static::$castTypeCache[$castType] = $convertedCastType;
- }
- /**
- * Increment or decrement the given attribute using the custom cast class.
- *
- * @param string $method
- * @param string $key
- * @param mixed $value
- * @return mixed
- */
- protected function deviateClassCastableAttribute($method, $key, $value)
- {
- return $this->resolveCasterClass($key)->{$method}(
- $this, $key, $value, $this->attributes
- );
- }
- /**
- * Serialize the given attribute using the custom cast class.
- *
- * @param string $key
- * @param mixed $value
- * @return mixed
- */
- protected function serializeClassCastableAttribute($key, $value)
- {
- return $this->resolveCasterClass($key)->serialize(
- $this, $key, $value, $this->attributes
- );
- }
- /**
- * Determine if the cast type is a custom date time cast.
- *
- * @param string $cast
- * @return bool
- */
- protected function isCustomDateTimeCast($cast)
- {
- return str_starts_with($cast, 'date:') ||
- str_starts_with($cast, 'datetime:');
- }
- /**
- * Determine if the cast type is an immutable custom date time cast.
- *
- * @param string $cast
- * @return bool
- */
- protected function isImmutableCustomDateTimeCast($cast)
- {
- return str_starts_with($cast, 'immutable_date:') ||
- str_starts_with($cast, 'immutable_datetime:');
- }
- /**
- * Determine if the cast type is a decimal cast.
- *
- * @param string $cast
- * @return bool
- */
- protected function isDecimalCast($cast)
- {
- return str_starts_with($cast, 'decimal:');
- }
- /**
- * 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
- // this model, such as "json_encoding" a listing of data for storage.
- if ($this->hasSetMutator($key)) {
- return $this->setMutatedAttributeValue($key, $value);
- } elseif ($this->hasAttributeSetMutator($key)) {
- return $this->setAttributeMarkedMutatedAttributeValue($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 (! is_null($value) && $this->isDateAttribute($key)) {
- $value = $this->fromDateTime($value);
- }
- if ($this->isEnumCastable($key)) {
- $this->setEnumCastableAttribute($key, $value);
- return $this;
- }
- if ($this->isClassCastable($key)) {
- $this->setClassCastableAttribute($key, $value);
- return $this;
- }
- if (! is_null($value) && $this->isJsonCastable($key)) {
- $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);
- }
- if (! is_null($value) && $this->isEncryptedCastable($key)) {
- $value = $this->castAttributeAsEncryptedString($key, $value);
- }
- if (! is_null($value) && $this->hasCast($key, 'hashed')) {
- $value = $this->castAttributeAsHashedString($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');
- }
- /**
- * Determine if an "Attribute" return type marked set mutator exists for an attribute.
- *
- * @param string $key
- * @return bool
- */
- public function hasAttributeSetMutator($key)
- {
- $class = get_class($this);
- if (isset(static::$setAttributeMutatorCache[$class][$key])) {
- return static::$setAttributeMutatorCache[$class][$key];
- }
- if (! method_exists($this, $method = Str::camel($key))) {
- return static::$setAttributeMutatorCache[$class][$key] = false;
- }
- $returnType = (new ReflectionMethod($this, $method))->getReturnType();
- return static::$setAttributeMutatorCache[$class][$key] =
- $returnType instanceof ReflectionNamedType &&
- $returnType->getName() === Attribute::class &&
- is_callable($this->{$method}()->set);
- }
- /**
- * 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);
- }
- /**
- * Set the value of a "Attribute" return type marked attribute using its mutator.
- *
- * @param string $key
- * @param mixed $value
- * @return mixed
- */
- protected function setAttributeMarkedMutatedAttributeValue($key, $value)
- {
- $attribute = $this->{Str::camel($key)}();
- $callback = $attribute->set ?: function ($value) use ($key) {
- $this->attributes[$key] = $value;
- };
- $this->attributes = array_merge(
- $this->attributes,
- $this->normalizeCastClassResponse(
- $key, $callback($value, $this->attributes)
- )
- );
- if ($attribute->withCaching || (is_object($value) && $attribute->withObjectCaching)) {
- $this->attributeCastCache[$key] = $value;
- } else {
- unset($this->attributeCastCache[$key]);
- }
- return $this;
- }
- /**
- * 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(), true) ||
- $this->isDateCastable($key);
- }
- /**
- * Set a given JSON attribute on the model.
- *
- * @param string $key
- * @param mixed $value
- * @return $this
- */
- public function fillJsonAttribute($key, $value)
- {
- [$key, $path] = explode('->', $key, 2);
- $value = $this->asJson($this->getArrayAttributeWithValue(
- $path, $key, $value
- ));
- $this->attributes[$key] = $this->isEncryptedCastable($key)
- ? $this->castAttributeAsEncryptedString($key, $value)
- : $value;
- if ($this->isClassCastable($key)) {
- unset($this->classCastCache[$key]);
- }
- return $this;
- }
- /**
- * Set the value of a class castable attribute.
- *
- * @param string $key
- * @param mixed $value
- * @return void
- */
- protected function setClassCastableAttribute($key, $value)
- {
- $caster = $this->resolveCasterClass($key);
- $this->attributes = array_replace(
- $this->attributes,
- $this->normalizeCastClassResponse($key, $caster->set(
- $this, $key, $value, $this->attributes
- ))
- );
- if ($caster instanceof CastsInboundAttributes ||
- ! is_object($value) ||
- ($caster->withoutObjectCaching ?? false)) {
- unset($this->classCastCache[$key]);
- } else {
- $this->classCastCache[$key] = $value;
- }
- }
- /**
- * Set the value of an enum castable attribute.
- *
- * @param string $key
- * @param \UnitEnum|string|int $value
- * @return void
- */
- protected function setEnumCastableAttribute($key, $value)
- {
- $enumClass = $this->getCasts()[$key];
- if (! isset($value)) {
- $this->attributes[$key] = null;
- } elseif (is_object($value)) {
- $this->attributes[$key] = $this->getStorableEnumValue($enumClass, $value);
- } else {
- $this->attributes[$key] = $this->getStorableEnumValue(
- $enumClass, $this->getEnumCaseFromValue($enumClass, $value)
- );
- }
- }
- /**
- * Get an enum case instance from a given class and value.
- *
- * @param string $enumClass
- * @param string|int $value
- * @return \UnitEnum|\BackedEnum
- */
- protected function getEnumCaseFromValue($enumClass, $value)
- {
- return is_subclass_of($enumClass, BackedEnum::class)
- ? $enumClass::from($value)
- : constant($enumClass.'::'.$value);
- }
- /**
- * Get the storable value from the given enum.
- *
- * @param string $expectedEnum
- * @param \UnitEnum|\BackedEnum $value
- * @return string|int
- */
- protected function getStorableEnumValue($expectedEnum, $value)
- {
- if (! $value instanceof $expectedEnum) {
- throw new ValueError(sprintf('Value [%s] is not of the expected enum type [%s].', var_export($value, true), $expectedEnum));
- }
- return $value instanceof BackedEnum
- ? $value->value
- : $value->name;
- }
- /**
- * 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)
- {
- if (! isset($this->attributes[$key])) {
- return [];
- }
- return $this->fromJson(
- $this->isEncryptedCastable($key)
- ? $this->fromEncryptedString($this->attributes[$key])
- : $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);
- }
- /**
- * Decrypt the given encrypted string.
- *
- * @param string $value
- * @return mixed
- */
- public function fromEncryptedString($value)
- {
- return static::currentEncrypter()->decrypt($value, false);
- }
- /**
- * Cast the given attribute to an encrypted string.
- *
- * @param string $key
- * @param mixed $value
- * @return string
- */
- protected function castAttributeAsEncryptedString($key, #[\SensitiveParameter] $value)
- {
- return static::currentEncrypter()->encrypt($value, false);
- }
- /**
- * Set the encrypter instance that will be used to encrypt attributes.
- *
- * @param \Illuminate\Contracts\Encryption\Encrypter|null $encrypter
- * @return void
- */
- public static function encryptUsing($encrypter)
- {
- static::$encrypter = $encrypter;
- }
- /**
- * Get the current encrypter being used by the model.
- *
- * @return \Illuminate\Contracts\Encryption\Encrypter
- */
- protected static function currentEncrypter()
- {
- return static::$encrypter ?? Crypt::getFacadeRoot();
- }
- /**
- * Cast the given attribute to a hashed string.
- *
- * @param string $key
- * @param mixed $value
- * @return string
- */
- protected function castAttributeAsHashedString($key, #[\SensitiveParameter] $value)
- {
- if ($value === null) {
- return null;
- }
- if (! Hash::isHashed($value)) {
- return Hash::make($value);
- }
- if (! Hash::verifyConfiguration($value)) {
- throw new RuntimeException("Could not verify the hashed value's configuration.");
- }
- return $value;
- }
- /**
- * Decode the given float.
- *
- * @param mixed $value
- * @return mixed
- */
- public function fromFloat($value)
- {
- return match ((string) $value) {
- 'Infinity' => INF,
- '-Infinity' => -INF,
- 'NaN' => NAN,
- default => (float) $value,
- };
- }
- /**
- * Return a decimal as string.
- *
- * @param float|string $value
- * @param int $decimals
- * @return string
- */
- protected function asDecimal($value, $decimals)
- {
- try {
- return (string) BigDecimal::of($value)->toScale($decimals, RoundingMode::HALF_UP);
- } catch (BrickMathException $e) {
- throw new MathException('Unable to cast value to a decimal.', previous: $e);
- }
- }
- /**
- * 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 CarbonInterface) {
- return Date::instance($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 Date::parse(
- $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 Date::createFromTimestamp($value, date_default_timezone_get());
- }
- // 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 Date::instance(Carbon::createFromFormat('Y-m-d', $value)->startOfDay());
- }
- $format = $this->getDateFormat();
- // 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.
- try {
- $date = Date::createFromFormat($format, $value);
- } catch (InvalidArgumentException) {
- $date = false;
- }
- return $date ?: Date::parse($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 mixed $value
- * @return string|null
- */
- 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 instanceof DateTimeImmutable ?
- CarbonImmutable::instance($date)->toJSON() :
- Carbon::instance($date)->toJSON();
- }
- /**
- * Get the attributes that should be converted to dates.
- *
- * @return array
- */
- public function getDates()
- {
- return $this->usesTimestamps() ? [
- $this->getCreatedAtColumn(),
- $this->getUpdatedAtColumn(),
- ] : [];
- }
- /**
- * 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 attributes that should be cast.
- *
- * @return array
- */
- public function getCasts()
- {
- if ($this->getIncrementing()) {
- return array_merge([$this->getKeyName() => $this->getKeyType()], $this->casts);
- }
- return $this->casts;
- }
- /**
- * Get the attributes that should be cast.
- *
- * @return array
- */
- protected function casts()
- {
- return [];
- }
- /**
- * 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', 'immutable_date', 'immutable_datetime']);
- }
- /**
- * Determine whether a value is Date / DateTime custom-castable for inbound manipulation.
- *
- * @param string $key
- * @return bool
- */
- protected function isDateCastableWithCustomFormat($key)
- {
- return $this->hasCast($key, ['custom_datetime', 'immutable_custom_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', 'encrypted:array', 'encrypted:collection', 'encrypted:json', 'encrypted:object']);
- }
- /**
- * Determine whether a value is an encrypted castable for inbound manipulation.
- *
- * @param string $key
- * @return bool
- */
- protected function isEncryptedCastable($key)
- {
- return $this->hasCast($key, ['encrypted', 'encrypted:array', 'encrypted:collection', 'encrypted:json', 'encrypted:object']);
- }
- /**
- * Determine if the given key is cast using a custom class.
- *
- * @param string $key
- * @return bool
- *
- * @throws \Illuminate\Database\Eloquent\InvalidCastException
- */
- protected function isClassCastable($key)
- {
- $casts = $this->getCasts();
- if (! array_key_exists($key, $casts)) {
- return false;
- }
- $castType = $this->parseCasterClass($casts[$key]);
- if (in_array($castType, static::$primitiveCastTypes)) {
- return false;
- }
- if (class_exists($castType)) {
- return true;
- }
- throw new InvalidCastException($this->getModel(), $key, $castType);
- }
- /**
- * Determine if the given key is cast using an enum.
- *
- * @param string $key
- * @return bool
- */
- protected function isEnumCastable($key)
- {
- $casts = $this->getCasts();
- if (! array_key_exists($key, $casts)) {
- return false;
- }
- $castType = $casts[$key];
- if (in_array($castType, static::$primitiveCastTypes)) {
- return false;
- }
- return enum_exists($castType);
- }
- /**
- * Determine if the key is deviable using a custom class.
- *
- * @param string $key
- * @return bool
- *
- * @throws \Illuminate\Database\Eloquent\InvalidCastException
- */
- protected function isClassDeviable($key)
- {
- if (! $this->isClassCastable($key)) {
- return false;
- }
- $castType = $this->resolveCasterClass($key);
- return method_exists($castType::class, 'increment') && method_exists($castType::class, 'decrement');
- }
- /**
- * Determine if the key is serializable using a custom class.
- *
- * @param string $key
- * @return bool
- *
- * @throws \Illuminate\Database\Eloquent\InvalidCastException
- */
- protected function isClassSerializable($key)
- {
- return ! $this->isEnumCastable($key) &&
- $this->isClassCastable($key) &&
- method_exists($this->resolveCasterClass($key), 'serialize');
- }
- /**
- * Resolve the custom caster class for a given key.
- *
- * @param string $key
- * @return mixed
- */
- protected function resolveCasterClass($key)
- {
- $castType = $this->getCasts()[$key];
- $arguments = [];
- if (is_string($castType) && str_contains($castType, ':')) {
- $segments = explode(':', $castType, 2);
- $castType = $segments[0];
- $arguments = explode(',', $segments[1]);
- }
- if (is_subclass_of($castType, Castable::class)) {
- $castType = $castType::castUsing($arguments);
- }
- if (is_object($castType)) {
- return $castType;
- }
- return new $castType(...$arguments);
- }
- /**
- * Parse the given caster class, removing any arguments.
- *
- * @param string $class
- * @return string
- */
- protected function parseCasterClass($class)
- {
- return ! str_contains($class, ':')
- ? $class
- : explode(':', $class, 2)[0];
- }
- /**
- * Merge the cast class and attribute cast attributes back into the model.
- *
- * @return void
- */
- protected function mergeAttributesFromCachedCasts()
- {
- $this->mergeAttributesFromClassCasts();
- $this->mergeAttributesFromAttributeCasts();
- }
- /**
- * Merge the cast class attributes back into the model.
- *
- * @return void
- */
- protected function mergeAttributesFromClassCasts()
- {
- foreach ($this->classCastCache as $key => $value) {
- $caster = $this->resolveCasterClass($key);
- $this->attributes = array_merge(
- $this->attributes,
- $caster instanceof CastsInboundAttributes
- ? [$key => $value]
- : $this->normalizeCastClassResponse($key, $caster->set($this, $key, $value, $this->attributes))
- );
- }
- }
- /**
- * Merge the cast class attributes back into the model.
- *
- * @return void
- */
- protected function mergeAttributesFromAttributeCasts()
- {
- foreach ($this->attributeCastCache as $key => $value) {
- $attribute = $this->{Str::camel($key)}();
- if ($attribute->get && ! $attribute->set) {
- continue;
- }
- $callback = $attribute->set ?: function ($value) use ($key) {
- $this->attributes[$key] = $value;
- };
- $this->attributes = array_merge(
- $this->attributes,
- $this->normalizeCastClassResponse(
- $key, $callback($value, $this->attributes)
- )
- );
- }
- }
- /**
- * 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 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());
- }
- /**
- * Discard attribute changes and reset the attributes to their original state.
- *
- * @return $this
- */
- public function discardChanges()
- {
- [$this->attributes, $this->changes] = [$this->original, []];
- return $this;
- }
- /**
- * Determine if the model or any of the given attribute(s) were changed when the model was last saved.
- *
- * @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 when the model was last saved.
- *
- * @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 have been changed since the last sync for an update operation.
- *
- * @return array
- */
- protected function getDirtyForUpdate()
- {
- return $this->getDirty();
- }
- /**
- * Get the attributes that were changed when the model was last saved.
- *
- * @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 ($original === null) {
- return false;
- }
- return abs($this->castAttribute($key, $attribute) - $this->castAttribute($key, $original)) < PHP_FLOAT_EPSILON * 4;
- } elseif ($this->isEncryptedCastable($key) && ! empty(static::currentEncrypter()->getPreviousKeys())) {
- return false;
- } elseif ($this->hasCast($key, static::$primitiveCastTypes)) {
- return $this->castAttribute($key, $attribute) ===
- $this->castAttribute($key, $original);
- } elseif ($this->isClassCastable($key) && Str::startsWith($this->getCasts()[$key], [AsArrayObject::class, AsCollection::class])) {
- return $this->fromJson($attribute) === $this->fromJson($original);
- } elseif ($this->isClassCastable($key) && Str::startsWith($this->getCasts()[$key], [AsEnumArrayObject::class, AsEnumCollection::class])) {
- return $this->fromJson($attribute) === $this->fromJson($original);
- } elseif ($this->isClassCastable($key) && $original !== null && Str::startsWith($this->getCasts()[$key], [AsEncryptedArrayObject::class, AsEncryptedCollection::class])) {
- if (empty(static::currentEncrypter()->getPreviousKeys())) {
- return $this->fromEncryptedString($attribute) === $this->fromEncryptedString($original);
- }
- return false;
- }
- 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)) {
- if (static::preventsAccessingMissingAttributes() &&
- ! array_key_exists($key, $this->attributes) &&
- ($this->isEnumCastable($key) ||
- in_array($this->getCastType($key), static::$primitiveCastTypes))) {
- $this->throwMissingAttributeExceptionIfApplicable($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_values(array_unique(
- array_merge($this->appends, is_string($attributes) ? func_get_args() : $attributes)
- ));
- return $this;
- }
- /**
- * Get the accessors that are being appended to model arrays.
- *
- * @return array
- */
- public function getAppends()
- {
- return $this->appends;
- }
- /**
- * 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()
- {
- if (! isset(static::$mutatorCache[static::class])) {
- static::cacheMutatedAttributes($this);
- }
- return static::$mutatorCache[static::class];
- }
- /**
- * Extract and cache all the mutated attributes of a class.
- *
- * @param object|string $classOrInstance
- * @return void
- */
- public static function cacheMutatedAttributes($classOrInstance)
- {
- $reflection = new ReflectionClass($classOrInstance);
- $class = $reflection->getName();
- static::$getAttributeMutatorCache[$class] =
- collect($attributeMutatorMethods = static::getAttributeMarkedMutatorMethods($classOrInstance))
- ->mapWithKeys(function ($match) {
- return [lcfirst(static::$snakeAttributes ? Str::snake($match) : $match) => true];
- })->all();
- static::$mutatorCache[$class] = 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 collect((new ReflectionClass($instance))->getMethods())->filter(function ($method) use ($instance) {
- $returnType = $method->getReturnType();
- if ($returnType instanceof ReflectionNamedType &&
- $returnType->getName() === Attribute::class) {
- if (is_callable($method->invoke($instance)->get)) {
- return true;
- }
- }
- return false;
- })->map->name->values()->all();
- }
- }
|