From 5451c1aab66fe4130e8a6d691be414e076b589d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Nowacki?= Date: Thu, 30 Aug 2018 12:50:06 +0200 Subject: [PATCH 01/13] Small but useful. Entity class. #1176 I've changed name which i proposed before (json-object -> json) cuz I use default parameter for json_decode($value, [$assoc = false]) in calling. --- system/Entity.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/system/Entity.php b/system/Entity.php index 58c30ad069d3..392cff675bb5 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))); @@ -388,6 +396,22 @@ protected function castAs($value, string $type) $value = unserialize($value); } + $value = (array)$value; + break; + case 'json': + if (function_exists('json_decode') && is_string($value) && (strpos($value, '[') === 0 || strpos($value, '{') === 0)) + { + $value = json_decode($value, false); + } + + $value = (object)$value; + break; + case 'json-array': + if (function_exists('json_decode') && is_string($value) && (strpos($value, '[') === 0 || strpos($value, '{') === 0)) + { + $value = json_decode($value, true); + } + $value = (array)$value; break; case 'datetime': From 31843fde6b5f3184bc814e986d7f83b47befe7e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Nowacki?= Date: Sun, 2 Sep 2018 11:07:02 +0200 Subject: [PATCH 02/13] entity cast json / json-array docs --- user_guide_src/source/database/entities.rst | 22 ++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/user_guide_src/source/database/entities.rst b/user_guide_src/source/database/entities.rst index 79b4f493fa97..5a1ad7f692b7 100755 --- a/user_guide_src/source/database/entities.rst +++ b/user_guide_src/source/database/entities.rst @@ -337,12 +337,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' => [] From e3a52a870c6061c1af9dd3f4bcd98b84e9af1d7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Nowacki?= Date: Tue, 11 Sep 2018 08:58:23 +0200 Subject: [PATCH 03/13] Adding support for system/Exceptions/CastException --- system/Entity.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/system/Entity.php b/system/Entity.php index 392cff675bb5..a290d0fed099 100644 --- a/system/Entity.php +++ b/system/Entity.php @@ -402,16 +402,24 @@ protected function castAs($value, string $type) if (function_exists('json_decode') && is_string($value) && (strpos($value, '[') === 0 || strpos($value, '{') === 0)) { $value = json_decode($value, false); + $json_last_error = json_last_error(); + if($json_last_error !== JSON_ERROR_NONE) + { + throw CastException::forInvalidJsonFormatException($json_last_error); + } } - $value = (object)$value; break; case 'json-array': if (function_exists('json_decode') && is_string($value) && (strpos($value, '[') === 0 || strpos($value, '{') === 0)) { $value = json_decode($value, true); + $json_last_error = json_last_error(); + if($json_last_error !== JSON_ERROR_NONE) + { + throw CastException::forInvalidJsonFormatException($json_last_error); + } } - $value = (array)$value; break; case 'datetime': From fc93a03e6b69f267129459c4cf7380b4cb02d259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Nowacki?= Date: Tue, 11 Sep 2018 09:15:14 +0200 Subject: [PATCH 04/13] language file (Cast) for system/Exception/CastException.php --- system/Language/en/Cast.php | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 system/Language/en/Cast.php diff --git a/system/Language/en/Cast.php b/system/Language/en/Cast.php new file mode 100644 index 000000000000..6debec80c97a --- /dev/null +++ b/system/Language/en/Cast.php @@ -0,0 +1,9 @@ + 'Maximum stack depth exceeded', + 'Cast.jsonErrorStateMismatch' => 'Underflow or the modes mismatch', + 'Cast.jsonErrorCtrlChar' => 'Unexpected control character found', + 'Cast.jsonErrorSyntax' => 'Syntax error, malformed JSON', + 'Cast.jsonErrorUtf8' => 'Malformed UTF-8 characters, possibly incorrectly encoded', + 'Cast.jsonErrorUnknown' => 'Unknown error' +]; From 5fcd6630e7171fcf4c509ab36b49211b07f4f9b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Nowacki?= Date: Tue, 11 Sep 2018 09:16:26 +0200 Subject: [PATCH 05/13] Update Cast.php --- system/Language/en/Cast.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/system/Language/en/Cast.php b/system/Language/en/Cast.php index 6debec80c97a..acb3d4e9e681 100644 --- a/system/Language/en/Cast.php +++ b/system/Language/en/Cast.php @@ -1,4 +1,17 @@ 'Maximum stack depth exceeded', 'Cast.jsonErrorStateMismatch' => 'Underflow or the modes mismatch', From 8496c6bb354cc1e90b5e06d6450188a47dc0a91a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Nowacki?= Date: Tue, 11 Sep 2018 09:17:07 +0200 Subject: [PATCH 06/13] Update Cast.php --- system/Language/en/Cast.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/system/Language/en/Cast.php b/system/Language/en/Cast.php index acb3d4e9e681..722eee491333 100644 --- a/system/Language/en/Cast.php +++ b/system/Language/en/Cast.php @@ -12,11 +12,12 @@ * * @codeCoverageIgnore */ + 'Maximum stack depth exceeded', - 'Cast.jsonErrorStateMismatch' => 'Underflow or the modes mismatch', - 'Cast.jsonErrorCtrlChar' => 'Unexpected control character found', - 'Cast.jsonErrorSyntax' => 'Syntax error, malformed JSON', - 'Cast.jsonErrorUtf8' => 'Malformed UTF-8 characters, possibly incorrectly encoded', - 'Cast.jsonErrorUnknown' => 'Unknown error' + 'Cast.jsonErrorDepth' => 'Maximum stack depth exceeded', + 'Cast.jsonErrorStateMismatch' => 'Underflow or the modes mismatch', + 'Cast.jsonErrorCtrlChar' => 'Unexpected control character found', + 'Cast.jsonErrorSyntax' => 'Syntax error, malformed JSON', + 'Cast.jsonErrorUtf8' => 'Malformed UTF-8 characters, possibly incorrectly encoded', + 'Cast.jsonErrorUnknown' => 'Unknown error' ]; From 7fd144f55e11f9d6d93a2811e5a3b4ec2144aed4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Nowacki?= Date: Tue, 11 Sep 2018 09:17:19 +0200 Subject: [PATCH 07/13] Update Cast.php --- system/Language/en/Cast.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Language/en/Cast.php b/system/Language/en/Cast.php index 722eee491333..a260bf9b6904 100644 --- a/system/Language/en/Cast.php +++ b/system/Language/en/Cast.php @@ -12,7 +12,7 @@ * * @codeCoverageIgnore */ - 'Maximum stack depth exceeded', 'Cast.jsonErrorStateMismatch' => 'Underflow or the modes mismatch', From 90ba7bb794ea5acc15d53a798fe2cd09b66efd49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Nowacki?= Date: Tue, 11 Sep 2018 09:23:33 +0200 Subject: [PATCH 08/13] Cast exceptions initially used in system/Entity.php --- system/Exceptions/CastException.php | 44 +++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 system/Exceptions/CastException.php diff --git a/system/Exceptions/CastException.php b/system/Exceptions/CastException.php new file mode 100644 index 000000000000..92980da31996 --- /dev/null +++ b/system/Exceptions/CastException.php @@ -0,0 +1,44 @@ + Date: Tue, 11 Sep 2018 09:24:13 +0200 Subject: [PATCH 09/13] Update CastException.php --- system/Exceptions/CastException.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/system/Exceptions/CastException.php b/system/Exceptions/CastException.php index 92980da31996..a6e763d5937d 100644 --- a/system/Exceptions/CastException.php +++ b/system/Exceptions/CastException.php @@ -18,19 +18,19 @@ public static function forInvalidJsonFormatException(int $error) { throw new static(lang('Cast.jsonErrorDepth')); } - else if($error == JSON_ERROR_STATE_MISMATCH) + else if($error === JSON_ERROR_STATE_MISMATCH) { throw new static(lang('Cast.jsonErrorStateMismatch')); } - else if($error == JSON_ERROR_CTRL_CHAR) + else if($error === JSON_ERROR_CTRL_CHAR) { throw new static(lang('Cast.jsonErrorCtrlChar')); } - else if($error == JSON_ERROR_SYNTAX) + else if($error === JSON_ERROR_SYNTAX) { throw new static(lang('Cast.jsonErrorSyntax')); } - else if($error == JSON_ERROR_UTF8) + else if($error === JSON_ERROR_UTF8) { throw new static(lang('Cast.jsonErrorUtf8')); } From ad7c74f2b348f86f6853f6596266d1cd8b5764af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Nowacki?= Date: Tue, 11 Sep 2018 09:25:07 +0200 Subject: [PATCH 10/13] Update Entity.php --- system/Entity.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/system/Entity.php b/system/Entity.php index a290d0fed099..e072b317b552 100644 --- a/system/Entity.php +++ b/system/Entity.php @@ -398,14 +398,14 @@ protected function castAs($value, string $type) $value = (array)$value; break; - case 'json': + case 'json': if (function_exists('json_decode') && is_string($value) && (strpos($value, '[') === 0 || strpos($value, '{') === 0)) { $value = json_decode($value, false); - $json_last_error = json_last_error(); - if($json_last_error !== JSON_ERROR_NONE) + + if(json_last_error() !== JSON_ERROR_NONE) { - throw CastException::forInvalidJsonFormatException($json_last_error); + throw CastException::forInvalidJsonFormatException(json_last_error()); } } $value = (object)$value; @@ -414,10 +414,10 @@ protected function castAs($value, string $type) if (function_exists('json_decode') && is_string($value) && (strpos($value, '[') === 0 || strpos($value, '{') === 0)) { $value = json_decode($value, true); - $json_last_error = json_last_error(); - if($json_last_error !== JSON_ERROR_NONE) + + if(json_last_error() !== JSON_ERROR_NONE) { - throw CastException::forInvalidJsonFormatException($json_last_error); + throw CastException::forInvalidJsonFormatException(json_last_error()); } } $value = (array)$value; From 919b3bde667968a17f8cf27b4e63efe769bd9499 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Nowacki?= Date: Tue, 11 Sep 2018 11:06:29 +0200 Subject: [PATCH 11/13] Update CastException.php --- system/Exceptions/CastException.php | 42 +++++++++++++---------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/system/Exceptions/CastException.php b/system/Exceptions/CastException.php index a6e763d5937d..b4cbbbbd55ec 100644 --- a/system/Exceptions/CastException.php +++ b/system/Exceptions/CastException.php @@ -13,30 +13,26 @@ class CastException extends CriticalError protected $code = 3; public static function forInvalidJsonFormatException(int $error) - { - if($error === JSON_ERROR_DEPTH) + { + switch($error) { - throw new static(lang('Cast.jsonErrorDepth')); - } - else if($error === JSON_ERROR_STATE_MISMATCH) - { - throw new static(lang('Cast.jsonErrorStateMismatch')); - } - else if($error === JSON_ERROR_CTRL_CHAR) - { - throw new static(lang('Cast.jsonErrorCtrlChar')); - } - else if($error === JSON_ERROR_SYNTAX) - { - throw new static(lang('Cast.jsonErrorSyntax')); - } - else if($error === JSON_ERROR_UTF8) - { - throw new static(lang('Cast.jsonErrorUtf8')); - } - else - { - throw new static(lang('Cast.jsonErrorUnknown')); + case JSON_ERROR_DEPTH: + throw new static(lang('Cast.jsonErrorDepth')); + break; + case JSON_ERROR_STATE_MISMATCH: + throw new static(lang('Cast.jsonErrorStateMismatch')); + break; + case JSON_ERROR_CTRL_CHAR: + throw new static(lang('Cast.jsonErrorCtrlChar')); + break; + case JSON_ERROR_SYNTAX: + throw new static(lang('Cast.jsonErrorSyntax')); + break; + case JSON_ERROR_UTF8: + throw new static(lang('Cast.jsonErrorUtf8')); + break; + default: + throw new static(lang('Cast.jsonErrorUnknown')); } } From cdaffddddfc1c786fafb56bfe5873d7403424e92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Nowacki?= Date: Wed, 12 Sep 2018 08:46:52 +0200 Subject: [PATCH 12/13] Keys: Cast.stringName => stringName --- system/Language/en/Cast.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/system/Language/en/Cast.php b/system/Language/en/Cast.php index a260bf9b6904..1b9a60a5839e 100644 --- a/system/Language/en/Cast.php +++ b/system/Language/en/Cast.php @@ -14,10 +14,10 @@ */ return [ - 'Cast.jsonErrorDepth' => 'Maximum stack depth exceeded', - 'Cast.jsonErrorStateMismatch' => 'Underflow or the modes mismatch', - 'Cast.jsonErrorCtrlChar' => 'Unexpected control character found', - 'Cast.jsonErrorSyntax' => 'Syntax error, malformed JSON', - 'Cast.jsonErrorUtf8' => 'Malformed UTF-8 characters, possibly incorrectly encoded', - 'Cast.jsonErrorUnknown' => 'Unknown error' + 'jsonErrorDepth' => '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' ]; From 33395e5792e197332ce92ca7d21febc50360471b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Nowacki?= Date: Fri, 14 Sep 2018 16:10:47 +0200 Subject: [PATCH 13/13] improvments tl;dr; small improvements according to experience with using json, json-array casting in implementation of my cms formatting extract code for JSON casting (which was almost identical for "json" and "json-array") to castAsJson support for "string" and numeric casting: 0x322, 0272, 0b1010101, 1234e0, removing force casting to object as "json" (what was too much according to actual manual) --- system/Entity.php | 53 ++++++++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/system/Entity.php b/system/Entity.php index e072b317b552..2f4e4994fac7 100644 --- a/system/Entity.php +++ b/system/Entity.php @@ -368,6 +368,7 @@ protected function mutateDate($value) * * @return mixed */ + protected function castAs($value, string $type) { switch($type) @@ -398,29 +399,11 @@ protected function castAs($value, string $type) $value = (array)$value; break; - case 'json': - if (function_exists('json_decode') && is_string($value) && (strpos($value, '[') === 0 || strpos($value, '{') === 0)) - { - $value = json_decode($value, false); - - if(json_last_error() !== JSON_ERROR_NONE) - { - throw CastException::forInvalidJsonFormatException(json_last_error()); - } - } - $value = (object)$value; + case 'json': + $value = $this->castAsJson($value, false); break; case 'json-array': - if (function_exists('json_decode') && is_string($value) && (strpos($value, '[') === 0 || strpos($value, '{') === 0)) - { - $value = json_decode($value, true); - - if(json_last_error() !== JSON_ERROR_NONE) - { - throw CastException::forInvalidJsonFormatException(json_last_error()); - } - } - $value = (array)$value; + $value = $this->castAsJson($value, true); break; case 'datetime': return new \DateTime($value); @@ -432,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; + } }