diff --git a/system/Entity.php b/system/Entity.php index 58c30ad069d3..2f4e4994fac7 100644 --- a/system/Entity.php +++ b/system/Entity.php @@ -221,6 +221,14 @@ public function __set(string $key, $value = null) $value = serialize($value); } + // JSON casting requires that we JSONize the value + // when setting it so that it can easily be stored + // back to the database. + if (function_exists('json_encode') && array_key_exists($key, $this->_options['casts']) && ($this->_options['casts'][$key] === 'json' || $this->_options['casts'][$key] === 'json-array')) + { + $value = json_encode($value); + } + // if a set* method exists for this key,
 // use that method to insert this value.
 $method = 'set' . str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $key))); @@ -360,6 +368,7 @@ protected function mutateDate($value) * * @return mixed */ + protected function castAs($value, string $type) { switch($type) @@ -390,6 +399,12 @@ protected function castAs($value, string $type) $value = (array)$value; break; + case 'json': + $value = $this->castAsJson($value, false); + break; + case 'json-array': + $value = $this->castAsJson($value, true); + break; case 'datetime': return new \DateTime($value); break; @@ -400,4 +415,32 @@ protected function castAs($value, string $type) return $value; } + + //-------------------------------------------------------------------- + + /** + * Cast as JSON + * + * @param mixed $value + * @param bool $asArray + * + * @return mixed + */ + private function castAsJson($value, bool $asArray = false) + { + $tmp = !is_null($value) ? ($asArray ? [] : new \stdClass) : null; + if(function_exists('json_decode')) + { + if((is_string($value) && (strpos($value, '[') === 0 || strpos($value, '{') === 0 || (strpos($value, '"') === 0 && strrpos($value, '"') === 0 ))) || is_numeric($value)) + { + $tmp = json_decode($value, $asArray); + + if(json_last_error() !== JSON_ERROR_NONE) + { + throw CastException::forInvalidJsonFormatException(json_last_error()); + } + } + } + return $tmp; + } } diff --git a/system/Exceptions/CastException.php b/system/Exceptions/CastException.php new file mode 100644 index 000000000000..b4cbbbbd55ec --- /dev/null +++ b/system/Exceptions/CastException.php @@ -0,0 +1,40 @@ + 'Maximum stack depth exceeded', + 'jsonErrorStateMismatch' => 'Underflow or the modes mismatch', + 'jsonErrorCtrlChar' => 'Unexpected control character found', + 'jsonErrorSyntax' => 'Syntax error, malformed JSON', + 'jsonErrorUtf8' => 'Malformed UTF-8 characters, possibly incorrectly encoded', + 'jsonErrorUnknown' => 'Unknown error' +]; diff --git a/user_guide_src/source/models/entities.rst b/user_guide_src/source/models/entities.rst index 01487bbb6eb1..d2fb742960c2 100644 --- a/user_guide_src/source/models/entities.rst +++ b/user_guide_src/source/models/entities.rst @@ -338,12 +338,22 @@ For example, if you had a User entity with an **is_banned** property, you can ca ]; } -Array Casting +Array/Json Casting ------------- -Array casting is especially useful with fields that store serialized arrays or json in them. When cast as an array, -they will automatically be unserialized when you read the property's value. Unlike the rest of the data types that -you can cast properties into, the **array** cast type will serialize the value whenever the property is set:: +Array/Json casting is especially useful with fields that store serialized arrays or json in them. When cast as: + +* an **array**, they will automatically be unserialized, +* a **json**, they will automatically be set as an value of json_decode($value, false), +* a **json-array**, they will automatically be set as an value of json_decode($value, true), + +when you read the property's value. +Unlike the rest of the data types that you can cast properties into, the: + +* **array** cast type will serialize, +* **json** and **json-array** cast will use json_encode function on + +the value whenever the property is set:: [ - 'options' => 'array' + 'options' => 'array', + 'options_object' => 'json', + 'options_array' => 'json-array' ], 'dates' => ['created_at', 'updated_at', 'deleted_at'], 'datamap' => []