From 4d4c098ffe8fdf309898a09a4f4a9c4b451c35d6 Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Fri, 27 Dec 2019 13:20:50 +0300 Subject: [PATCH 01/30] [6.x] The `hasAttributes` trait is divided into two: `hasAttributes` and `hasCasts` --- .../Eloquent/Concerns/HasAttributes.php | 257 ---------------- .../Database/Eloquent/Concerns/HasCasts.php | 276 ++++++++++++++++++ src/Illuminate/Database/Eloquent/Model.php | 1 + 3 files changed, 277 insertions(+), 257 deletions(-) create mode 100644 src/Illuminate/Database/Eloquent/Concerns/HasCasts.php diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index 9fb153be7a13..5c15944f30df 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -5,11 +5,9 @@ use Carbon\CarbonInterface; use DateTimeInterface; use Illuminate\Contracts\Support\Arrayable; -use Illuminate\Database\Eloquent\JsonEncodingException; use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Support\Arr; use Illuminate\Support\Carbon; -use Illuminate\Support\Collection as BaseCollection; use Illuminate\Support\Facades\Date; use Illuminate\Support\Str; use LogicException; @@ -37,13 +35,6 @@ trait HasAttributes */ protected $changes = []; - /** - * The attributes that should be cast to native types. - * - * @var array - */ - protected $casts = []; - /** * The attributes that should be mutated to dates. * @@ -163,47 +154,6 @@ protected function addMutatedAttributesToArray(array $attributes, array $mutated 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]); - } - - if ($attributes[$key] instanceof Arrayable) { - $attributes[$key] = $attributes[$key]->toArray(); - } - } - - return $attributes; - } - /** * Get an attribute array of all arrayable attributes. * @@ -468,95 +418,6 @@ protected function mutateAttributeForArray($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 $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 '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'; - } - - if ($this->isDecimalCast($this->getCasts()[$key])) { - return 'decimal'; - } - - 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; - } - - /** - * Determine if the cast type is a decimal cast. - * - * @param string $cast - * @return bool - */ - protected function isDecimalCast($cast) - { - return strncmp($cast, 'decimal:', 8) === 0; - } - /** * Set a given attribute on the model. * @@ -619,18 +480,6 @@ 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(), true) || - $this->isDateCastable($key); - } - /** * Set a given JSON attribute on the model. * @@ -676,26 +525,6 @@ protected function getArrayAttributeByKey($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. * @@ -899,58 +728,6 @@ public function setDateFormat($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. * @@ -1153,40 +930,6 @@ 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 - */ - public 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, ['object', 'collection'])) { - return $this->castAttribute($key, $current) == - $this->castAttribute($key, $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. * diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php b/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php new file mode 100644 index 000000000000..3060149eba59 --- /dev/null +++ b/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php @@ -0,0 +1,276 @@ +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 if the new and old values for a given key are equivalent. + * + * @param string $key + * @param mixed $current + * + * @return bool + */ + public 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, ['object', 'collection'])) { + return $this->castAttribute($key, $current) == + $this->castAttribute($key, $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; + } + + /** + * 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]); + } + + if ($attributes[$key] instanceof Arrayable) { + $attributes[$key] = $attributes[$key]->toArray(); + } + } + + return $attributes; + } + + /** + * 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 $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 Collection($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'; + } + + if ($this->isDecimalCast($this->getCasts()[$key])) { + return 'decimal'; + } + + return trim(strtolower($this->getCasts()[$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; + } + + /** + * 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; + } + + /** + * Determine if the cast type is a decimal cast. + * + * @param string $cast + * + * @return bool + */ + protected function isDecimalCast($cast) + { + return strncmp($cast, 'decimal:', 8) === 0; + } + + /** + * 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); + } + + /** + * 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']); + } +} diff --git a/src/Illuminate/Database/Eloquent/Model.php b/src/Illuminate/Database/Eloquent/Model.php index d020fef30653..f1586b33410b 100644 --- a/src/Illuminate/Database/Eloquent/Model.php +++ b/src/Illuminate/Database/Eloquent/Model.php @@ -20,6 +20,7 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializable, QueueableEntity, UrlRoutable { use Concerns\HasAttributes, + Concerns\HasCasts, Concerns\HasEvents, Concerns\HasGlobalScopes, Concerns\HasRelationships, From 1dfd194b542f9031b795afb230701a09b1fa9a22 Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Sat, 28 Dec 2019 00:23:36 +0300 Subject: [PATCH 02/30] [6.x] Added creation of custom Cast classes for Eloquent --- .../Contracts/Database/Eloquent/Castable.php | 24 ++++ .../Eloquent/Concerns/HasAttributes.php | 4 + .../Database/Eloquent/Concerns/HasCasts.php | 107 +++++++++++++++--- .../EloquentModelCustomCastingTest.php | 101 +++++++++++++++++ 4 files changed, 222 insertions(+), 14 deletions(-) create mode 100644 src/Illuminate/Contracts/Database/Eloquent/Castable.php create mode 100644 tests/Integration/Database/EloquentModelCustomCastingTest.php diff --git a/src/Illuminate/Contracts/Database/Eloquent/Castable.php b/src/Illuminate/Contracts/Database/Eloquent/Castable.php new file mode 100644 index 000000000000..412fcd4d9f21 --- /dev/null +++ b/src/Illuminate/Contracts/Database/Eloquent/Castable.php @@ -0,0 +1,24 @@ +fromDateTime($value); } + if ($this->isCustomCastable($key) && ! is_null($value)) { + $value = $this->toCustomCastable($key, $value); + } + if ($this->isJsonCastable($key) && ! is_null($value)) { $value = $this->castAttributeAsJson($key, $value); } diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php b/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php index 3060149eba59..ec417bef3d35 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php @@ -2,6 +2,9 @@ namespace Illuminate\Database\Eloquent\Concerns; +use Illuminate\Container\Container; +use Illuminate\Contracts\Container\BindingResolutionException; +use Illuminate\Contracts\Database\Eloquent\Castable; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Database\Eloquent\JsonEncodingException; use Illuminate\Support\Collection; @@ -25,11 +28,13 @@ trait HasCasts */ public function hasCast($key, $types = null) { - if (array_key_exists($key, $this->getCasts())) { - return $types ? in_array($this->getCastType($key), (array) $types, true) : true; + if (! array_key_exists($key, $this->getCasts())) { + return false; } - return false; + return $types + ? in_array($this->getCastType($key), (array) $types, true) + : true; } /** @@ -39,11 +44,14 @@ public function hasCast($key, $types = null) */ public function getCasts() { - if ($this->getIncrementing()) { - return array_merge([$this->getKeyName() => $this->getKeyType()], $this->casts); - } + return $this->getIncrementing() + ? array_merge([$this->getKeyName() => $this->getKeyType()], $this->casts) + : $this->casts; + } - return $this->casts; + public function getCast($key) + { + return $this->getCasts()[$key] ?? null; } /** @@ -52,6 +60,8 @@ public function getCasts() * @param string $key * @param mixed $current * + * @throws BindingResolutionException + * * @return bool */ public function originalIsEquivalent($key, $current) @@ -87,6 +97,8 @@ public function originalIsEquivalent($key, $current) * @param array $attributes * @param array $mutatedAttributes * + * @throws BindingResolutionException + * * @return array */ protected function addCastAttributesToArray(array $attributes, array $mutatedAttributes) @@ -111,7 +123,7 @@ protected function addCastAttributesToArray(array $attributes, array $mutatedAtt $attributes[$key] = $this->serializeDate($attributes[$key]); } - if ($attributes[$key] && $this->isCustomDateTimeCast($value)) { + if ($this->isCustomDateTimeCast($value)) { $attributes[$key] = $attributes[$key]->format(explode(':', $value, 2)[1]); } @@ -129,9 +141,11 @@ protected function addCastAttributesToArray(array $attributes, array $mutatedAtt * @param string $key * @param mixed $value * + * @throws BindingResolutionException + * * @return mixed */ - protected function castAttribute($key, $value) + protected function castAttribute($key, $value = null) { if (is_null($value)) { return $value; @@ -146,7 +160,7 @@ protected function castAttribute($key, $value) case 'double': return $this->fromFloat($value); case 'decimal': - return $this->asDecimal($value, explode(':', $this->getCasts()[$key], 2)[1]); + return $this->asDecimal($value, explode(':', $this->getCast($key), 2)[1]); case 'string': return (string) $value; case 'bool': @@ -167,7 +181,9 @@ protected function castAttribute($key, $value) case 'timestamp': return $this->asTimestamp($value); default: - return $value; + return $this->isCustomCastable($key) + ? $this->fromCustomCastable($key, $value) + : $value; } } @@ -180,15 +196,17 @@ protected function castAttribute($key, $value) */ protected function getCastType($key) { - if ($this->isCustomDateTimeCast($this->getCasts()[$key])) { + $cast = $this->getCast($key); + + if ($this->isCustomDateTimeCast($cast)) { return 'custom_datetime'; } - if ($this->isDecimalCast($this->getCasts()[$key])) { + if ($this->isDecimalCast($cast)) { return 'decimal'; } - return trim(strtolower($this->getCasts()[$key])); + return trim(strtolower($cast)); } /** @@ -250,6 +268,22 @@ protected function isDateAttribute($key) $this->isDateCastable($key); } + /** + * Is the checked value a custom Cast. + * + * @param string $key + * + * @return bool + */ + protected function isCustomCastable($key) + { + if ($cast = $this->getCast($key)) { + return is_subclass_of($cast, Castable::class); + } + + return false; + } + /** * Determine whether a value is Date / DateTime castable for inbound manipulation. * @@ -273,4 +307,49 @@ protected function isJsonCastable($key) { return $this->hasCast($key, ['array', 'json', 'object', 'collection']); } + + /** + * Getting the execution result from a user Cast object. + * + * @param string $key + * @param null $value + * + * @throws BindingResolutionException + * + * @return mixed + */ + protected function fromCustomCastable($key, $value = null) + { + return $this + ->normalizeHandlerToCallable($key) + ->get($value); + } + + /** + * @param $key + * @param null $value + * + * @throws BindingResolutionException + * + * @return mixed + */ + protected function toCustomCastable($key, $value = null) + { + return $this + ->normalizeHandlerToCallable($key) + ->set($value); + } + + /** + * @param string $key + * + * @throws BindingResolutionException + * + * @return Castable + */ + protected function normalizeHandlerToCallable($key) + { + return Container::getInstance() + ->make($this->getCast($key)); + } } diff --git a/tests/Integration/Database/EloquentModelCustomCastingTest.php b/tests/Integration/Database/EloquentModelCustomCastingTest.php new file mode 100644 index 000000000000..366cf94db762 --- /dev/null +++ b/tests/Integration/Database/EloquentModelCustomCastingTest.php @@ -0,0 +1,101 @@ + 'foobar', + 'field_2' => 20, + 'field_3' => '08:19:12', + ]); + + $this->assertSame(['f', 'o', 'o', 'b', 'a', 'r'], $item->toArray()['field_1']); + + $this->assertSame(0.2, $item->toArray()['field_2']); + + $this->assertIsNumeric($item->toArray()['field_3']); + + $this->assertSame( + strtotime('08:19:12'), + $item->toArray()['field_3'] + ); + } + + protected function setUp(): void + { + parent::setUp(); + + Schema::create('test_model1', function (Blueprint $table) { + $table->increments('id'); + $table->string('field_1')->nullable(); + $table->integer('field_2')->nullable(); + $table->time('field_3')->nullable(); + }); + } +} + +class TestModel extends Model +{ + public $table = 'test_model1'; + + public $timestamps = false; + + public $casts = [ + 'field_1' => StringCast::class, + 'field_2' => NumberCast::class, + 'field_3' => TimeCast::class, + ]; + + protected $guarded = ['id']; +} + +class TimeCast implements Castable +{ + public function get($value) + { + return strtotime($value); + } + + public function set($value) + { + return is_numeric($value) + ? date('H:i:s', strtotime($value)) + : $value; + } +} + +class StringCast implements Castable +{ + public function get($value) + { + return str_split($value); + } + + public function set($value) + { + return is_array($value) + ? implode('', $value) + : $value; + } +} + +class NumberCast implements Castable +{ + public function get($value) + { + return $value / 100; + } + + public function set($value) + { + return $value; + } +} From 3920b0183ed284048a144b3bd4debd8a174c7f0c Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Sat, 28 Dec 2019 00:23:53 +0300 Subject: [PATCH 03/30] [6.x] Added make console command of custom Cast --- .../Foundation/Console/CastMakeCommand.php | 51 +++++++++++++++++++ .../Foundation/Console/stubs/cast.stub | 32 ++++++++++++ .../Providers/ArtisanServiceProvider.php | 9 ++++ 3 files changed, 92 insertions(+) create mode 100644 src/Illuminate/Foundation/Console/CastMakeCommand.php create mode 100644 src/Illuminate/Foundation/Console/stubs/cast.stub diff --git a/src/Illuminate/Foundation/Console/CastMakeCommand.php b/src/Illuminate/Foundation/Console/CastMakeCommand.php new file mode 100644 index 000000000000..21b9164d9063 --- /dev/null +++ b/src/Illuminate/Foundation/Console/CastMakeCommand.php @@ -0,0 +1,51 @@ + 'command.mail.make', 'MiddlewareMake' => 'command.middleware.make', 'ModelMake' => 'command.model.make', + 'CastMake' => 'command.cast.make', 'NotificationMake' => 'command.notification.make', 'NotificationTable' => 'command.notification.table', 'ObserverMake' => 'command.observer.make', @@ -486,6 +488,13 @@ protected function registerModelMakeCommand() }); } + protected function registerCastMakeCommand() + { + $this->app->singleton('command.cast.make', function ($app) { + return new CastMakeCommand($app['files']); + }); + } + /** * Register the command. * From 67e25625542152e9eaf52647b5145084b9170b30 Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Sat, 28 Dec 2019 01:53:32 +0300 Subject: [PATCH 04/30] Updated codestyle See https://laravel.com/docs/6.x/contributions#phpdoc --- .../Contracts/Database/Eloquent/Castable.php | 6 +- .../Database/Eloquent/Concerns/HasCasts.php | 71 ++++++++----------- 2 files changed, 30 insertions(+), 47 deletions(-) diff --git a/src/Illuminate/Contracts/Database/Eloquent/Castable.php b/src/Illuminate/Contracts/Database/Eloquent/Castable.php index 412fcd4d9f21..5a18bd2b9ab0 100644 --- a/src/Illuminate/Contracts/Database/Eloquent/Castable.php +++ b/src/Illuminate/Contracts/Database/Eloquent/Castable.php @@ -7,8 +7,7 @@ interface Castable /** * Get a given attribute from the model. * - * @param mixed $value - * + * @param mixed $value * @return mixed */ public function get($value); @@ -16,8 +15,7 @@ public function get($value); /** * Set a given attribute on the model. * - * @param mixed $value - * + * @param mixed $value * @return mixed */ public function set($value); diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php b/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php index ec417bef3d35..49ca23a4ac97 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php @@ -21,9 +21,8 @@ trait HasCasts /** * Determine whether an attribute should be cast to a native type. * - * @param string $key - * @param array|string|null $types - * + * @param string $key + * @param array|string|null $types * @return bool */ public function hasCast($key, $types = null) @@ -57,12 +56,11 @@ public function getCast($key) /** * Determine if the new and old values for a given key are equivalent. * - * @param string $key - * @param mixed $current + * @param string $key + * @param mixed $current + * @return bool * * @throws BindingResolutionException - * - * @return bool */ public function originalIsEquivalent($key, $current) { @@ -94,12 +92,11 @@ public function originalIsEquivalent($key, $current) /** * Add the casted attributes to the attributes array. * - * @param array $attributes - * @param array $mutatedAttributes + * @param array $attributes + * @param array $mutatedAttributes + * @return array * * @throws BindingResolutionException - * - * @return array */ protected function addCastAttributesToArray(array $attributes, array $mutatedAttributes) { @@ -138,12 +135,11 @@ protected function addCastAttributesToArray(array $attributes, array $mutatedAtt /** * Cast an attribute to a native PHP type. * - * @param string $key - * @param mixed $value + * @param string $key + * @param mixed $value + * @return mixed * * @throws BindingResolutionException - * - * @return mixed */ protected function castAttribute($key, $value = null) { @@ -190,8 +186,7 @@ protected function castAttribute($key, $value = null) /** * Get the type of cast for a model attribute. * - * @param string $key - * + * @param string $key * @return string */ protected function getCastType($key) @@ -212,9 +207,8 @@ protected function getCastType($key) /** * Cast the given attribute to JSON. * - * @param string $key - * @param mixed $value - * + * @param string $key + * @param mixed $value * @return string */ protected function castAttributeAsJson($key, $value) @@ -233,8 +227,7 @@ protected function castAttributeAsJson($key, $value) /** * Determine if the cast type is a custom date time cast. * - * @param string $cast - * + * @param string $cast * @return bool */ protected function isCustomDateTimeCast($cast) @@ -246,8 +239,7 @@ protected function isCustomDateTimeCast($cast) /** * Determine if the cast type is a decimal cast. * - * @param string $cast - * + * @param string $cast * @return bool */ protected function isDecimalCast($cast) @@ -258,8 +250,7 @@ protected function isDecimalCast($cast) /** * Determine if the given attribute is a date or date castable. * - * @param string $key - * + * @param string $key * @return bool */ protected function isDateAttribute($key) @@ -271,8 +262,7 @@ protected function isDateAttribute($key) /** * Is the checked value a custom Cast. * - * @param string $key - * + * @param string $key * @return bool */ protected function isCustomCastable($key) @@ -287,8 +277,7 @@ protected function isCustomCastable($key) /** * Determine whether a value is Date / DateTime castable for inbound manipulation. * - * @param string $key - * + * @param string $key * @return bool */ protected function isDateCastable($key) @@ -299,8 +288,7 @@ protected function isDateCastable($key) /** * Determine whether a value is JSON castable for inbound manipulation. * - * @param string $key - * + * @param string $key * @return bool */ protected function isJsonCastable($key) @@ -311,12 +299,11 @@ protected function isJsonCastable($key) /** * Getting the execution result from a user Cast object. * - * @param string $key - * @param null $value + * @param string $key + * @param mixed $value + * @return mixed * * @throws BindingResolutionException - * - * @return mixed */ protected function fromCustomCastable($key, $value = null) { @@ -326,12 +313,11 @@ protected function fromCustomCastable($key, $value = null) } /** - * @param $key - * @param null $value + * @param string $key + * @param mixed $value + * @return mixed * * @throws BindingResolutionException - * - * @return mixed */ protected function toCustomCastable($key, $value = null) { @@ -341,11 +327,10 @@ protected function toCustomCastable($key, $value = null) } /** - * @param string $key + * @param string $key + * @return Castable * * @throws BindingResolutionException - * - * @return Castable */ protected function normalizeHandlerToCallable($key) { From 3914f7377c714285ebef8a6ae0127a5995c18187 Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Sat, 28 Dec 2019 14:10:22 +0300 Subject: [PATCH 05/30] Fixed docblocks --- .../Database/Eloquent/Concerns/HasCasts.php | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php b/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php index 49ca23a4ac97..e9ffe036fb42 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php @@ -3,7 +3,6 @@ namespace Illuminate\Database\Eloquent\Concerns; use Illuminate\Container\Container; -use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Contracts\Database\Eloquent\Castable; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Database\Eloquent\JsonEncodingException; @@ -48,6 +47,12 @@ public function getCasts() : $this->casts; } + /** + * Get the cast from casts array. + * + * @param string $key + * @return mixed|null + */ public function getCast($key) { return $this->getCasts()[$key] ?? null; @@ -60,7 +65,7 @@ public function getCast($key) * @param mixed $current * @return bool * - * @throws BindingResolutionException + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function originalIsEquivalent($key, $current) { @@ -96,7 +101,7 @@ public function originalIsEquivalent($key, $current) * @param array $mutatedAttributes * @return array * - * @throws BindingResolutionException + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ protected function addCastAttributesToArray(array $attributes, array $mutatedAttributes) { @@ -139,7 +144,7 @@ protected function addCastAttributesToArray(array $attributes, array $mutatedAtt * @param mixed $value * @return mixed * - * @throws BindingResolutionException + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ protected function castAttribute($key, $value = null) { @@ -303,7 +308,7 @@ protected function isJsonCastable($key) * @param mixed $value * @return mixed * - * @throws BindingResolutionException + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ protected function fromCustomCastable($key, $value = null) { @@ -313,11 +318,13 @@ protected function fromCustomCastable($key, $value = null) } /** + * Converting a value by custom Cast. + * * @param string $key * @param mixed $value * @return mixed * - * @throws BindingResolutionException + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ protected function toCustomCastable($key, $value = null) { @@ -327,10 +334,12 @@ protected function toCustomCastable($key, $value = null) } /** + * Getting a custom cast instance. + * * @param string $key - * @return Castable + * @return \Illuminate\Contracts\Database\Eloquent\Castable * - * @throws BindingResolutionException + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ protected function normalizeHandlerToCallable($key) { From ec1ba0116837e0198bd0477f0ef60d89bb394298 Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Sat, 28 Dec 2019 14:21:07 +0300 Subject: [PATCH 06/30] Fixed docblock --- src/Illuminate/Database/Eloquent/Concerns/HasCasts.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php b/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php index e9ffe036fb42..e0f1a54a4b99 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php @@ -51,7 +51,7 @@ public function getCasts() * Get the cast from casts array. * * @param string $key - * @return mixed|null + * @return mixed */ public function getCast($key) { From f67cb97c2c2271a31212c51a064a155d6c87bcc7 Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Sun, 29 Dec 2019 01:04:22 +0300 Subject: [PATCH 07/30] Fixed transfer of null value to custom castes --- .../Contracts/Database/Eloquent/Castable.php | 4 +-- .../Eloquent/Concerns/HasAttributes.php | 2 +- .../EloquentModelCustomCastingTest.php | 30 +++++++++++++++---- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/Illuminate/Contracts/Database/Eloquent/Castable.php b/src/Illuminate/Contracts/Database/Eloquent/Castable.php index 5a18bd2b9ab0..dca92c00ae53 100644 --- a/src/Illuminate/Contracts/Database/Eloquent/Castable.php +++ b/src/Illuminate/Contracts/Database/Eloquent/Castable.php @@ -10,7 +10,7 @@ interface Castable * @param mixed $value * @return mixed */ - public function get($value); + public function get($value = null); /** * Set a given attribute on the model. @@ -18,5 +18,5 @@ public function get($value); * @param mixed $value * @return mixed */ - public function set($value); + public function set($value = null); } diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index ce734501fc92..d510772b88f4 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -441,7 +441,7 @@ public function setAttribute($key, $value) $value = $this->fromDateTime($value); } - if ($this->isCustomCastable($key) && ! is_null($value)) { + if ($this->isCustomCastable($key)) { $value = $this->toCustomCastable($key, $value); } diff --git a/tests/Integration/Database/EloquentModelCustomCastingTest.php b/tests/Integration/Database/EloquentModelCustomCastingTest.php index 366cf94db762..f08ab2366420 100644 --- a/tests/Integration/Database/EloquentModelCustomCastingTest.php +++ b/tests/Integration/Database/EloquentModelCustomCastingTest.php @@ -15,6 +15,7 @@ public function testFoo() 'field_1' => 'foobar', 'field_2' => 20, 'field_3' => '08:19:12', + 'field_4' => null, ]); $this->assertSame(['f', 'o', 'o', 'b', 'a', 'r'], $item->toArray()['field_1']); @@ -27,6 +28,8 @@ public function testFoo() strtotime('08:19:12'), $item->toArray()['field_3'] ); + + $this->assertSame(null, $item->toArray()['field_4']); } protected function setUp(): void @@ -38,6 +41,7 @@ protected function setUp(): void $table->string('field_1')->nullable(); $table->integer('field_2')->nullable(); $table->time('field_3')->nullable(); + $table->string('field_4')->nullable(); }); } } @@ -52,6 +56,7 @@ class TestModel extends Model 'field_1' => StringCast::class, 'field_2' => NumberCast::class, 'field_3' => TimeCast::class, + 'field_4' => NullCast::class, ]; protected $guarded = ['id']; @@ -59,12 +64,12 @@ class TestModel extends Model class TimeCast implements Castable { - public function get($value) + public function get($value = null) { return strtotime($value); } - public function set($value) + public function set($value = null) { return is_numeric($value) ? date('H:i:s', strtotime($value)) @@ -74,12 +79,12 @@ public function set($value) class StringCast implements Castable { - public function get($value) + public function get($value = null) { return str_split($value); } - public function set($value) + public function set($value = null) { return is_array($value) ? implode('', $value) @@ -89,13 +94,26 @@ public function set($value) class NumberCast implements Castable { - public function get($value) + public function get($value = null) { return $value / 100; } - public function set($value) + public function set($value = null) { return $value; } } + +class NullCast implements Castable +{ + public function get($value = null) + { + return null; + } + + public function set($value = null) + { + return null; + } +} From 38b62f9b7c7c2a65e008bfc03c697a4f1c360bd8 Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Sun, 29 Dec 2019 01:06:18 +0300 Subject: [PATCH 08/30] Fixed getting values for custom castes --- src/Illuminate/Database/Eloquent/Concerns/HasCasts.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php b/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php index e0f1a54a4b99..719df57b7d98 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php @@ -150,6 +150,8 @@ protected function castAttribute($key, $value = null) { if (is_null($value)) { return $value; + } elseif ($this->isCustomCastable($key)) { + return $this->fromCustomCastable($key, $value); } switch ($this->getCastType($key)) { @@ -182,9 +184,7 @@ protected function castAttribute($key, $value = null) case 'timestamp': return $this->asTimestamp($value); default: - return $this->isCustomCastable($key) - ? $this->fromCustomCastable($key, $value) - : $value; + return $value; } } From f7689b4278b474219ac2aa9e931c052fb3fa5d98 Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Sun, 29 Dec 2019 01:15:13 +0300 Subject: [PATCH 09/30] Added verification and storage of initialized instances of custom castes --- .../Database/Eloquent/Concerns/HasCasts.php | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php b/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php index 719df57b7d98..8cbea44405c8 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php @@ -2,7 +2,6 @@ namespace Illuminate\Database\Eloquent\Concerns; -use Illuminate\Container\Container; use Illuminate\Contracts\Database\Eloquent\Castable; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Database\Eloquent\JsonEncodingException; @@ -17,6 +16,13 @@ trait HasCasts */ protected $casts = []; + /** + * Initialized instances of custom castes. + * + * @var array + */ + protected $casts_instances = []; + /** * Determine whether an attribute should be cast to a native type. * @@ -343,7 +349,12 @@ protected function toCustomCastable($key, $value = null) */ protected function normalizeHandlerToCallable($key) { - return Container::getInstance() - ->make($this->getCast($key)); + if (! array_key_exists($key, $this->casts_instances)) { + $cast = $this->getCast($key); + + $this->casts_instances[$key] = new $cast; + } + + return $this->casts_instances[$key]; } } From 0503111b7c157daf344f2a903220bfb086e645b5 Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Sun, 29 Dec 2019 01:16:48 +0300 Subject: [PATCH 10/30] Fixed null-casting test --- tests/Integration/Database/EloquentModelCustomCastingTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Integration/Database/EloquentModelCustomCastingTest.php b/tests/Integration/Database/EloquentModelCustomCastingTest.php index f08ab2366420..fd25482a9114 100644 --- a/tests/Integration/Database/EloquentModelCustomCastingTest.php +++ b/tests/Integration/Database/EloquentModelCustomCastingTest.php @@ -109,11 +109,11 @@ class NullCast implements Castable { public function get($value = null) { - return null; + return $value; } public function set($value = null) { - return null; + return $value; } } From 99f4dedfcce7ce1f1b23e8e83441e384c06f88cc Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Sun, 29 Dec 2019 01:34:16 +0300 Subject: [PATCH 11/30] Fixed procedure for checking NULL values --- src/Illuminate/Database/Eloquent/Concerns/HasCasts.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php b/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php index 8cbea44405c8..74e63a662290 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php @@ -154,10 +154,10 @@ protected function addCastAttributesToArray(array $attributes, array $mutatedAtt */ protected function castAttribute($key, $value = null) { - if (is_null($value)) { - return $value; - } elseif ($this->isCustomCastable($key)) { + if ($this->isCustomCastable($key)) { return $this->fromCustomCastable($key, $value); + } elseif (is_null($value)) { + return $value; } switch ($this->getCastType($key)) { From 27eb546d1e80090e131c98c6d4b6edfb13b10a23 Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Sun, 29 Dec 2019 01:34:31 +0300 Subject: [PATCH 12/30] Added test for checking NULL values --- .../EloquentModelCustomCastingTest.php | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/Integration/Database/EloquentModelCustomCastingTest.php b/tests/Integration/Database/EloquentModelCustomCastingTest.php index fd25482a9114..19cb61c700c2 100644 --- a/tests/Integration/Database/EloquentModelCustomCastingTest.php +++ b/tests/Integration/Database/EloquentModelCustomCastingTest.php @@ -16,6 +16,7 @@ public function testFoo() 'field_2' => 20, 'field_3' => '08:19:12', 'field_4' => null, + 'field_5' => null, ]); $this->assertSame(['f', 'o', 'o', 'b', 'a', 'r'], $item->toArray()['field_1']); @@ -30,6 +31,8 @@ public function testFoo() ); $this->assertSame(null, $item->toArray()['field_4']); + + $this->assertSame('foo', $item->toArray()['field_5']); } protected function setUp(): void @@ -42,6 +45,7 @@ protected function setUp(): void $table->integer('field_2')->nullable(); $table->time('field_3')->nullable(); $table->string('field_4')->nullable(); + $table->string('field_5')->nullable(); }); } } @@ -57,6 +61,7 @@ class TestModel extends Model 'field_2' => NumberCast::class, 'field_3' => TimeCast::class, 'field_4' => NullCast::class, + 'field_5' => NullChangedCast::class, ]; protected $guarded = ['id']; @@ -114,6 +119,19 @@ public function get($value = null) public function set($value = null) { - return $value; + return null; + } +} + +class NullChangedCast implements Castable +{ + public function get($value = null) + { + return 'foo'; + } + + public function set($value = null) + { + return null; } } From 4b8a2d1d6d3ee26f00a00ed0091dfbcd3c478b89 Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Sun, 29 Dec 2019 01:36:01 +0300 Subject: [PATCH 13/30] Fixed `return null` style-ci errors --- tests/Integration/Database/EloquentModelCustomCastingTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Integration/Database/EloquentModelCustomCastingTest.php b/tests/Integration/Database/EloquentModelCustomCastingTest.php index 19cb61c700c2..f91456aed7d1 100644 --- a/tests/Integration/Database/EloquentModelCustomCastingTest.php +++ b/tests/Integration/Database/EloquentModelCustomCastingTest.php @@ -119,7 +119,7 @@ public function get($value = null) public function set($value = null) { - return null; + return $value; } } @@ -132,6 +132,6 @@ public function get($value = null) public function set($value = null) { - return null; + return $value; } } From 0954ebff56526a0e863a77a36ab735336f249891 Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Sun, 29 Dec 2019 01:39:40 +0300 Subject: [PATCH 14/30] Fixed cast.stub file for the custom Cast console command creating --- src/Illuminate/Foundation/Console/stubs/cast.stub | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Illuminate/Foundation/Console/stubs/cast.stub b/src/Illuminate/Foundation/Console/stubs/cast.stub index 614642d213a0..1c24edcfee61 100644 --- a/src/Illuminate/Foundation/Console/stubs/cast.stub +++ b/src/Illuminate/Foundation/Console/stubs/cast.stub @@ -9,11 +9,10 @@ class DummyClass implements Castable /** * Get a given attribute from the model. * - * @param mixed $value - * + * @param mixed $value * @return mixed */ - public function get($value) + public function get($value = null) { return $value; } @@ -21,11 +20,10 @@ class DummyClass implements Castable /** * Set a given attribute on the model. * - * @param mixed $value - * + * @param mixed $value * @return mixed */ - public function set($value) + public function set($value = null) { return $value; } From ad8adbcccf773a7d66c0207c6e15f76d4fdd0ddb Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Sun, 29 Dec 2019 13:30:02 +0300 Subject: [PATCH 15/30] Changed the variable name from snake_case to camelCase. --- src/Illuminate/Database/Eloquent/Concerns/HasCasts.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php b/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php index 74e63a662290..418218f0f21e 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php @@ -21,7 +21,7 @@ trait HasCasts * * @var array */ - protected $casts_instances = []; + protected $castsInstances = []; /** * Determine whether an attribute should be cast to a native type. @@ -349,12 +349,12 @@ protected function toCustomCastable($key, $value = null) */ protected function normalizeHandlerToCallable($key) { - if (! array_key_exists($key, $this->casts_instances)) { + if (! array_key_exists($key, $this->castsInstances)) { $cast = $this->getCast($key); - $this->casts_instances[$key] = new $cast; + $this->castsInstances[$key] = new $cast; } - return $this->casts_instances[$key]; + return $this->castsInstances[$key]; } } From 6fa8bd1c16dd7003d41a733625126a65d22f05ec Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Sun, 29 Dec 2019 13:32:57 +0300 Subject: [PATCH 16/30] Updated docblocks --- .../Database/Eloquent/Concerns/HasCasts.php | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php b/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php index 418218f0f21e..65ecd8ba3854 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php @@ -17,9 +17,9 @@ trait HasCasts protected $casts = []; /** - * Initialized instances of custom castes. + * Initialized instances of custom casts. * - * @var array + * @var \Illuminate\Contracts\Database\Eloquent\Castable[] */ protected $castsInstances = []; @@ -70,8 +70,6 @@ public function getCast($key) * @param string $key * @param mixed $current * @return bool - * - * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function originalIsEquivalent($key, $current) { @@ -106,8 +104,6 @@ public function originalIsEquivalent($key, $current) * @param array $attributes * @param array $mutatedAttributes * @return array - * - * @throws \Illuminate\Contracts\Container\BindingResolutionException */ protected function addCastAttributesToArray(array $attributes, array $mutatedAttributes) { @@ -149,8 +145,6 @@ protected function addCastAttributesToArray(array $attributes, array $mutatedAtt * @param string $key * @param mixed $value * @return mixed - * - * @throws \Illuminate\Contracts\Container\BindingResolutionException */ protected function castAttribute($key, $value = null) { @@ -313,8 +307,6 @@ protected function isJsonCastable($key) * @param string $key * @param mixed $value * @return mixed - * - * @throws \Illuminate\Contracts\Container\BindingResolutionException */ protected function fromCustomCastable($key, $value = null) { @@ -329,8 +321,6 @@ protected function fromCustomCastable($key, $value = null) * @param string $key * @param mixed $value * @return mixed - * - * @throws \Illuminate\Contracts\Container\BindingResolutionException */ protected function toCustomCastable($key, $value = null) { @@ -344,8 +334,6 @@ protected function toCustomCastable($key, $value = null) * * @param string $key * @return \Illuminate\Contracts\Database\Eloquent\Castable - * - * @throws \Illuminate\Contracts\Container\BindingResolutionException */ protected function normalizeHandlerToCallable($key) { From febda924816131f4841ee87d54f7c80deda59f5c Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Sun, 29 Dec 2019 13:34:19 +0300 Subject: [PATCH 17/30] Updated make:cast command description --- src/Illuminate/Foundation/Console/CastMakeCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Console/CastMakeCommand.php b/src/Illuminate/Foundation/Console/CastMakeCommand.php index 21b9164d9063..6728ecf0c71c 100644 --- a/src/Illuminate/Foundation/Console/CastMakeCommand.php +++ b/src/Illuminate/Foundation/Console/CastMakeCommand.php @@ -18,7 +18,7 @@ class CastMakeCommand extends GeneratorCommand * * @var string */ - protected $description = 'Create a new cast mutator'; + protected $description = 'Create a new custom Eloquent attribute caster'; /** * The type of class being generated. From d5ddc3b5dc01f84d9b80b3012426806bfc90fa6b Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Sun, 29 Dec 2019 13:44:41 +0300 Subject: [PATCH 18/30] Updated test for DateTime custom case testing --- .../EloquentModelCustomCastingTest.php | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/tests/Integration/Database/EloquentModelCustomCastingTest.php b/tests/Integration/Database/EloquentModelCustomCastingTest.php index f91456aed7d1..3c15315cf549 100644 --- a/tests/Integration/Database/EloquentModelCustomCastingTest.php +++ b/tests/Integration/Database/EloquentModelCustomCastingTest.php @@ -2,6 +2,8 @@ namespace Illuminate\Tests\Integration\Database; +use DateTime; +use DateTimeInterface; use Illuminate\Contracts\Database\Eloquent\Castable; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Schema\Blueprint; @@ -23,12 +25,9 @@ public function testFoo() $this->assertSame(0.2, $item->toArray()['field_2']); - $this->assertIsNumeric($item->toArray()['field_3']); + $this->assertInstanceOf(DateTimeInterface::class, $item->toArray()['field_3']); - $this->assertSame( - strtotime('08:19:12'), - $item->toArray()['field_3'] - ); + $this->assertSame('08:19:12', $item->toArray()['field_3']->format('H:i:s')); $this->assertSame(null, $item->toArray()['field_4']); @@ -69,15 +68,20 @@ class TestModel extends Model class TimeCast implements Castable { + /** + * @param mixed $value + * @return DateTime + * @throws \Exception + */ public function get($value = null) { - return strtotime($value); + return new DateTime($value); } public function set($value = null) { return is_numeric($value) - ? date('H:i:s', strtotime($value)) + ? DateTime::createFromFormat('H:i:s', $value)->format('H:i:s') : $value; } } From ce3985ac7c478bc5f2699a66e2ef1e4feeef06d4 Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Sun, 29 Dec 2019 14:23:37 +0300 Subject: [PATCH 19/30] Added the ability to store the key and the original value in custom cast. --- src/Illuminate/Database/Eloquent/Cast.php | 68 +++++++++++++++++++ .../Database/Eloquent/Concerns/HasCasts.php | 8 ++- .../Foundation/Console/stubs/cast.stub | 4 +- .../EloquentModelCustomCastingTest.php | 14 ++-- 4 files changed, 83 insertions(+), 11 deletions(-) create mode 100644 src/Illuminate/Database/Eloquent/Cast.php diff --git a/src/Illuminate/Database/Eloquent/Cast.php b/src/Illuminate/Database/Eloquent/Cast.php new file mode 100644 index 000000000000..60a04476b067 --- /dev/null +++ b/src/Illuminate/Database/Eloquent/Cast.php @@ -0,0 +1,68 @@ +keyName; + } + + /** + * Setting the field name. + * + * @param string $value + * @return $this + */ + public function setKeyName($value) + { + $this->keyName = $value; + + return $this; + } + + /** + * Getting the original value. + * + * @return mixed + */ + public function getOriginalValue() + { + return $this->originalValue; + } + + /** + * Setting the original value. + * + * @param mixed $value + * @return $this + */ + public function setOriginalValue($value = null) + { + $this->originalValue = $value; + + return $this; + } +} diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php b/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php index 65ecd8ba3854..4aa4eabb88d9 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php @@ -19,7 +19,7 @@ trait HasCasts /** * Initialized instances of custom casts. * - * @var \Illuminate\Contracts\Database\Eloquent\Castable[] + * @var \Illuminate\Database\Eloquent\Cast[] */ protected $castsInstances = []; @@ -312,6 +312,8 @@ protected function fromCustomCastable($key, $value = null) { return $this ->normalizeHandlerToCallable($key) + ->setKeyName($key) + ->setOriginalValue($value) ->get($value); } @@ -326,6 +328,8 @@ protected function toCustomCastable($key, $value = null) { return $this ->normalizeHandlerToCallable($key) + ->setKeyName($key) + ->setOriginalValue($value) ->set($value); } @@ -333,7 +337,7 @@ protected function toCustomCastable($key, $value = null) * Getting a custom cast instance. * * @param string $key - * @return \Illuminate\Contracts\Database\Eloquent\Castable + * @return \Illuminate\Database\Eloquent\Cast */ protected function normalizeHandlerToCallable($key) { diff --git a/src/Illuminate/Foundation/Console/stubs/cast.stub b/src/Illuminate/Foundation/Console/stubs/cast.stub index 1c24edcfee61..6cefc7c9b093 100644 --- a/src/Illuminate/Foundation/Console/stubs/cast.stub +++ b/src/Illuminate/Foundation/Console/stubs/cast.stub @@ -2,9 +2,9 @@ namespace DummyNamespace; -use Illuminate\Contracts\Database\Eloquent\Castable; +use Illuminate\Database\Eloquent\Cast; -class DummyClass implements Castable +class DummyClass extends Cast { /** * Get a given attribute from the model. diff --git a/tests/Integration/Database/EloquentModelCustomCastingTest.php b/tests/Integration/Database/EloquentModelCustomCastingTest.php index 3c15315cf549..053a28e79365 100644 --- a/tests/Integration/Database/EloquentModelCustomCastingTest.php +++ b/tests/Integration/Database/EloquentModelCustomCastingTest.php @@ -4,14 +4,14 @@ use DateTime; use DateTimeInterface; -use Illuminate\Contracts\Database\Eloquent\Castable; +use Illuminate\Database\Eloquent\Cast; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class EloquentModelCustomCastingTest extends DatabaseTestCase { - public function testFoo() + public function testValues() { $item = TestModel::create([ 'field_1' => 'foobar', @@ -66,7 +66,7 @@ class TestModel extends Model protected $guarded = ['id']; } -class TimeCast implements Castable +class TimeCast extends Cast { /** * @param mixed $value @@ -86,7 +86,7 @@ public function set($value = null) } } -class StringCast implements Castable +class StringCast extends Cast { public function get($value = null) { @@ -101,7 +101,7 @@ public function set($value = null) } } -class NumberCast implements Castable +class NumberCast extends Cast { public function get($value = null) { @@ -114,7 +114,7 @@ public function set($value = null) } } -class NullCast implements Castable +class NullCast extends Cast { public function get($value = null) { @@ -127,7 +127,7 @@ public function set($value = null) } } -class NullChangedCast implements Castable +class NullChangedCast extends Cast { public function get($value = null) { From e578d21e5e3cdd135f723478c52ef8030f625e63 Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Sun, 29 Dec 2019 18:07:58 +0300 Subject: [PATCH 20/30] The `get` and `set` methods are renamed `fromDatabase` and `toDatabase` for Cast --- .../Contracts/Database/Eloquent/Castable.php | 6 +- src/Illuminate/Database/Eloquent/Cast.php | 68 ------------------- .../Database/Eloquent/Concerns/HasCasts.php | 12 ++-- .../Foundation/Console/stubs/cast.stub | 10 +-- .../EloquentModelCustomCastingTest.php | 32 ++++----- 5 files changed, 30 insertions(+), 98 deletions(-) delete mode 100644 src/Illuminate/Database/Eloquent/Cast.php diff --git a/src/Illuminate/Contracts/Database/Eloquent/Castable.php b/src/Illuminate/Contracts/Database/Eloquent/Castable.php index dca92c00ae53..e63cc547802b 100644 --- a/src/Illuminate/Contracts/Database/Eloquent/Castable.php +++ b/src/Illuminate/Contracts/Database/Eloquent/Castable.php @@ -7,16 +7,18 @@ interface Castable /** * Get a given attribute from the model. * + * @param string $key * @param mixed $value * @return mixed */ - public function get($value = null); + public function fromDatabase($key, $value = null); /** * Set a given attribute on the model. * + * @param string $key * @param mixed $value * @return mixed */ - public function set($value = null); + public function toDatabase($key, $value = null); } diff --git a/src/Illuminate/Database/Eloquent/Cast.php b/src/Illuminate/Database/Eloquent/Cast.php deleted file mode 100644 index 60a04476b067..000000000000 --- a/src/Illuminate/Database/Eloquent/Cast.php +++ /dev/null @@ -1,68 +0,0 @@ -keyName; - } - - /** - * Setting the field name. - * - * @param string $value - * @return $this - */ - public function setKeyName($value) - { - $this->keyName = $value; - - return $this; - } - - /** - * Getting the original value. - * - * @return mixed - */ - public function getOriginalValue() - { - return $this->originalValue; - } - - /** - * Setting the original value. - * - * @param mixed $value - * @return $this - */ - public function setOriginalValue($value = null) - { - $this->originalValue = $value; - - return $this; - } -} diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php b/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php index 4aa4eabb88d9..a0dbfa9edbda 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php @@ -19,7 +19,7 @@ trait HasCasts /** * Initialized instances of custom casts. * - * @var \Illuminate\Database\Eloquent\Cast[] + * @var \Illuminate\Contracts\Database\Eloquent\Castable[] */ protected $castsInstances = []; @@ -312,9 +312,7 @@ protected function fromCustomCastable($key, $value = null) { return $this ->normalizeHandlerToCallable($key) - ->setKeyName($key) - ->setOriginalValue($value) - ->get($value); + ->fromDatabase($key, $value); } /** @@ -328,16 +326,14 @@ protected function toCustomCastable($key, $value = null) { return $this ->normalizeHandlerToCallable($key) - ->setKeyName($key) - ->setOriginalValue($value) - ->set($value); + ->toDatabase($key, $value); } /** * Getting a custom cast instance. * * @param string $key - * @return \Illuminate\Database\Eloquent\Cast + * @return \Illuminate\Contracts\Database\Eloquent\Castable */ protected function normalizeHandlerToCallable($key) { diff --git a/src/Illuminate/Foundation/Console/stubs/cast.stub b/src/Illuminate/Foundation/Console/stubs/cast.stub index 6cefc7c9b093..ee46b1de4c44 100644 --- a/src/Illuminate/Foundation/Console/stubs/cast.stub +++ b/src/Illuminate/Foundation/Console/stubs/cast.stub @@ -2,17 +2,18 @@ namespace DummyNamespace; -use Illuminate\Database\Eloquent\Cast; +use Illuminate\Contracts\Database\Eloquent\Castable; -class DummyClass extends Cast +class DummyClass implements Castable { /** * Get a given attribute from the model. * + * @param string $key * @param mixed $value * @return mixed */ - public function get($value = null) + public function fromDatabase($key, $value = null) { return $value; } @@ -20,10 +21,11 @@ class DummyClass extends Cast /** * Set a given attribute on the model. * + * @param string $key * @param mixed $value * @return mixed */ - public function set($value = null) + public function toDatabase($key, $value = null) { return $value; } diff --git a/tests/Integration/Database/EloquentModelCustomCastingTest.php b/tests/Integration/Database/EloquentModelCustomCastingTest.php index 053a28e79365..2a1fb453e262 100644 --- a/tests/Integration/Database/EloquentModelCustomCastingTest.php +++ b/tests/Integration/Database/EloquentModelCustomCastingTest.php @@ -4,7 +4,7 @@ use DateTime; use DateTimeInterface; -use Illuminate\Database\Eloquent\Cast; +use Illuminate\Contracts\Database\Eloquent\Castable; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; @@ -66,19 +66,19 @@ class TestModel extends Model protected $guarded = ['id']; } -class TimeCast extends Cast +class TimeCast implements Castable { /** * @param mixed $value * @return DateTime * @throws \Exception */ - public function get($value = null) + public function fromDatabase($key, $value = null) { return new DateTime($value); } - public function set($value = null) + public function toDatabase($key, $value = null) { return is_numeric($value) ? DateTime::createFromFormat('H:i:s', $value)->format('H:i:s') @@ -86,14 +86,14 @@ public function set($value = null) } } -class StringCast extends Cast +class StringCast implements Castable { - public function get($value = null) + public function fromDatabase($key, $value = null) { return str_split($value); } - public function set($value = null) + public function toDatabase($key, $value = null) { return is_array($value) ? implode('', $value) @@ -101,40 +101,40 @@ public function set($value = null) } } -class NumberCast extends Cast +class NumberCast implements Castable { - public function get($value = null) + public function fromDatabase($key, $value = null) { return $value / 100; } - public function set($value = null) + public function toDatabase($key, $value = null) { return $value; } } -class NullCast extends Cast +class NullCast implements Castable { - public function get($value = null) + public function fromDatabase($key, $value = null) { return $value; } - public function set($value = null) + public function toDatabase($key, $value = null) { return $value; } } -class NullChangedCast extends Cast +class NullChangedCast implements Castable { - public function get($value = null) + public function fromDatabase($key, $value = null) { return 'foo'; } - public function set($value = null) + public function toDatabase($key, $value = null) { return $value; } From 92db2cd8f056f2f0712dd3db93f2cb9ebe0b658f Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Sun, 29 Dec 2019 22:50:38 +0300 Subject: [PATCH 21/30] The `hasAttributes` trait split has been canceled --- .../Eloquent/Concerns/HasAttributes.php | 342 ++++++++++++++++- .../Database/Eloquent/Concerns/HasCasts.php | 348 ------------------ src/Illuminate/Database/Eloquent/Model.php | 1 - 3 files changed, 338 insertions(+), 353 deletions(-) delete mode 100644 src/Illuminate/Database/Eloquent/Concerns/HasCasts.php diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index d510772b88f4..49859a61984f 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -4,10 +4,13 @@ use Carbon\CarbonInterface; use DateTimeInterface; +use Illuminate\Contracts\Database\Eloquent\Castable; use Illuminate\Contracts\Support\Arrayable; +use Illuminate\Database\Eloquent\JsonEncodingException; use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Support\Arr; use Illuminate\Support\Carbon; +use Illuminate\Support\Collection as BaseCollection; use Illuminate\Support\Facades\Date; use Illuminate\Support\Str; use LogicException; @@ -35,6 +38,20 @@ trait HasAttributes */ protected $changes = []; + /** + * The attributes that should be cast to native types. + * + * @var array + */ + protected $casts = []; + + /** + * Initialized instances of custom casts. + * + * @var \Illuminate\Contracts\Database\Eloquent\Castable[] + */ + protected $castsInstances = []; + /** * The attributes that should be mutated to dates. * @@ -154,6 +171,47 @@ protected function addMutatedAttributesToArray(array $attributes, array $mutated 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]); + } + + if ($attributes[$key] instanceof Arrayable) { + $attributes[$key] = $attributes[$key]->toArray(); + } + } + + return $attributes; + } + /** * Get an attribute array of all arrayable attributes. * @@ -418,6 +476,114 @@ protected function mutateAttributeForArray($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 ($this->isCustomCastable($key)) { + return $this->fromCustomCastable($key, $value); + } elseif (is_null($value)) { + return $value; + } + + switch ($this->getCastType($key)) { + 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 '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) + { + $cast = $this->getCast($key); + + if ($this->isCustomDateTimeCast($cast)) { + return 'custom_datetime'; + } + + if ($this->isDecimalCast($cast)) { + return 'decimal'; + } + + return trim(strtolower($cast)); + } + + /** + * 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; + } + + /** + * Determine if the cast type is a decimal cast. + * + * @param string $cast + * @return bool + */ + protected function isDecimalCast($cast) + { + return strncmp($cast, 'decimal:', 8) === 0; + } + + /** + * Is the checked value a custom Cast. + * + * @param string $key + * @return bool + */ + protected function isCustomCastable($key) + { + if ($cast = $this->getCast($key)) { + return is_subclass_of($cast, Castable::class); + } + + return false; + } + /** * Set a given attribute on the model. * @@ -441,10 +607,6 @@ public function setAttribute($key, $value) $value = $this->fromDateTime($value); } - if ($this->isCustomCastable($key)) { - $value = $this->toCustomCastable($key, $value); - } - if ($this->isJsonCastable($key) && ! is_null($value)) { $value = $this->castAttributeAsJson($key, $value); } @@ -484,6 +646,18 @@ 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(), true) || + $this->isDateCastable($key); + } + /** * Set a given JSON attribute on the model. * @@ -529,6 +703,26 @@ protected function getArrayAttributeByKey($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. * @@ -732,6 +926,112 @@ public function setDateFormat($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() + { + return $this->getIncrementing() + ? array_merge([$this->getKeyName() => $this->getKeyType()], $this->casts) + : $this->casts; + } + + /** + * Get the cast from casts array. + * + * @param string $key + * @return mixed + */ + public function getCast($key) + { + return $this->getCasts()[$key] ?? null; + } + + /** + * 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']); + } + + /** + * Getting the execution result from a user Cast object. + * + * @param string $key + * @param mixed $value + * @return mixed + */ + protected function fromCustomCastable($key, $value = null) + { + return $this + ->normalizeCastToCallable($key) + ->fromDatabase($key, $value); + } + + /** + * Converting a value by custom Cast. + * + * @param string $key + * @param mixed $value + * @return mixed + */ + protected function toCustomCastable($key, $value = null) + { + return $this + ->normalizeCastToCallable($key) + ->toDatabase($key, $value); + } + + /** + * Getting a custom cast instance. + * + * @param string $key + * @return \Illuminate\Contracts\Database\Eloquent\Castable + */ + protected function normalizeCastToCallable($key) + { + if (! array_key_exists($key, $this->castsInstances)) { + $cast = $this->getCast($key); + + $this->castsInstances[$key] = new $cast; + } + + return $this->castsInstances[$key]; + } + /** * Get all of the current attributes on the model. * @@ -934,6 +1234,40 @@ 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 + */ + public 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, ['object', 'collection'])) { + return $this->castAttribute($key, $current) == + $this->castAttribute($key, $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. * diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php b/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php deleted file mode 100644 index a0dbfa9edbda..000000000000 --- a/src/Illuminate/Database/Eloquent/Concerns/HasCasts.php +++ /dev/null @@ -1,348 +0,0 @@ -getCasts())) { - return false; - } - - return $types - ? in_array($this->getCastType($key), (array) $types, true) - : true; - } - - /** - * Get the casts array. - * - * @return array - */ - public function getCasts() - { - return $this->getIncrementing() - ? array_merge([$this->getKeyName() => $this->getKeyType()], $this->casts) - : $this->casts; - } - - /** - * Get the cast from casts array. - * - * @param string $key - * @return mixed - */ - public function getCast($key) - { - return $this->getCasts()[$key] ?? null; - } - - /** - * Determine if the new and old values for a given key are equivalent. - * - * @param string $key - * @param mixed $current - * @return bool - */ - public 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, ['object', 'collection'])) { - return $this->castAttribute($key, $current) == - $this->castAttribute($key, $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; - } - - /** - * 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 ($this->isCustomDateTimeCast($value)) { - $attributes[$key] = $attributes[$key]->format(explode(':', $value, 2)[1]); - } - - if ($attributes[$key] instanceof Arrayable) { - $attributes[$key] = $attributes[$key]->toArray(); - } - } - - return $attributes; - } - - /** - * Cast an attribute to a native PHP type. - * - * @param string $key - * @param mixed $value - * @return mixed - */ - protected function castAttribute($key, $value = null) - { - if ($this->isCustomCastable($key)) { - return $this->fromCustomCastable($key, $value); - } elseif (is_null($value)) { - return $value; - } - - switch ($this->getCastType($key)) { - 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->getCast($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 Collection($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) - { - $cast = $this->getCast($key); - - if ($this->isCustomDateTimeCast($cast)) { - return 'custom_datetime'; - } - - if ($this->isDecimalCast($cast)) { - return 'decimal'; - } - - return trim(strtolower($cast)); - } - - /** - * 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; - } - - /** - * 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; - } - - /** - * Determine if the cast type is a decimal cast. - * - * @param string $cast - * @return bool - */ - protected function isDecimalCast($cast) - { - return strncmp($cast, 'decimal:', 8) === 0; - } - - /** - * 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); - } - - /** - * Is the checked value a custom Cast. - * - * @param string $key - * @return bool - */ - protected function isCustomCastable($key) - { - if ($cast = $this->getCast($key)) { - return is_subclass_of($cast, Castable::class); - } - - return false; - } - - /** - * 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']); - } - - /** - * Getting the execution result from a user Cast object. - * - * @param string $key - * @param mixed $value - * @return mixed - */ - protected function fromCustomCastable($key, $value = null) - { - return $this - ->normalizeHandlerToCallable($key) - ->fromDatabase($key, $value); - } - - /** - * Converting a value by custom Cast. - * - * @param string $key - * @param mixed $value - * @return mixed - */ - protected function toCustomCastable($key, $value = null) - { - return $this - ->normalizeHandlerToCallable($key) - ->toDatabase($key, $value); - } - - /** - * Getting a custom cast instance. - * - * @param string $key - * @return \Illuminate\Contracts\Database\Eloquent\Castable - */ - protected function normalizeHandlerToCallable($key) - { - if (! array_key_exists($key, $this->castsInstances)) { - $cast = $this->getCast($key); - - $this->castsInstances[$key] = new $cast; - } - - return $this->castsInstances[$key]; - } -} diff --git a/src/Illuminate/Database/Eloquent/Model.php b/src/Illuminate/Database/Eloquent/Model.php index f1586b33410b..d020fef30653 100644 --- a/src/Illuminate/Database/Eloquent/Model.php +++ b/src/Illuminate/Database/Eloquent/Model.php @@ -20,7 +20,6 @@ abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializable, QueueableEntity, UrlRoutable { use Concerns\HasAttributes, - Concerns\HasCasts, Concerns\HasEvents, Concerns\HasGlobalScopes, Concerns\HasRelationships, From eb397ef0a679393f43d768eb9254d3ea8cab34fd Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Mon, 30 Dec 2019 01:21:21 +0300 Subject: [PATCH 22/30] Small fixed code in the `hasAttributes` trait --- .../Database/Eloquent/Concerns/HasAttributes.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index 49859a61984f..4b766c248669 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -487,7 +487,9 @@ protected function castAttribute($key, $value) { if ($this->isCustomCastable($key)) { return $this->fromCustomCastable($key, $value); - } elseif (is_null($value)) { + } + + if (is_null($value)) { return $value; } @@ -949,9 +951,11 @@ public function hasCast($key, $types = null) */ public function getCasts() { - return $this->getIncrementing() - ? array_merge([$this->getKeyName() => $this->getKeyType()], $this->casts) - : $this->casts; + if ($this->getIncrementing()) { + return array_merge([$this->getKeyName() => $this->getKeyType()], $this->casts); + } + + return $this->casts; } /** From 064353a65f4aaba2318db84d89182f1e71f9f3fa Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Mon, 30 Dec 2019 09:33:53 +0300 Subject: [PATCH 23/30] `array_key_exists` function replaced with faster `isset` --- src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index 4b766c248669..d5c6092c850a 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -1027,7 +1027,7 @@ protected function toCustomCastable($key, $value = null) */ protected function normalizeCastToCallable($key) { - if (! array_key_exists($key, $this->castsInstances)) { + if (! isset($this->castsInstances[$key])) { $cast = $this->getCast($key); $this->castsInstances[$key] = new $cast; From 3e51364116652bc92a5dc5cbed85ffd6b09a25f0 Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Mon, 30 Dec 2019 09:41:53 +0300 Subject: [PATCH 24/30] Replaced class instance creation method --- .../Eloquent/Concerns/HasAttributes.php | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index d5c6092c850a..389325c7c345 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -4,6 +4,7 @@ use Carbon\CarbonInterface; use DateTimeInterface; +use Illuminate\Container\Container; use Illuminate\Contracts\Database\Eloquent\Castable; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Database\Eloquent\JsonEncodingException; @@ -91,6 +92,7 @@ trait HasAttributes * Convert the model's attributes to an array. * * @return array + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function attributesToArray() { @@ -177,6 +179,7 @@ protected function addMutatedAttributesToArray(array $attributes, array $mutated * @param array $attributes * @param array $mutatedAttributes * @return array + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ protected function addCastAttributesToArray(array $attributes, array $mutatedAttributes) { @@ -316,6 +319,7 @@ protected function getArrayableItems(array $values) * * @param string $key * @return mixed + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function getAttribute($key) { @@ -346,6 +350,7 @@ public function getAttribute($key) * * @param string $key * @return mixed + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function getAttributeValue($key) { @@ -482,6 +487,7 @@ protected function mutateAttributeForArray($key, $value) * @param string $key * @param mixed $value * @return mixed + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ protected function castAttribute($key, $value) { @@ -592,6 +598,7 @@ protected function isCustomCastable($key) * @param string $key * @param mixed $value * @return mixed + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function setAttribute($key, $value) { @@ -997,6 +1004,7 @@ protected function isJsonCastable($key) * @param string $key * @param mixed $value * @return mixed + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ protected function fromCustomCastable($key, $value = null) { @@ -1011,6 +1019,7 @@ protected function fromCustomCastable($key, $value = null) * @param string $key * @param mixed $value * @return mixed + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ protected function toCustomCastable($key, $value = null) { @@ -1024,13 +1033,13 @@ protected function toCustomCastable($key, $value = null) * * @param string $key * @return \Illuminate\Contracts\Database\Eloquent\Castable + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ protected function normalizeCastToCallable($key) { if (! isset($this->castsInstances[$key])) { - $cast = $this->getCast($key); - - $this->castsInstances[$key] = new $cast; + $this->castsInstances[$key] = Container::getInstance() + ->make($this->getCast($key)); } return $this->castsInstances[$key]; @@ -1081,6 +1090,7 @@ public function getOriginal($key = null, $default = null) * * @param array|mixed $attributes * @return array + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function only($attributes) { @@ -1137,6 +1147,7 @@ public function syncOriginalAttributes($attributes) * Sync the changed attributes. * * @return $this + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function syncChanges() { @@ -1150,6 +1161,7 @@ public function syncChanges() * * @param array|string|null $attributes * @return bool + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function isDirty($attributes = null) { @@ -1163,6 +1175,7 @@ public function isDirty($attributes = null) * * @param array|string|null $attributes * @return bool + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function isClean($attributes = null) { @@ -1214,6 +1227,7 @@ protected function hasChanges($changes, $attributes = null) * Get the attributes that have been changed since last sync. * * @return array + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function getDirty() { @@ -1244,6 +1258,7 @@ public function getChanges() * @param string $key * @param mixed $current * @return bool + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function originalIsEquivalent($key, $current) { From 493e4d72a688a63c2a9d33921d35a3d0b54ee5f9 Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Mon, 30 Dec 2019 09:48:55 +0300 Subject: [PATCH 25/30] Added method call skipped when canceling the separation of traits --- src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php | 6 ++++++ .../Integration/Database/EloquentModelCustomCastingTest.php | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index 389325c7c345..eab0ab1cfce3 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -616,6 +616,12 @@ public function setAttribute($key, $value) $value = $this->fromDateTime($value); } + // If the attribute is specified as Cast, we will convert it according to + // the method specified in it. + if ($this->isCustomCastable($key)) { + $value = $this->toCustomCastable($key, $value); + } + if ($this->isJsonCastable($key) && ! is_null($value)) { $value = $this->castAttributeAsJson($key, $value); } diff --git a/tests/Integration/Database/EloquentModelCustomCastingTest.php b/tests/Integration/Database/EloquentModelCustomCastingTest.php index 2a1fb453e262..b78549a88553 100644 --- a/tests/Integration/Database/EloquentModelCustomCastingTest.php +++ b/tests/Integration/Database/EloquentModelCustomCastingTest.php @@ -14,7 +14,7 @@ class EloquentModelCustomCastingTest extends DatabaseTestCase public function testValues() { $item = TestModel::create([ - 'field_1' => 'foobar', + 'field_1' => ['f', 'o', 'o', 'b', 'a', 'r'], 'field_2' => 20, 'field_3' => '08:19:12', 'field_4' => null, From 13ceace9af368b2d610d8a8add7950bef4c8901f Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Mon, 30 Dec 2019 09:52:22 +0300 Subject: [PATCH 26/30] The principle of creating a container has been changed See comment: https://github.com/laravel/framework/pull/30958#discussion_r361871611 --- .../Database/Eloquent/Concerns/HasAttributes.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index eab0ab1cfce3..92d64bd7cd00 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -1043,12 +1043,8 @@ protected function toCustomCastable($key, $value = null) */ protected function normalizeCastToCallable($key) { - if (! isset($this->castsInstances[$key])) { - $this->castsInstances[$key] = Container::getInstance() - ->make($this->getCast($key)); - } - - return $this->castsInstances[$key]; + return Container::getInstance() + ->make($this->getCast($key)); } /** From 3446cbe567256e3bba83d2d9441aeb33dd4582e0 Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Mon, 30 Dec 2019 09:54:57 +0300 Subject: [PATCH 27/30] Added a docblock for the `registerCastMakeCommand` method --- .../Foundation/Providers/ArtisanServiceProvider.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php b/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php index 70926b9fc421..3bd3b83b55bb 100755 --- a/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php +++ b/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php @@ -488,6 +488,11 @@ protected function registerModelMakeCommand() }); } + /** + * Register the command. + * + * @return void + */ protected function registerCastMakeCommand() { $this->app->singleton('command.cast.make', function ($app) { From d62cd8d036fe5b5e10be64ab9603601ac0176460 Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Mon, 30 Dec 2019 12:03:41 +0300 Subject: [PATCH 28/30] Storage of custom Cast instances transferred to the global static array of the Model --- .../Eloquent/Concerns/HasAttributes.php | 39 +++++++------------ 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index 92d64bd7cd00..74b992d0537a 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -4,7 +4,6 @@ use Carbon\CarbonInterface; use DateTimeInterface; -use Illuminate\Container\Container; use Illuminate\Contracts\Database\Eloquent\Castable; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Database\Eloquent\JsonEncodingException; @@ -46,13 +45,6 @@ trait HasAttributes */ protected $casts = []; - /** - * Initialized instances of custom casts. - * - * @var \Illuminate\Contracts\Database\Eloquent\Castable[] - */ - protected $castsInstances = []; - /** * The attributes that should be mutated to dates. * @@ -88,11 +80,17 @@ trait HasAttributes */ protected static $mutatorCache = []; + /** + * Initialized instances of custom casts. + * + * @var \Illuminate\Contracts\Database\Eloquent\Castable[] + */ + protected static $castsCache = []; + /** * Convert the model's attributes to an array. * * @return array - * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function attributesToArray() { @@ -179,7 +177,6 @@ protected function addMutatedAttributesToArray(array $attributes, array $mutated * @param array $attributes * @param array $mutatedAttributes * @return array - * @throws \Illuminate\Contracts\Container\BindingResolutionException */ protected function addCastAttributesToArray(array $attributes, array $mutatedAttributes) { @@ -319,7 +316,6 @@ protected function getArrayableItems(array $values) * * @param string $key * @return mixed - * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function getAttribute($key) { @@ -350,7 +346,6 @@ public function getAttribute($key) * * @param string $key * @return mixed - * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function getAttributeValue($key) { @@ -487,7 +482,6 @@ protected function mutateAttributeForArray($key, $value) * @param string $key * @param mixed $value * @return mixed - * @throws \Illuminate\Contracts\Container\BindingResolutionException */ protected function castAttribute($key, $value) { @@ -598,7 +592,6 @@ protected function isCustomCastable($key) * @param string $key * @param mixed $value * @return mixed - * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function setAttribute($key, $value) { @@ -1010,7 +1003,6 @@ protected function isJsonCastable($key) * @param string $key * @param mixed $value * @return mixed - * @throws \Illuminate\Contracts\Container\BindingResolutionException */ protected function fromCustomCastable($key, $value = null) { @@ -1025,7 +1017,6 @@ protected function fromCustomCastable($key, $value = null) * @param string $key * @param mixed $value * @return mixed - * @throws \Illuminate\Contracts\Container\BindingResolutionException */ protected function toCustomCastable($key, $value = null) { @@ -1039,12 +1030,16 @@ protected function toCustomCastable($key, $value = null) * * @param string $key * @return \Illuminate\Contracts\Database\Eloquent\Castable - * @throws \Illuminate\Contracts\Container\BindingResolutionException */ protected function normalizeCastToCallable($key) { - return Container::getInstance() - ->make($this->getCast($key)); + if (! isset(static::$castsCache[$key])) { + $cast = $this->getCast($key); + + static::$castsCache[$key] = new $cast; + } + + return static::$castsCache[$key]; } /** @@ -1092,7 +1087,6 @@ public function getOriginal($key = null, $default = null) * * @param array|mixed $attributes * @return array - * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function only($attributes) { @@ -1149,7 +1143,6 @@ public function syncOriginalAttributes($attributes) * Sync the changed attributes. * * @return $this - * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function syncChanges() { @@ -1163,7 +1156,6 @@ public function syncChanges() * * @param array|string|null $attributes * @return bool - * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function isDirty($attributes = null) { @@ -1177,7 +1169,6 @@ public function isDirty($attributes = null) * * @param array|string|null $attributes * @return bool - * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function isClean($attributes = null) { @@ -1229,7 +1220,6 @@ protected function hasChanges($changes, $attributes = null) * Get the attributes that have been changed since last sync. * * @return array - * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function getDirty() { @@ -1260,7 +1250,6 @@ public function getChanges() * @param string $key * @param mixed $current * @return bool - * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function originalIsEquivalent($key, $current) { From 14720d43829583bbf74bba839f614fd05a8a0fe9 Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Mon, 30 Dec 2019 12:42:21 +0300 Subject: [PATCH 29/30] Updated docblock --- src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index 74b992d0537a..bcd4d43a2239 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -968,7 +968,7 @@ public function getCasts() * Get the cast from casts array. * * @param string $key - * @return mixed + * @return string|null */ public function getCast($key) { From b8adc6824a4ff2c65ed7e5f3c748fe8bead8e46b Mon Sep 17 00:00:00 2001 From: Andrey Helldar Date: Mon, 30 Dec 2019 12:46:33 +0300 Subject: [PATCH 30/30] Replaced `new $cast` by Container make method --- .../Eloquent/Concerns/HasAttributes.php | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index bcd4d43a2239..0525c4c95903 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -4,6 +4,7 @@ use Carbon\CarbonInterface; use DateTimeInterface; +use Illuminate\Container\Container; use Illuminate\Contracts\Database\Eloquent\Castable; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Database\Eloquent\JsonEncodingException; @@ -91,6 +92,7 @@ trait HasAttributes * Convert the model's attributes to an array. * * @return array + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function attributesToArray() { @@ -177,6 +179,7 @@ protected function addMutatedAttributesToArray(array $attributes, array $mutated * @param array $attributes * @param array $mutatedAttributes * @return array + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ protected function addCastAttributesToArray(array $attributes, array $mutatedAttributes) { @@ -316,6 +319,7 @@ protected function getArrayableItems(array $values) * * @param string $key * @return mixed + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function getAttribute($key) { @@ -346,6 +350,7 @@ public function getAttribute($key) * * @param string $key * @return mixed + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function getAttributeValue($key) { @@ -482,6 +487,7 @@ protected function mutateAttributeForArray($key, $value) * @param string $key * @param mixed $value * @return mixed + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ protected function castAttribute($key, $value) { @@ -592,6 +598,7 @@ protected function isCustomCastable($key) * @param string $key * @param mixed $value * @return mixed + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function setAttribute($key, $value) { @@ -1003,6 +1010,7 @@ protected function isJsonCastable($key) * @param string $key * @param mixed $value * @return mixed + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ protected function fromCustomCastable($key, $value = null) { @@ -1017,6 +1025,7 @@ protected function fromCustomCastable($key, $value = null) * @param string $key * @param mixed $value * @return mixed + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ protected function toCustomCastable($key, $value = null) { @@ -1030,13 +1039,13 @@ protected function toCustomCastable($key, $value = null) * * @param string $key * @return \Illuminate\Contracts\Database\Eloquent\Castable + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ protected function normalizeCastToCallable($key) { if (! isset(static::$castsCache[$key])) { - $cast = $this->getCast($key); - - static::$castsCache[$key] = new $cast; + static::$castsCache[$key] = Container::getInstance() + ->make($this->getCast($key)); } return static::$castsCache[$key]; @@ -1087,6 +1096,7 @@ public function getOriginal($key = null, $default = null) * * @param array|mixed $attributes * @return array + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function only($attributes) { @@ -1143,6 +1153,7 @@ public function syncOriginalAttributes($attributes) * Sync the changed attributes. * * @return $this + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function syncChanges() { @@ -1156,6 +1167,7 @@ public function syncChanges() * * @param array|string|null $attributes * @return bool + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function isDirty($attributes = null) { @@ -1169,6 +1181,7 @@ public function isDirty($attributes = null) * * @param array|string|null $attributes * @return bool + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function isClean($attributes = null) { @@ -1220,6 +1233,7 @@ protected function hasChanges($changes, $attributes = null) * Get the attributes that have been changed since last sync. * * @return array + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function getDirty() { @@ -1250,6 +1264,7 @@ public function getChanges() * @param string $key * @param mixed $current * @return bool + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function originalIsEquivalent($key, $current) {