diff --git a/CHANGELOG-5.0.md b/CHANGELOG-5.0.md index fb671d31f89..8b80b3302b3 100644 --- a/CHANGELOG-5.0.md +++ b/CHANGELOG-5.0.md @@ -8,6 +8,8 @@ - Added `Phalcon\Storage\Adapter\Weak` implemented with WeakReference has a cache/retrieval solution for objects not yet collected by the Garbage Collection. [#16372](https://github.com/phalcon/cphalcon/issues/16372) - Extended `Phalcon\Di\Injectable` from `stdClass` to remove the deprecation warning (dynamic properties) for PHP 8.2 [#16308](https://github.com/phalcon/cphalcon/issues/16308) - Corrected the return type of `Phalcon\Mvc\View::getVar()` so that stubs can be accurate. [#16276](https://github.com/phalcon/cphalcon/issues/16276) +- Changed all the `encode`/`decode` methods for JSON to use the `Phalcon\Support\Helper\Json\*` classes. [#15608](https://github.com/phalcon/cphalcon/issues/15608) +- Changed the `Phalcon\Support\Helper\Json\*` classes to clear up `json_last_error()` before doing any conversions. [#15608](https://github.com/phalcon/cphalcon/issues/15608) ## [5.2.2](https://github.com/phalcon/cphalcon/releases/tag/v5.2.2) (2023-06-18) diff --git a/phalcon/Config/Adapter/Json.zep b/phalcon/Config/Adapter/Json.zep index 1cb1be8416a..1ecad32ac0b 100644 --- a/phalcon/Config/Adapter/Json.zep +++ b/phalcon/Config/Adapter/Json.zep @@ -10,8 +10,8 @@ namespace Phalcon\Config\Adapter; -use InvalidArgumentException; // @todo this will also be removed when traits are available use Phalcon\Config\Config; +use Phalcon\Support\Helper\Json\Decode; /** * Reads JSON files and converts them to Phalcon\Config\Config objects. @@ -41,33 +41,10 @@ class Json extends Config public function __construct(string! filePath) { parent::__construct( - this->decode( + (new Decode())->__invoke( file_get_contents(filePath), true ) ); } - - /** - * @todo This will be removed when traits are introduced - */ - private function decode( - string! data, - bool associative = false, - int depth = 512, - int options = 0 - ) -> var - { - var decoded; - - let decoded = json_decode(data, associative, depth, options); - - if unlikely JSON_ERROR_NONE !== json_last_error() { - throw new InvalidArgumentException( - "json_decode error: " . json_last_error_msg() - ); - } - - return decoded; - } } diff --git a/phalcon/DataMapper/Pdo/Profiler/Profiler.zep b/phalcon/DataMapper/Pdo/Profiler/Profiler.zep index 84f6ec121f6..d256f159fba 100644 --- a/phalcon/DataMapper/Pdo/Profiler/Profiler.zep +++ b/phalcon/DataMapper/Pdo/Profiler/Profiler.zep @@ -15,10 +15,10 @@ namespace Phalcon\DataMapper\Pdo\Profiler; -use InvalidArgumentException; // @todo this will also be removed when traits are available use Phalcon\DataMapper\Pdo\Exception\Exception; use Phalcon\Logger\Enum; use Phalcon\Logger\LoggerInterface; +use Phalcon\Support\Helper\Json\Encode; /** * Sends query profiles to a logger. @@ -50,6 +50,11 @@ class Profiler implements ProfilerInterface */ protected logger; + /** + * @var Encode + */ + private encode; + /** * Constructor. * @@ -63,7 +68,8 @@ class Profiler implements ProfilerInterface let this->logFormat = "{method} ({duration}s): {statement} {backtrace}", this->logLevel = Enum::DEBUG, - this->logger = logger; + this->logger = logger, + this->encode = new Encode(); } /** @@ -85,7 +91,7 @@ class Profiler implements ProfilerInterface this->context["duration"] = finish - this->context["start"], this->context["finish"] = finish, this->context["statement"] = statement, - this->context["values"] = empty(values) ? "" : this->encode(values); + this->context["values"] = empty(values) ? "" : this->encode->__invoke(values); this->logger->log(this->logLevel, this->logFormat, this->context); @@ -189,26 +195,4 @@ class Profiler implements ProfilerInterface ]; } } - - /** - * @todo This will be removed when traits are introduced - */ - private function encode( - var data, - int options = 0, - int depth = 512 - ) -> string - { - var encoded; - - let encoded = json_encode(data, options, depth); - - if unlikely JSON_ERROR_NONE !== json_last_error() { - throw new InvalidArgumentException( - "json_encode error: " . json_last_error_msg() - ); - } - - return encoded; - } } diff --git a/phalcon/Encryption/Security/JWT/Builder.zep b/phalcon/Encryption/Security/JWT/Builder.zep index 2d2ab1d6bf6..9c8ec2da15a 100644 --- a/phalcon/Encryption/Security/JWT/Builder.zep +++ b/phalcon/Encryption/Security/JWT/Builder.zep @@ -10,15 +10,15 @@ namespace Phalcon\Encryption\Security\JWT; -use InvalidArgumentException; // @todo this will also be removed when traits are available -use Phalcon\Support\Collection; -use Phalcon\Support\Collection\CollectionInterface; use Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException; use Phalcon\Encryption\Security\JWT\Signer\SignerInterface; use Phalcon\Encryption\Security\JWT\Token\Enum; use Phalcon\Encryption\Security\JWT\Token\Item; use Phalcon\Encryption\Security\JWT\Token\Signature; use Phalcon\Encryption\Security\JWT\Token\Token; +use Phalcon\Support\Collection; +use Phalcon\Support\Collection\CollectionInterface; +use Phalcon\Support\Helper\Json\Encode; /** * JWT Builder @@ -32,6 +32,11 @@ class Builder */ private claims; + /** + * @var Encode + */ + private encode; + /** * @var CollectionInterface */ @@ -57,7 +62,8 @@ class Builder ) { this->init(); - let this->signer = signer; + let this->signer = signer, + this->encode = new Encode(); this->jose->set( Enum::ALGO, @@ -192,9 +198,9 @@ class Builder ); } - let encodedClaims = this->encodeUrl(this->encode(this->getClaims())), + let encodedClaims = this->encodeUrl(this->encode->__invoke(this->getClaims())), claims = new Item(this->getClaims(), encodedClaims), - encodedHeaders = this->encodeUrl(this->encode(this->getHeaders())), + encodedHeaders = this->encodeUrl(this->encode->__invoke(this->getHeaders())), headers = new Item(this->getHeaders(), encodedHeaders), signatureHash = this->signer->sign( encodedHeaders . "." . encodedClaims, @@ -426,26 +432,4 @@ class Builder { return str_replace("=", "", strtr(base64_encode(input), "+/", "-_")); } - - /** - * @todo This will be removed when traits are introduced - */ - private function encode( - var data, - int options = 0, - int depth = 512 - ) -> string - { - var encoded; - - let encoded = json_encode(data, options, depth); - - if unlikely JSON_ERROR_NONE !== json_last_error() { - throw new InvalidArgumentException( - "json_encode error: " . json_last_error_msg() - ); - } - - return encoded; - } } diff --git a/phalcon/Encryption/Security/JWT/Token/Parser.zep b/phalcon/Encryption/Security/JWT/Token/Parser.zep index 1e9b1c8ac57..3d32a339e03 100644 --- a/phalcon/Encryption/Security/JWT/Token/Parser.zep +++ b/phalcon/Encryption/Security/JWT/Token/Parser.zep @@ -11,6 +11,7 @@ namespace Phalcon\Encryption\Security\JWT\Token; use InvalidArgumentException; +use Phalcon\Support\Helper\Json\Decode; /** * Token Parser class. @@ -21,6 +22,23 @@ use InvalidArgumentException; */ class Parser { + /** + * @var Decode + */ + private decode; + + public function __construct( decode = null) + { + var service; + + let service = decode; + if (null === service) { + let service = new Decode(); + } + + let this->decode = service; + } + /** * Parse a token and return it * @@ -55,7 +73,7 @@ class Parser { var decoded; - let decoded = this->decode(this->decodeUrl(claims), true); + let decoded = this->decode->__invoke(this->decodeUrl(claims), true); if typeof decoded !== "array" { throw new InvalidArgumentException( @@ -84,7 +102,7 @@ class Parser { var decoded; - let decoded = this->decode(this->decodeUrl(headers), true); + let decoded = this->decode->__invoke(this->decodeUrl(headers), true); if typeof decoded !== "array" { throw new InvalidArgumentException( @@ -147,29 +165,6 @@ class Parser return parts; } - /** - * @todo This will be removed when traits are introduced - */ - private function decode( - string! data, - bool associative = false, - int depth = 512, - int options = 0 - ) -> var - { - var decoded; - - let decoded = json_decode(data, associative, depth, options); - - if unlikely JSON_ERROR_NONE !== json_last_error() { - throw new InvalidArgumentException( - "json_decode error: " . json_last_error_msg() - ); - } - - return decoded; - } - /** * @todo This will be removed when traits are introduced */ diff --git a/phalcon/Http/Response.zep b/phalcon/Http/Response.zep index d0b54aa7a87..623ca7f7df8 100644 --- a/phalcon/Http/Response.zep +++ b/phalcon/Http/Response.zep @@ -12,19 +12,19 @@ namespace Phalcon\Http; use DateTime; use DateTimeZone; -use InvalidArgumentException; // @todo this will also be removed when traits are available use Phalcon\Di\Di; use Phalcon\Di\DiInterface; +use Phalcon\Di\InjectionAwareInterface; +use Phalcon\Events\EventsAwareInterface; +use Phalcon\Events\ManagerInterface; use Phalcon\Http\Message\ResponseStatusCodeInterface; +use Phalcon\Http\Response\CookiesInterface; use Phalcon\Http\Response\Exception; use Phalcon\Http\Response\HeadersInterface; -use Phalcon\Http\Response\CookiesInterface; use Phalcon\Mvc\Url\UrlInterface; use Phalcon\Mvc\ViewInterface; use Phalcon\Http\Response\Headers; -use Phalcon\Di\InjectionAwareInterface; -use Phalcon\Events\EventsAwareInterface; -use Phalcon\Events\ManagerInterface; +use Phalcon\Support\Helper\Json\Encode; /** * Part of the HTTP cycle is return responses to the clients. @@ -82,6 +82,11 @@ class Response implements ResponseInterface, InjectionAwareInterface, EventsAwar */ protected statusCodes = []; + /** + * @var Encode + */ + private encode; + /** * Phalcon\Http\Response constructor */ @@ -91,7 +96,8 @@ class Response implements ResponseInterface, InjectionAwareInterface, EventsAwar // A Phalcon\Http\Response\Headers bag is temporary used to manage // the headers before sent them to the client - let this->headers = new Headers(); + let this->headers = new Headers(), + this->encode = new Encode(); if content !== null { this->setContent(content); @@ -638,7 +644,7 @@ class Response implements ResponseInterface, InjectionAwareInterface, EventsAwar { this->setContentType("application/json"); - this->setContent(this->encode(content, jsonOptions, depth)); + this->setContent(this->encode->__invoke(content, jsonOptions, depth)); return this; } @@ -865,26 +871,4 @@ class Response implements ResponseInterface, InjectionAwareInterface, EventsAwar return filename; } - - /** - * @todo This will be removed when traits are introduced - */ - private function encode( - var data, - int options = 0, - int depth = 512 - ) -> string - { - var encoded; - - let encoded = json_encode(data, options, depth); - - if unlikely JSON_ERROR_NONE !== json_last_error() { - throw new InvalidArgumentException( - "json_encode error: " . json_last_error_msg() - ); - } - - return encoded; - } } diff --git a/phalcon/Storage/Serializer/Json.zep b/phalcon/Storage/Serializer/Json.zep index ad7366b5d24..c0d608fb619 100644 --- a/phalcon/Storage/Serializer/Json.zep +++ b/phalcon/Storage/Serializer/Json.zep @@ -12,9 +12,34 @@ namespace Phalcon\Storage\Serializer; use InvalidArgumentException; use JsonSerializable; +use Phalcon\Support\Helper\Json\Decode; +use Phalcon\Support\Helper\Json\Encode; class Json extends AbstractSerializer { + /** + * @var Decode + */ + private decode; + + /** + * @var Encode + */ + private encode; + + /** + * AbstractSerializer constructor. + * + * @param mixed|null $data + */ + public function __construct(var data = null) + { + let this->encode = new Encode(), + this->decode = new Decode(); + + parent::__construct(data); + } + /** * Serializes data * @@ -33,7 +58,7 @@ class Json extends AbstractSerializer return this->data; } - return this->getEncode(this->data); + return this->encode->__invoke(this->data); } /** @@ -48,50 +73,7 @@ class Json extends AbstractSerializer if (true !== this->isSerializable(data)) { let this->data = data; } else { - let this->data = this->getDecode(data); + let this->data = this->decode->__invoke(data); } } - - /** - * @todo Remove this when we get traits - */ - private function getDecode( - string! data, - bool associative = false, - int depth = 512, - int options = 0 - ) -> var { - var decoded; - - let decoded = json_decode(data, associative, depth, options); - - if unlikely JSON_ERROR_NONE !== json_last_error() { - throw new InvalidArgumentException( - "json_decode error: " . json_last_error_msg() - ); - } - - return decoded; - } - - /** - * @todo Remove this when we get traits - */ - private function getEncode( - var data, - int options = 0, - int depth = 512 - ) -> string { - var encoded; - - let encoded = json_encode(data, options, depth); - - if unlikely JSON_ERROR_NONE !== json_last_error() { - throw new InvalidArgumentException( - "json_encode error: " . json_last_error_msg() - ); - } - - return encoded; - } } diff --git a/phalcon/Support/Debug/Dump.zep b/phalcon/Support/Debug/Dump.zep index cb8e02760a1..8f1661d3fc2 100644 --- a/phalcon/Support/Debug/Dump.zep +++ b/phalcon/Support/Debug/Dump.zep @@ -10,8 +10,8 @@ namespace Phalcon\Support\Debug; -use InvalidArgumentException; // @todo this will also be removed when traits are available use Phalcon\Di\Di; +use Phalcon\Support\Helper\Json\Encode; use Reflection; use ReflectionClass; use ReflectionProperty; @@ -51,11 +51,18 @@ class Dump */ protected styles = []; + /** + * @var Encode + */ + private encode; + /** * Phalcon\Debug\Dump constructor */ public function __construct(array! styles = [], bool detailed = false) { + let this->encode = new Encode(); + this->setStyles(styles); let this->detailed = detailed; @@ -138,7 +145,7 @@ class Dump */ public function toJson(var variable) -> string { - return this->encode( + return this->encode->__invoke( variable, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ); @@ -346,26 +353,4 @@ class Dump return output . strtr("(:var)", [":style": this->getStyle("other"), ":var": variable]); } - - /** - * @todo This will be removed when traits are introduced - */ - private function encode( - var data, - int options = 0, - int depth = 512 - ) -> string - { - var encoded; - - let encoded = json_encode(data, options, depth); - - if unlikely JSON_ERROR_NONE !== json_last_error() { - throw new InvalidArgumentException( - "json_encode error: " . json_last_error_msg() - ); - } - - return encoded; - } } diff --git a/phalcon/Support/Helper/Json/Decode.zep b/phalcon/Support/Helper/Json/Decode.zep index 7e84890aa1d..df13934ffe5 100644 --- a/phalcon/Support/Helper/Json/Decode.zep +++ b/phalcon/Support/Helper/Json/Decode.zep @@ -15,6 +15,15 @@ use InvalidArgumentException; /** * Decodes a string using `json_decode` and throws an exception if the * JSON data cannot be decoded + * + * The following options are used if none specified for json_encode + * + * JSON_HEX_TAG, JSON_HEX_APOS, JSON_HEX_AMP, JSON_HEX_QUOT, + * JSON_UNESCAPED_SLASHES + * + * If JSON_THROW_ON_ERROR is defined in the options a JsonException will be + * thrown in the case of an error. Otherwise, any error will throw + * InvalidArgumentException */ class Decode { @@ -33,16 +42,26 @@ class Decode string data, bool associative = false, int depth = 512, - int options = 0 + int options = 79 ) { - var decoded; + var decoded, error, message; - let decoded = json_decode(data, associative, depth, options); + /** + * Need to clear the json_last_error() before the code below + */ + let decoded = json_encode(null), + decoded = json_decode(data, associative, depth, options), + error = json_last_error(), + message = json_last_error_msg(); - if (JSON_ERROR_NONE !== json_last_error()) { - throw new InvalidArgumentException( - "json_decode error: " . json_last_error_msg() - ); + /** + * The above will throw an exception when JSON_THROW_ON_ERROR is + * specified. If not, the code below will handle the exception when + * an error occurs + */ + if (JSON_ERROR_NONE !== error) { + json_encode(null); + throw new InvalidArgumentException(message, error); } return decoded; diff --git a/phalcon/Support/Helper/Json/Encode.zep b/phalcon/Support/Helper/Json/Encode.zep index 4a2dee772ea..93c3d8040b1 100644 --- a/phalcon/Support/Helper/Json/Encode.zep +++ b/phalcon/Support/Helper/Json/Encode.zep @@ -10,7 +10,8 @@ namespace Phalcon\Support\Helper\Json; -use JsonException; +use InvalidArgumentException; + /** * Encodes a string using `json_encode` and throws an exception if the * JSON data cannot be encoded @@ -18,7 +19,11 @@ use JsonException; * The following options are used if none specified for json_encode * * JSON_HEX_TAG, JSON_HEX_APOS, JSON_HEX_AMP, JSON_HEX_QUOT, - * JSON_UNESCAPED_SLASHES, JSON_THROW_ON_ERROR + * JSON_UNESCAPED_SLASHES + * + * If JSON_THROW_ON_ERROR is defined in the options a JsonException will be + * thrown in the case of an error. Otherwise, any error will throw + * InvalidArgumentException * * @see https://www.ietf.org/rfc/rfc4627.txt */ @@ -26,34 +31,37 @@ class Encode { /** * @param mixed $data JSON data to parse - * @param int $options Bitmask of JSON decode options. + * @param int $options Bitmask of JSON encode options. * @param int $depth Recursion depth. * * @return string * - * @throws JsonException if the JSON cannot be encoded. + * @throws InvalidArgumentException if the JSON cannot be encoded. * @link https://www.php.net/manual/en/function.json-encode.php */ public function __invoke( var data, - int options = 4194383, + int options = 79, int depth = 512 ) -> string { - var encoded; + var encoded, error, message; /** * Need to clear the json_last_error() before the code below */ - let encoded = json_encode(""), - encoded = json_encode(data, options, depth); + let encoded = json_encode(null), + encoded = json_encode(data, options, depth), + error = json_last_error(), + message = json_last_error_msg(); /** * The above will throw an exception when JSON_THROW_ON_ERROR is * specified. If not, the code below will handle the exception when * an error occurs */ - if (JSON_ERROR_NONE !== json_last_error()) { - throw new JsonException(json_last_error_msg(), 5); + if (JSON_ERROR_NONE !== error) { + json_encode(null); + throw new InvalidArgumentException(message, error); } return (string) encoded; diff --git a/tests/unit/Support/Helper/Json/DecodeCest.php b/tests/unit/Support/Helper/Json/DecodeCest.php index 33ceef4db41..7f4312719c5 100644 --- a/tests/unit/Support/Helper/Json/DecodeCest.php +++ b/tests/unit/Support/Helper/Json/DecodeCest.php @@ -55,8 +55,9 @@ public function supportHelperJsonDecodeException(UnitTester $I) $I->expectThrowable( new InvalidArgumentException( - "json_decode error: Control character error, " . - "possibly incorrectly encoded" + "Control character error, " . + "possibly incorrectly encoded", + 3 ), function () { $data = '{"one'; diff --git a/tests/unit/Support/Helper/Json/EncodeCest.php b/tests/unit/Support/Helper/Json/EncodeCest.php index 603e0471a67..33c6d31ff8a 100644 --- a/tests/unit/Support/Helper/Json/EncodeCest.php +++ b/tests/unit/Support/Helper/Json/EncodeCest.php @@ -13,7 +13,7 @@ namespace Phalcon\Tests\Unit\Support\Helper\Json; -use JsonException; +use InvalidArgumentException; use Phalcon\Support\Helper\Json\Encode; use UnitTester; @@ -56,7 +56,7 @@ public function supportHelperJsonEncodeExceptionDefaultOptions(UnitTester $I) $I->wantToTest('Support\Helper\Json - encode() - exception default options'); $I->expectThrowable( - new JsonException( + new InvalidArgumentException( 'Malformed UTF-8 characters, possibly incorrectly encoded', 5 ), @@ -80,7 +80,7 @@ public function supportHelperJsonEncodeExceptionNoOptions(UnitTester $I) $I->wantToTest('Support\Helper\Json - encode() - exception no options'); $I->expectThrowable( - new JsonException( + new InvalidArgumentException( 'Malformed UTF-8 characters, possibly incorrectly encoded', 5 ),