From b05034efea36e1a6036ce2e608c23077e0822622 Mon Sep 17 00:00:00 2001 From: ignace nyamagana butera Date: Mon, 7 Oct 2024 09:39:58 +0200 Subject: [PATCH] Improve JSONConverter public API --- docs/9.0/converter/json.md | 26 ++++++++++- src/JsonConverter.php | 92 ++++++++++++++++++++++++++++++++++++++ src/JsonConverterTest.php | 19 ++++++++ 3 files changed, 135 insertions(+), 2 deletions(-) diff --git a/docs/9.0/converter/json.md b/docs/9.0/converter/json.md index ea10c399..3dd99038 100644 --- a/docs/9.0/converter/json.md +++ b/docs/9.0/converter/json.md @@ -25,6 +25,27 @@ public JsonConverter::removeFlags(int ...$flag): self This method sets the JSON flags to be used during conversion. The method handles all the flags supported by PHP `json_encode` function. +If you prefer a more expressive way for setting the flags you can use `with*` and `without*` methods +whose name are derived from PHP JSON constants. + +```php +$converter = JsonConverter::create() + ->addFlags(JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_FORCE_OBJECT) + ->removeFlags(JSON_HEX_QUOT); + +//is equivalent to + +$converter = JsonConverter::create() + ->withPrettyPrint() + ->withUnescapedSlashes() + ->withForceObject() + ->withoutHexQuot(); +``` + +

Because we are converting one record at a time, the class always uses JSON_THROW_ON_ERROR +to stop the collection conversion. As such adding or removing the flag using the methods describe here before will +have no effect on its usage, the flag is ALWAYS set.

+ ### JsonConverter::depth ```php @@ -52,7 +73,7 @@ public JsonConverter::formatter(?callback $formatter): mixed This method allow to apply a callback prior to `json_encode` your collection individual item. Since the encoder does not rely on PHP's `JsonSerializable` interface but on PHP's `iterable` -structure. The expected conversion may differ to what you expect. This callback allows you to +structure. The resulting conversion may differ to what you expect. This callback allows you to specify how each item will be converted. The formatter should return a type that can be handled by PHP `json_encode` function. @@ -81,7 +102,8 @@ $document->setHeaderOffset(0); CharsetConverter::addTo($document, 'iso-8859-15', 'utf-8'); $converter = JsonConverter::create() - ->addFlags(JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES) + ->withPrettyPrint() + ->withUnescapedSlashes() ->depth(2) ->indentSize(2) ->formatter(function (array $row) { diff --git a/src/JsonConverter.php b/src/JsonConverter.php index b0d8adef..4e5101d3 100644 --- a/src/JsonConverter.php +++ b/src/JsonConverter.php @@ -13,6 +13,7 @@ namespace League\Csv; +use BadMethodCallException; use Closure; use Exception; use InvalidArgumentException; @@ -22,6 +23,26 @@ use SplFileInfo; use SplFileObject; +use function array_filter; +use function array_keys; +use function array_reduce; +use function get_defined_constants; +use function is_resource; +use function is_string; +use function json_encode; +use function json_last_error; +use function lcfirst; +use function restore_error_handler; +use function set_error_handler; +use function str_repeat; +use function str_replace; +use function str_starts_with; +use function strlen; +use function strtolower; +use function substr; +use function ucwords; + +use const ARRAY_FILTER_USE_KEY; use const JSON_ERROR_NONE; use const JSON_FORCE_OBJECT; use const JSON_PRETTY_PRINT; @@ -30,6 +51,41 @@ /** * Converts and store tabular data into a JSON string. * @template T + * + * @method JsonConverter withHexTag() adds the JSON_HEX_TAG flag + * @method JsonConverter withoutHexTag() removes the JSON_HEX_TAG flag + * @method JsonConverter withHexAmp() adds the JSON_HEX_AMP flag + * @method JsonConverter withoutHexAmp() removes the JSON_HEX_AMP flag + * @method JsonConverter withHexApos() adds the JSON_HEX_APOS flag + * @method JsonConverter withoutHexApos() removes the JSON_HEX_APOS flag + * @method JsonConverter withHexQuot() adds the JSON_HEX_QUOT flag + * @method JsonConverter withoutHexQuot() removes the JSON_HEX_QUOT flag + * @method JsonConverter withForceObject() adds the JSON_FORCE_OBJECT flag + * @method JsonConverter withoutForceObject() removes the JSON_FORCE_OBJECT flag + * @method JsonConverter withNumericCheck() adds the JSON_NUMERIC_CHECK flag + * @method JsonConverter withoutNumericCheck() removes the JSON_NUMERIC_CHECK flag + * @method JsonConverter withUnescapedSlashes() adds the JSON_UNESCAPED_SLASHES flag + * @method JsonConverter withoutUnescapedSlashes() removes the JSON_UNESCAPED_SLASHES flag + * @method JsonConverter withPrettyPrint() adds the JSON_PRETTY_PRINT flag + * @method JsonConverter withoutPrettyPrint() removes the JSON_PRETTY_PRINT flag + * @method JsonConverter withUnescapedUnicode() adds the JSON_UNESCAPED_UNICODE flag + * @method JsonConverter withoutUnescapedUnicode() removes the JSON_UNESCAPED_UNICODE flag + * @method JsonConverter withPartialOutputOnError() adds the JSON_PARTIAL_OUTPUT_ON_ERROR flag + * @method JsonConverter withoutPartialOutputOnError() removes the JSON_PARTIAL_OUTPUT_ON_ERROR flag + * @method JsonConverter withPreserveZeroFraction() adds the JSON_PRESERVE_ZERO_FRACTION flag + * @method JsonConverter withoutPreserveZeroFraction() removes the JSON_PRESERVE_ZERO_FRACTION flag + * @method JsonConverter withUnescapedLineTerminators() adds the JSON_UNESCAPED_LINE_TERMINATORS flag + * @method JsonConverter withoutUnescapedLineTerminators() removes the JSON_UNESCAPED_LINE_TERMINATORS flag + * @method JsonConverter withObjectAsArray() adds the JSON_OBJECT_AS_ARRAY flag + * @method JsonConverter withoutObjectAsArray() removes the JSON_OBJECT_AS_ARRAY flag + * @method JsonConverter withBigintAsString() adds the JSON_BIGINT_AS_STRING flag + * @method JsonConverter withoutBigintAsString() removes the JSON_BIGINT_AS_STRING flag + * @method JsonConverter withInvalidUtf8Ignore() adds the JSON_INVALID_UTF8_IGNORE flag + * @method JsonConverter withoutInvalidUtf8Ignore() removes the JSON_INVALID_UTF8_IGNORE flag + * @method JsonConverter withInvalidUtf8Substitute() adds the JSON_INVALID_UTF8_SUBSTITUTE flag + * @method JsonConverter withoutInvalidUtf8Substitute() removes the JSON_INVALID_UTF8_SUBSTITUTE flag + * @method JsonConverter withThrowOnError() adds the JSON_THROW_ON_ERROR flag + * @method JsonConverter withoutThrowOnError() removes the JSON_THROW_ON_ERROR flag */ final class JsonConverter { @@ -45,6 +101,30 @@ final class JsonConverter /** @var Closure(string, array-key): string */ private readonly Closure $internalFormatter; + /** + * @return array + */ + private static function methodToFlag(): array + { + static $methods; + if (null === $methods) { + $flagNames = array_keys( + array_filter( + get_defined_constants(true)['json'], + fn (string $key) => str_starts_with($key, 'JSON_') && !str_starts_with($key, 'JSON_ERROR_'), + ARRAY_FILTER_USE_KEY + ) + ); + + $methods = []; + foreach ($flagNames as $name) { + $methods[lcfirst(str_replace('_', '', ucwords(strtolower(substr($name, 5)), '_')))] = constant($name); + } + } + + return $methods; + } + public static function create(): self { return new self( @@ -79,6 +159,18 @@ private function __construct( $this->internalFormatter = $this->setInternalFormatter(); } + /** + * @throws BadMethodCallException + */ + public function __call(string $name, array $arguments): self + { + return match (true) { + str_starts_with($name, 'without') => $this->removeFlags(self::methodToFlag()[lcfirst(substr($name, 7))] ?? throw new BadMethodCallException('The method "'.$name.'" does not exist.')), + str_starts_with($name, 'with') => $this->addFlags(self::methodToFlag()[lcfirst(substr($name, 4))] ?? throw new BadMethodCallException('The method "'.$name.'" does not exist.')), + default => throw new BadMethodCallException('The method "'.$name.'" does not exist.'), + }; + } + /** * Adds a list of JSON flags. */ diff --git a/src/JsonConverterTest.php b/src/JsonConverterTest.php index b39ecacb..6a1d7b5f 100644 --- a/src/JsonConverterTest.php +++ b/src/JsonConverterTest.php @@ -19,6 +19,7 @@ use PHPUnit\Framework\TestCase; use const JSON_FORCE_OBJECT; +use const JSON_HEX_QUOT; use const JSON_PRETTY_PRINT; use const JSON_UNESCAPED_SLASHES; @@ -112,4 +113,22 @@ public function it_can_manipulate_the_record_prior_to_json_encode(): void self::assertSame('[{"foo":"BAR"}]', $converter->encode([['foo' => 'bar']])); } + + #[Test] + public function it_can_use_syntactic_sugar_methods_to_set_json_flags(): void + { + $usingJsonFlags = JsonConverter::create() + ->addFlags(JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_FORCE_OBJECT) + ->removeFlags(JSON_HEX_QUOT) + ->depth(24); + + $usingMethodFlags = JsonConverter::create() + ->withPrettyPrint() + ->withUnescapedSlashes() + ->withForceObject() + ->withoutHexQuot() + ->depth(24); + + self::assertEquals($usingJsonFlags, $usingMethodFlags); + } }