From 240569ff6e955d86363b9d9e2cdbb3491cc143df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Till=20Kru=CC=88ss?= Date: Fri, 24 Feb 2017 10:19:09 -0800 Subject: [PATCH 01/14] Port object casting (#13706) to master --- .../Eloquent/Concerns/HasAttributes.php | 131 ++++++++++++++++-- src/Illuminate/Database/Eloquent/Model.php | 16 +++ 2 files changed, 136 insertions(+), 11 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index ba436760fc22..bffa5a2c85c2 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -42,6 +42,13 @@ trait HasAttributes */ protected $casts = []; + /** + * The attributes that have been cast to classes. + * + * @var array + */ + protected $classCastCache = []; + /** * The attributes that should be mutated to dates. * @@ -77,6 +84,16 @@ trait HasAttributes */ protected static $mutatorCache = []; + /** + * All of the valid primitive cast types. + * + * @var array + */ + protected static $primitiveCastTypes = [ + 'int', 'integer', 'real', 'float', 'double', 'string', 'bool', 'boolean', + 'object', 'array', 'json', 'collection', 'date', 'datetime', 'timestamp', + ]; + /** * Convert the model's attributes to an array. * @@ -175,6 +192,10 @@ protected function addCastAttributesToArray(array $attributes, array $mutatedAtt continue; } + if ($this->isClassCastable($key)) { + 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. @@ -201,7 +222,7 @@ protected function addCastAttributesToArray(array $attributes, array $mutatedAtt */ protected function getArrayableAttributes() { - return $this->getArrayableItems($this->attributes); + return $this->getArrayableItems($this->getAttributes()); } /** @@ -308,8 +329,7 @@ public function getAttribute($key) // If the attribute exists in the attribute array or has a "get" mutator we will // get the attribute's value. Otherwise, we will proceed as if the developers // are asking for a relationship's value. This covers both types of values. - if (array_key_exists($key, $this->attributes) || - $this->hasGetMutator($key)) { + if (array_key_exists($key, $this->attributes) || $this->hasGetMutator($key) || $this->isClassCastable($key)) { return $this->getAttributeValue($key); } @@ -366,8 +386,10 @@ public function getAttributeValue($key) */ protected function getAttributeFromArray($key) { - if (isset($this->attributes[$key])) { - return $this->attributes[$key]; + $attributes = $this->getAttributes(); + + if (isset($attributes[$key])) { + return $attributes[$key]; } } @@ -447,7 +469,11 @@ protected function mutateAttribute($key, $value) */ protected function mutateAttributeForArray($key, $value) { - $value = $this->mutateAttribute($key, $value); + if ($this->isClassCastable($key)) { + $value = $this->castToClass($key); + } else { + $value = $this->mutateAttribute($key, $value); + } return $value instanceof Arrayable ? $value->toArray() : $value; } @@ -461,11 +487,13 @@ protected function mutateAttributeForArray($key, $value) */ protected function castAttribute($key, $value) { - if (is_null($value)) { + $castType = $this->getCastType($key); + + if (is_null($value) && in_array($castType, static::$primitiveCastTypes)) { return $value; } - switch ($this->getCastType($key)) { + switch ($castType) { case 'int': case 'integer': return (int) $value; @@ -491,8 +519,60 @@ protected function castAttribute($key, $value) return $this->asDateTime($value); case 'timestamp': return $this->asTimestamp($value); - default: - return $value; + } + + if ($this->isClassCastable($key)) { + return $this->castToClass($key); + } + + return $value; + } + + /** + * Cast the given attribute to a class. + * + * @param string $key + * @return mixed + */ + protected function castToClass($key) + { + if (isset($this->classCastCache[$key])) { + return $this->classCastCache[$key]; + } else { + return $this->classCastCache[$key] = forward_static_call( + [$this->getCasts()[$key], 'fromModelAttributes'], $this, $this->attributes + ); + } + } + + /** + * Determine whether a value is JSON castable for inbound manipulation. + * + * @param string $key + * @return bool + */ + protected function isClassCastable($key) + { + if (! array_key_exists($key, $this->getCasts())) { + return false; + } + + $class = $this->getCasts()[$key]; + + return class_exists($class) && ! in_array($class, static::$primitiveCastTypes); + } + + /** + * Merge the cast class attributes back into the model. + * + * @return void + */ + protected function mergeAttributesFromClassCasts() + { + foreach ($this->classCastCache as $key => $value) { + $this->attributes = array_merge( + $this->attributes, $value->toModelAttributes($this, $this->attributes) + ); } } @@ -543,11 +623,38 @@ public function setAttribute($key, $value) return $this->fillJsonAttribute($key, $value); } - $this->attributes[$key] = $value; + if ($this->isClassCastable($key)) { + $this->setClassCastableAttribute($key, $value); + } else { + $this->attributes[$key] = $value; + } return $this; } + /** + * Set the value of a class castable attribute. + * + * @param string $key + * @param mixed $value + * @return void + */ + protected function setClassCastableAttribute($key, $value) + { + if (is_null($value)) { + $this->attributes = array_merge($this->attributes, array_map( + function () { + return null; + }, + $this->castToClass($key)->toModelAttributes($this) + )); + + unset($this->classCastCache[$key]); + } else { + $this->classCastCache[$key] = $value; + } + } + /** * Determine if a set mutator exists for an attribute. * @@ -856,6 +963,8 @@ protected function isJsonCastable($key) */ public function getAttributes() { + $this->mergeAttributesFromClassCasts(); + return $this->attributes; } diff --git a/src/Illuminate/Database/Eloquent/Model.php b/src/Illuminate/Database/Eloquent/Model.php index e4097287722c..15cf87614e27 100644 --- a/src/Illuminate/Database/Eloquent/Model.php +++ b/src/Illuminate/Database/Eloquent/Model.php @@ -497,6 +497,8 @@ public function push() */ public function save(array $options = []) { + $this->mergeAttributesFromClassCasts(); + $query = $this->newQueryWithoutScopes(); // If the "saving" event returns false we'll bail out of the save and return @@ -728,6 +730,8 @@ public static function destroy($ids) */ public function delete() { + $this->mergeAttributesFromClassCasts(); + if (is_null($this->getKeyName())) { throw new Exception('No primary key defined on model.'); } @@ -1394,6 +1398,18 @@ public function __toString() return $this->toJson(); } + /** + * Prepare the object for serialization. + * + * @return array + */ + public function __sleep() + { + $this->mergeAttributesFromClassCasts(); + + return array_keys(get_object_vars($this)); + } + /** * When a model is being unserialized, check if it needs to be booted. * From 8803c7b8959d81e1b663948b1aecbef99dc8660e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Till=20Kru=CC=88ss?= Date: Fri, 24 Feb 2017 13:10:43 -0800 Subject: [PATCH 02/14] Pass attribute key to object for reusability --- .../Database/Eloquent/Concerns/HasAttributes.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index bffa5a2c85c2..2762d2909fe5 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -540,7 +540,7 @@ protected function castToClass($key) return $this->classCastCache[$key]; } else { return $this->classCastCache[$key] = forward_static_call( - [$this->getCasts()[$key], 'fromModelAttributes'], $this, $this->attributes + [$this->getCasts()[$key], 'fromModelAttributes'], $this->attributes, $key, $this ); } } @@ -571,7 +571,8 @@ protected function mergeAttributesFromClassCasts() { foreach ($this->classCastCache as $key => $value) { $this->attributes = array_merge( - $this->attributes, $value->toModelAttributes($this, $this->attributes) + $this->attributes, + $value->toModelAttributes($this->attributes, $key, $this) ); } } @@ -646,7 +647,7 @@ protected function setClassCastableAttribute($key, $value) function () { return null; }, - $this->castToClass($key)->toModelAttributes($this) + $this->castToClass($key)->toModelAttributes($this->attributes, $key, $this) )); unset($this->classCastCache[$key]); From 4e2430581a3d0c74a4327f6df05aa86a2f60680e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Till=20Kru=CC=88ss?= Date: Sun, 26 Feb 2017 13:07:43 -0800 Subject: [PATCH 03/14] =?UTF-8?q?Don=E2=80=99t=20bypass=20null=20values?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows the object to decide how to handle `null` values. --- .../Database/Eloquent/Concerns/HasAttributes.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index 2762d2909fe5..4848a1c426f8 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -489,7 +489,7 @@ protected function castAttribute($key, $value) { $castType = $this->getCastType($key); - if (is_null($value) && in_array($castType, static::$primitiveCastTypes)) { + if (is_null($value)) { return $value; } @@ -569,11 +569,14 @@ protected function isClassCastable($key) */ protected function mergeAttributesFromClassCasts() { - foreach ($this->classCastCache as $key => $value) { - $this->attributes = array_merge( - $this->attributes, - $value->toModelAttributes($this->attributes, $key, $this) - ); + foreach ($this->getCasts() as $attribute => $cast) { + if ($this->isClassCastable($attribute)) { + $this->attributes = array_merge( + $this->attributes, + $this->castToClass($attribute) + ->toModelAttributes($this->attributes, $attribute, $this) + ); + } } } From 4a2a7bee4ee31963fcf58c87f9e66aac57cd415c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Till=20Kru=CC=88ss?= Date: Thu, 2 Mar 2017 10:52:33 -0800 Subject: [PATCH 04/14] Refactor object casting --- .../Eloquent/Concerns/HasAttributes.php | 95 ++++++++++++------- 1 file changed, 61 insertions(+), 34 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index 4848a1c426f8..683bdde46a4d 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -84,16 +84,6 @@ trait HasAttributes */ protected static $mutatorCache = []; - /** - * All of the valid primitive cast types. - * - * @var array - */ - protected static $primitiveCastTypes = [ - 'int', 'integer', 'real', 'float', 'double', 'string', 'bool', 'boolean', - 'object', 'array', 'json', 'collection', 'date', 'datetime', 'timestamp', - ]; - /** * Convert the model's attributes to an array. * @@ -487,13 +477,15 @@ protected function mutateAttributeForArray($key, $value) */ protected function castAttribute($key, $value) { - $castType = $this->getCastType($key); - if (is_null($value)) { return $value; } - switch ($castType) { + if ($this->isClassCastable($key)) { + return $this->castToClass($key); + } + + switch ($this->getCastType($key)) { case 'int': case 'integer': return (int) $value; @@ -521,10 +513,6 @@ protected function castAttribute($key, $value) return $this->asTimestamp($value); } - if ($this->isClassCastable($key)) { - return $this->castToClass($key); - } - return $value; } @@ -538,28 +526,42 @@ protected function castToClass($key) { if (isset($this->classCastCache[$key])) { return $this->classCastCache[$key]; + } + + [$cast, $attributes] = $this->getClassCast($key); + + if ($attributes) { + $parameters = array_map(function ($key) { + return $this->attributes[$key] ?? null; + }, $attributes); } else { - return $this->classCastCache[$key] = forward_static_call( - [$this->getCasts()[$key], 'fromModelAttributes'], $this->attributes, $key, $this - ); + $parameters = [$this->attributes[$key] ?? null]; } + + return $this->classCastCache[$key] = forward_static_call( + [$this, 'castTo'.Str::studly($cast)], ...$parameters + ); } /** - * Determine whether a value is JSON castable for inbound manipulation. + * Determine whether a value is class castable. * * @param string $key * @return bool */ protected function isClassCastable($key) { - if (! array_key_exists($key, $this->getCasts())) { + $casts = array_map(function ($cast) { + return strpos($cast, ':') === false ? $cast : strstr($cast, ':', true); + }, $this->getCasts()); + + if (! array_key_exists($key, $casts)) { return false; } - $class = $this->getCasts()[$key]; + $cast = Str::studly($casts[$key]); - return class_exists($class) && ! in_array($class, static::$primitiveCastTypes); + return method_exists($this, "castTo{$cast}") && method_exists($this, "castFrom{$cast}"); } /** @@ -571,11 +573,16 @@ protected function mergeAttributesFromClassCasts() { foreach ($this->getCasts() as $attribute => $cast) { if ($this->isClassCastable($attribute)) { - $this->attributes = array_merge( - $this->attributes, + [$cast, $attributes] = $this->getClassCast($attribute); + + $casted = array_filter((array) call_user_func( + [$this, 'castFrom'. Str::studly($cast)], $this->castToClass($attribute) - ->toModelAttributes($this->attributes, $attribute, $this) - ); + )); + + if (count($casted) === count($attributes)) { + $this->attributes = array_merge($this->attributes, array_combine($attributes, $casted)); + } } } } @@ -646,12 +653,12 @@ public function setAttribute($key, $value) protected function setClassCastableAttribute($key, $value) { if (is_null($value)) { - $this->attributes = array_merge($this->attributes, array_map( - function () { - return null; - }, - $this->castToClass($key)->toModelAttributes($this->attributes, $key, $this) - )); + [$cast, $attributes] = $this->getClassCast($key); + + $this->attributes = array_merge( + $this->attributes, + array_fill_keys(array_keys(array_flip((array) $attributes)), null) + ); unset($this->classCastCache[$key]); } else { @@ -938,6 +945,26 @@ public function getCasts() return $this->casts; } + /** + * Get the casted class and attributes for given attribute. + * + * @param string $attribute + * @return array + */ + protected function getClassCast($attribute) + { + $cast = $this->getCasts()[$attribute]; + + if (strpos($cast, ':') === false) { + return [$cast, [$attribute]]; + } + + return [ + strstr($cast, ':', true), + explode(',', ltrim(strstr($cast, ':'), ':')) + ]; + } + /** * Determine whether a value is Date / DateTime castable for inbound manipulation. * From 4fc1e10d90b33a804a4fa9765a0c4998dc626c3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Till=20Kru=CC=88ss?= Date: Sun, 5 Mar 2017 18:56:03 -0800 Subject: [PATCH 05/14] =?UTF-8?q?Don=E2=80=99t=20re-assign=20$cast?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index 683bdde46a4d..e2180454966e 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -528,7 +528,7 @@ protected function castToClass($key) return $this->classCastCache[$key]; } - [$cast, $attributes] = $this->getClassCast($key); + [$type, $attributes] = $this->getClassCast($key); if ($attributes) { $parameters = array_map(function ($key) { @@ -573,10 +573,10 @@ protected function mergeAttributesFromClassCasts() { foreach ($this->getCasts() as $attribute => $cast) { if ($this->isClassCastable($attribute)) { - [$cast, $attributes] = $this->getClassCast($attribute); + [$type, $attributes] = $this->getClassCast($attribute); $casted = array_filter((array) call_user_func( - [$this, 'castFrom'. Str::studly($cast)], + [$this, 'castFrom'. Str::studly($type)], $this->castToClass($attribute) )); From 5b60a441f46c737091b1249d270cf9d20bd0ea21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Till=20Kru=CC=88ss?= Date: Sun, 5 Mar 2017 18:56:20 -0800 Subject: [PATCH 06/14] =?UTF-8?q?Don=E2=80=99t=20use=20forward=5Fstatic=5F?= =?UTF-8?q?call()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index e2180454966e..38fb68bf2446 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -538,9 +538,7 @@ protected function castToClass($key) $parameters = [$this->attributes[$key] ?? null]; } - return $this->classCastCache[$key] = forward_static_call( - [$this, 'castTo'.Str::studly($cast)], ...$parameters - ); + return $this->classCastCache[$key] = $this->{'castTo'.Str::studly($type)}(...$parameters); } /** From 5f4c28bb189426f123b77df6cfd67209d01aa897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Till=20Kru=CC=88ss?= Date: Sun, 5 Mar 2017 18:59:01 -0800 Subject: [PATCH 07/14] Remove stupid array stuff --- 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 38fb68bf2446..945eca9b54f1 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -655,7 +655,7 @@ protected function setClassCastableAttribute($key, $value) $this->attributes = array_merge( $this->attributes, - array_fill_keys(array_keys(array_flip((array) $attributes)), null) + array_fill_keys((array) $attributes, null) ); unset($this->classCastCache[$key]); From 9ff0f0a226077abe5dfd46e49cfcc089a7003a1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Till=20Kru=CC=88ss?= Date: Sun, 5 Mar 2017 19:15:28 -0800 Subject: [PATCH 08/14] Strip class cast attributes in hasCast() --- src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index 945eca9b54f1..c4eb5c699c5d 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -923,7 +923,10 @@ public function setDateFormat($format) public function hasCast($key, $types = null) { if (array_key_exists($key, $this->getCasts())) { - return $types ? in_array($this->getCastType($key), (array) $types, true) : true; + $cast = $this->getCastType($key); + $cast = strstr($cast, ':', true) ?: $cast; + + return $types ? in_array($cast, (array) $types, true) : true; } return false; From 72dad61d3eab0a40175930711bef2fa5a14efaa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Till=20Kru=CC=88ss?= Date: Sun, 5 Mar 2017 20:31:22 -0800 Subject: [PATCH 09/14] =?UTF-8?q?Don=E2=80=99t=20require=20castFromFoo();?= =?UTF-8?q?=20Throw=20LogicException?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Eloquent/Concerns/HasAttributes.php | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index c4eb5c699c5d..b47f22d79584 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -557,9 +557,7 @@ protected function isClassCastable($key) return false; } - $cast = Str::studly($casts[$key]); - - return method_exists($this, "castTo{$cast}") && method_exists($this, "castFrom{$cast}"); + return method_exists($this, 'castTo'.Str::studly($casts[$key])); } /** @@ -573,14 +571,20 @@ protected function mergeAttributesFromClassCasts() if ($this->isClassCastable($attribute)) { [$type, $attributes] = $this->getClassCast($attribute); - $casted = array_filter((array) call_user_func( - [$this, 'castFrom'. Str::studly($type)], - $this->castToClass($attribute) - )); + $attributeCount = count($attributes); + $object = $this->castToClass($attribute); + + $castedAttributes = method_exists($object, '__toString') + ? (string) $object + : $this->{'castFrom'.Str::studly($type)}($object); - if (count($casted) === count($attributes)) { - $this->attributes = array_merge($this->attributes, array_combine($attributes, $casted)); + if ($attributeCount !== count($castedAttributes)) { + throw new LogicException("Class cast {$attribute} must return {$attributeCount} attributes"); } + + $this->attributes = array_merge( + $this->attributes, array_combine($attributes, (array) $castedAttributes) + ); } } } From 765ff344ce1746f091da8bf31a0ab6d073721a05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Till=20Kru=CC=88ss?= Date: Mon, 6 Mar 2017 11:20:53 -0800 Subject: [PATCH 10/14] StyleCI fixes --- 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 b47f22d79584..e2044af0b06e 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -966,7 +966,7 @@ protected function getClassCast($attribute) return [ strstr($cast, ':', true), - explode(',', ltrim(strstr($cast, ':'), ':')) + explode(',', ltrim(strstr($cast, ':'), ':')), ]; } From 2e4117d4c83cc81fc8daa557b0f494a117c329db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Till=20Kru=CC=88ss?= Date: Mon, 6 Mar 2017 11:25:20 -0800 Subject: [PATCH 11/14] =?UTF-8?q?Don=E2=80=99t=20require=20PHP=207.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index e2044af0b06e..a94a21d37bb2 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -528,7 +528,7 @@ protected function castToClass($key) return $this->classCastCache[$key]; } - [$type, $attributes] = $this->getClassCast($key); + list($type, $attributes) = $this->getClassCast($key); if ($attributes) { $parameters = array_map(function ($key) { @@ -569,7 +569,7 @@ protected function mergeAttributesFromClassCasts() { foreach ($this->getCasts() as $attribute => $cast) { if ($this->isClassCastable($attribute)) { - [$type, $attributes] = $this->getClassCast($attribute); + list($type, $attributes) = $this->getClassCast($attribute); $attributeCount = count($attributes); $object = $this->castToClass($attribute); @@ -655,7 +655,7 @@ public function setAttribute($key, $value) protected function setClassCastableAttribute($key, $value) { if (is_null($value)) { - [$cast, $attributes] = $this->getClassCast($key); + list($cast, $attributes) = $this->getClassCast($key); $this->attributes = array_merge( $this->attributes, From 24c84acfe6910a08a647fc9085825eb3705ebc92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Till=20Kru=CC=88ss?= Date: Thu, 9 Mar 2017 07:26:18 -0800 Subject: [PATCH 12/14] Prefer castFrom* methods over __toString --- src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index a94a21d37bb2..d54da71eed8c 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -574,9 +574,9 @@ protected function mergeAttributesFromClassCasts() $attributeCount = count($attributes); $object = $this->castToClass($attribute); - $castedAttributes = method_exists($object, '__toString') - ? (string) $object - : $this->{'castFrom'.Str::studly($type)}($object); + $castedAttributes = method_exists($this, 'castFrom'.Str::studly($type)) + ? $this->{'castFrom'.Str::studly($type)}($object) + : $object->__toString(); if ($attributeCount !== count($castedAttributes)) { throw new LogicException("Class cast {$attribute} must return {$attributeCount} attributes"); From 58cbd8189172fbbd5bfc6c07fb9b9b85a736e90e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Till=20Kr=C3=BCss?= Date: Tue, 28 Mar 2017 08:53:24 -0700 Subject: [PATCH 13/14] Adjust exception message --- 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 d54da71eed8c..ec0c834cf9d4 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -579,7 +579,7 @@ protected function mergeAttributesFromClassCasts() : $object->__toString(); if ($attributeCount !== count($castedAttributes)) { - throw new LogicException("Class cast {$attribute} must return {$attributeCount} attributes"); + throw new LogicException("Class cast {$attribute} must return {$attributeCount} attribute(s)"); } $this->attributes = array_merge( From 22b4fe83d44f6d18261c42f02ed0e87f3c9c042c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Till=20Kru=CC=88ss?= Date: Wed, 19 Jul 2017 08:37:01 -0700 Subject: [PATCH 14/14] restore tests --- .../DatabaseEloquentIntegrationTest.php | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/tests/Database/DatabaseEloquentIntegrationTest.php b/tests/Database/DatabaseEloquentIntegrationTest.php index cecf0e14943e..f873fe16d0e6 100644 --- a/tests/Database/DatabaseEloquentIntegrationTest.php +++ b/tests/Database/DatabaseEloquentIntegrationTest.php @@ -1135,6 +1135,49 @@ public function testFreshMethodOnCollection() $this->assertEquals($users->map->fresh(), $users->fresh()); } + public function testAttributesMayBeCastToObjects() + { + $model = new EloquentTestObjectCast(); + + $model->token = new EloquentTestBase64Object('Taylor'); + $this->assertInstanceOf(EloquentTestBase64Object::class, $model->token); + $this->assertEquals('Taylor', $model->token->secret); + $this->assertEquals('VGF5bG9y', $model->toArray()['token']); + $this->assertTrue($model->hasCast('token', 'base64')); + + $model->price = (new EloquentTestMoneyObject(10))->setCurrency('BTC'); + $this->assertInstanceOf(EloquentTestMoneyObject::class, $model->price); + $this->assertEquals(10, $model->toArray()['price']); + $this->assertEquals('BTC', $model->toArray()['currency']); + $this->assertTrue($model->hasCast('price', 'money')); + + $model->price->amount = 20; + $this->assertEquals(20, $model->toArray()['price']); + + $model->price = (new EloquentTestMoneyObject(30)); + $this->assertEquals(30, $model->toArray()['price']); + $this->assertNull($model->toArray()['currency']); + + $model->price = null; + $this->assertNull($model->price); + $this->assertNull($model->toArray()['price']); + $this->assertNull($model->toArray()['currency']); + + $model->price = (new EloquentTestMoneyObject(10))->setCurrency('BTC'); + $model->forceFill(['price' => null]); + $this->assertNull($model->price); + $this->assertNull($model->toArray()['price']); + $this->assertNull($model->toArray()['currency']); + + $model->price = (new EloquentTestMoneyObject(10))->setCurrency('BTC'); + $model->price->setCurrency('XBT'); + $model = unserialize(serialize($model)); + $this->assertEquals('XBT', $model->currency); + + $model->meta = new EloquentTestStringifyableObject(['friends' => ['Adam']]); + $this->assertEquals('{"friends":["Adam"]}', $model->toArray()['meta']); + } + /** * Helpers... */ @@ -1365,3 +1408,97 @@ public function level() return $this->belongsTo(EloquentTestFriendLevel::class, 'friend_level_id'); } } + +class EloquentTestBase64Object +{ + public $secret; + + public function __construct($secret) + { + $this->secret = empty($secret) ? null : $secret; + } + + public static function fromHash($secret) + { + return new static(empty($secret) ? null : base64_decode($secret)); + } + + public function hash() + { + return empty($this->secret) ? null : base64_encode($this->secret); + } +} + +class EloquentTestStringifyableObject +{ + public $json; + + public function __construct($json) + { + $this->json = $json; + } + + public static function fromString($string) + { + return new static(json_encode($string)); + } + + public function __toString() + { + return json_encode($this->json); + } +} + +class EloquentTestMoneyObject +{ + public $amount; + public $currency; + + public function __construct($amount) + { + $this->amount = $amount; + } + + public function setCurrency($currency) + { + $this->currency = $currency; + + return $this; + } +} + +class EloquentTestObjectCast extends Eloquent +{ + protected $casts = [ + 'meta' => 'stringifyable', + 'token' => 'base64', + 'price' => 'money:price,currency', + ]; + + public function castToStringifyable($value) + { + return EloquentTestStringifyableObject::fromString($value); + } + + public function castToBase64($value) + { + return EloquentTestBase64Object::fromHash($value); + } + + public function castFromBase64($secret) + { + return $secret->hash(); + } + + public function castToMoney($amount, $currency) + { + return (new EloquentTestMoneyObject($amount))->setCurrency($currency); + } + + public function castFromMoney($money) + { + if ($money) { + return [$money->amount, $money->currency]; + } + } +}