From 860cda29422b83693d5a0f690b76954d815f6a2d Mon Sep 17 00:00:00 2001 From: Filip Halaxa Date: Mon, 26 Aug 2024 21:43:32 +0200 Subject: [PATCH] wip --- src/FacadeTrait.php | 126 +++++-------------- src/Items.php | 65 +++++++++- src/Parser.php | 16 ++- src/RecursiveItems.php | 114 ++++++++++++++++- test/JsonMachineTest/ItemsTest.php | 1 - test/JsonMachineTest/NestedIteratorTest.php | 15 --- test/JsonMachineTest/ParserTest.php | 9 -- test/JsonMachineTest/RecursiveItemsTest.json | 1 + test/JsonMachineTest/RecursiveItemsTest.php | 75 +++++++++++ 9 files changed, 286 insertions(+), 136 deletions(-) create mode 100644 test/JsonMachineTest/RecursiveItemsTest.json create mode 100644 test/JsonMachineTest/RecursiveItemsTest.php diff --git a/src/FacadeTrait.php b/src/FacadeTrait.php index dbd8057..412e984 100644 --- a/src/FacadeTrait.php +++ b/src/FacadeTrait.php @@ -10,21 +10,6 @@ trait FacadeTrait { - /** - * @var iterable - */ - private $chunks; - - /** - * @var string - */ - private $jsonPointer; - - /** - * @var ItemDecoder|null - */ - private $jsonDecoder; - /** * @var Parser */ @@ -35,100 +20,34 @@ trait FacadeTrait */ private $debugEnabled; - /** - * @todo Make private when PHP 7 stops being supported - */ - protected abstract function recursive(): bool; + public function isDebugEnabled(): bool + { + return $this->debugEnabled; + } /** * @param iterable $bytesIterator * * @throws InvalidArgumentException */ - public function __construct($bytesIterator, array $options = []) + private static function createParser($bytesIterator, ItemsOptions $options, bool $recursive): Parser { - $options = new ItemsOptions($options); - - $this->chunks = $bytesIterator; - $this->jsonPointer = $options['pointer']; - $this->jsonDecoder = $options['decoder']; - $this->debugEnabled = $options['debug']; - - if ($this->debugEnabled) { + if ($options['debug']) { $tokensClass = TokensWithDebugging::class; } else { $tokensClass = Tokens::class; } - $this->parser = new Parser( + return new Parser( new $tokensClass( - $this->chunks + $bytesIterator ), - $this->jsonPointer, - $this->jsonDecoder ?: new ExtJsonDecoder(), - $this->recursive() + $options['pointer'], + $options['decoder'] ?: new ExtJsonDecoder(), + $recursive ); } - /** - * @param string $string - * - * @return self - * - * @throws InvalidArgumentException - */ - public static function fromString($string, array $options = []) - { - return new self(new StringChunks($string), $options); - } - - /** - * @param string $file - * - * @return self - * - * @throws Exception\InvalidArgumentException - */ - public static function fromFile($file, array $options = []) - { - return new self(new FileChunks($file), $options); - } - - /** - * @param resource $stream - * - * @return self - * - * @throws Exception\InvalidArgumentException - */ - public static function fromStream($stream, array $options = []) - { - return new self(new StreamChunks($stream), $options); - } - - /** - * @param iterable $iterable - * - * @return self - * - * @throws Exception\InvalidArgumentException - */ - public static function fromIterable($iterable, array $options = []) - { - return new self($iterable, $options); - } - - /** - * @return \Generator - * - * @throws Exception\PathNotFoundException - */ - #[\ReturnTypeWillChange] - public function getIterator() - { - return $this->parser->getIterator(); - } - /** * @throws Exception\JsonMachineException */ @@ -159,10 +78,23 @@ public function getMatchedJsonPointer(): string } /** - * @return bool + * @param string $string */ - public function isDebugEnabled() - { - return $this->debugEnabled; - } + abstract public static function fromString($string, array $options = []): self; + + /** + * @param string $file + */ + abstract public static function fromFile($file, array $options = []): self; + + /** + * @param resource $stream + */ + abstract public static function fromStream($stream, array $options = []): self; + + /** + * @param iterable $iterable + */ + abstract public static function fromIterable($iterable, array $options = []): self; + } diff --git a/src/Items.php b/src/Items.php index 5749f8a..2136223 100644 --- a/src/Items.php +++ b/src/Items.php @@ -4,6 +4,8 @@ namespace JsonMachine; +use JsonMachine\Exception\InvalidArgumentException; + /** * Entry-point facade for JSON Machine. */ @@ -11,8 +13,67 @@ final class Items implements \IteratorAggregate, PositionAware { use FacadeTrait; - protected function recursive(): bool + /** + * @param iterable $bytesIterator + * + * @throws InvalidArgumentException + */ + public function __construct($bytesIterator, array $options = []) + { + $options = new ItemsOptions($options); + $this->debugEnabled = $options['debug']; + + $this->parser = $this->createParser($bytesIterator, $options, false); + } + + /** + * @param string $string + * + * @throws InvalidArgumentException + */ + public static function fromString($string, array $options = []): self + { + return new self(new StringChunks($string), $options); + } + + /** + * @param string $file + * + * @throws Exception\InvalidArgumentException + */ + public static function fromFile($file, array $options = []): self + { + return new self(new FileChunks($file), $options); + } + + /** + * @param resource $stream + * + * @throws Exception\InvalidArgumentException + */ + public static function fromStream($stream, array $options = []): self + { + return new self(new StreamChunks($stream), $options); + } + + /** + * @param iterable $iterable + * + * @throws Exception\InvalidArgumentException + */ + public static function fromIterable($iterable, array $options = []): self + { + return new self($iterable, $options); + } + + /** + * @return \Generator + * + * @throws Exception\PathNotFoundException + */ + #[\ReturnTypeWillChange] + public function getIterator() { - return false; + return $this->parser->getIterator(); } } diff --git a/src/Parser.php b/src/Parser.php index d93085d..80691aa 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -163,14 +163,12 @@ public function getIterator() ) ) { if ($this->recursive && ($token == '{' || $token == '[')) { - $jsonValue = new NestedIterator( - (new self( - $this->remainingTokens(), - '', - $this->jsonDecoder, - true - ))->getIterator() - ); + $jsonValue = (new self( + $this->remainingTokens(), + '', + $this->jsonDecoder, + true + ))->$this->getIterator(); $token = ' '; } else { $jsonValue .= $token; @@ -400,7 +398,7 @@ public function getCurrentJsonPointer(): string */ public function getMatchedJsonPointer(): string { - if ($this->matchedJsonPointer === null) { + if ($this->isOutsideGenerator()) { throw new JsonMachineException(__METHOD__.' must be called inside a loop'); } diff --git a/src/RecursiveItems.php b/src/RecursiveItems.php index 3d470ce..f8e1325 100644 --- a/src/RecursiveItems.php +++ b/src/RecursiveItems.php @@ -4,15 +4,123 @@ namespace JsonMachine; +use Iterator; +use JsonMachine\Exception\InvalidArgumentException; + /** * Entry-point facade for recursive iteration. */ -final class RecursiveItems implements \IteratorAggregate, PositionAware +final class RecursiveItems implements \RecursiveIterator, PositionAware { use FacadeTrait; - protected function recursive(): bool + /** @var Parser */ + private $parser; + + /** @var ItemsOptions */ + private $options; + + /** @var Iterator */ + private $parserIterator; + + public function __construct(Parser $parser, ItemsOptions $options) + { + $this->parser = $parser; + $this->options = $options; + $this->debugEnabled = $options['debug']; + } + + /** + * @throws InvalidArgumentException + */ + public static function fromString($string, array $options = []): self + { + $options = new ItemsOptions($options); + return new self( + self::createParser(new StringChunks($string), $options, true), + $options + ); + } + + /** + * @throws InvalidArgumentException + */ + public static function fromFile($file, array $options = []): self { - return true; + $options = new ItemsOptions($options); + return new self( + self::createParser(new FileChunks($file), $options, true), + $options + ); + } + + /** + * @throws InvalidArgumentException + */ + public static function fromStream($stream, array $options = []): self + { + $options = new ItemsOptions($options); + return new self( + self::createParser(new StreamChunks($stream), $options, true), + $options + ); + } + + /** + * @throws InvalidArgumentException + */ + public static function fromIterable($iterable, array $options = []): self + { + $options = new ItemsOptions($options); + return new self( + self::createParser($iterable, $options, true), + $options + ); + } + + public function current() + { + $current = $this->parserIterator->current(); + if ($current instanceof Parser) { + return new self($current, $this->options); + } + + return $current; + } + + public function next() + { + $this->parserIterator->next(); + } + + public function key() + { + return $this->parserIterator->key(); + } + + public function valid(): bool + { + return $this->parserIterator->valid(); + } + + public function rewind() + { + $this->parserIterator = $this->parser->getIterator(); + $this->parserIterator->rewind(); + } + + public function hasChildren(): bool + { + return $this->current() instanceof self; + } + + public function getChildren() + { + $current = $this->current(); + if ($current instanceof self) { + return $current; + } + + return null; } } diff --git a/test/JsonMachineTest/ItemsTest.php b/test/JsonMachineTest/ItemsTest.php index b68668a..9c4ab18 100644 --- a/test/JsonMachineTest/ItemsTest.php +++ b/test/JsonMachineTest/ItemsTest.php @@ -9,7 +9,6 @@ /** * @covers \JsonMachine\Items - * @covers \JsonMachine\RecursiveItems */ class ItemsTest extends \PHPUnit_Framework_TestCase { diff --git a/test/JsonMachineTest/NestedIteratorTest.php b/test/JsonMachineTest/NestedIteratorTest.php index faeffa9..e053505 100644 --- a/test/JsonMachineTest/NestedIteratorTest.php +++ b/test/JsonMachineTest/NestedIteratorTest.php @@ -46,21 +46,6 @@ public function testHasChildrenFollowsIterators() $this->assertSame([false, true, false], $result); } - public function testGetChildrenReturnsNestedIterator() - { - $generator = function () {yield from [1, new \ArrayIterator([]), 3]; }; - $iterator = new NestedIterator($generator()); - - $result = []; - foreach ($iterator as $item) { - $result[] = $iterator->getChildren(); - } - - $this->assertSame(null, $result[0]); - $this->assertInstanceOf(NestedIterator::class, $result[1]); - $this->assertSame(null, $result[2]); - } - public function testGetChildrenReturnsCorrectItems() { $generator = function () {yield from [1, new \ArrayIterator([2]), 3]; }; diff --git a/test/JsonMachineTest/ParserTest.php b/test/JsonMachineTest/ParserTest.php index b14b342..6a7d117 100644 --- a/test/JsonMachineTest/ParserTest.php +++ b/test/JsonMachineTest/ParserTest.php @@ -590,13 +590,4 @@ public function testRecursiveParserDoesNotRequireChildParserToBeIteratedToTheEnd $this->expectExceptionMessage('generator'); iterator_to_array($array[1]); } - - public function testRecursiveIterationYieldsNestedIterator() - { - $iterator = new Parser(new Tokens(['[[1]]']), '', null, true); - - foreach ($iterator as $item) { - $this->assertInstanceOf(NestedIterator::class, $item); - } - } } diff --git a/test/JsonMachineTest/RecursiveItemsTest.json b/test/JsonMachineTest/RecursiveItemsTest.json new file mode 100644 index 0000000..bfb8d7b --- /dev/null +++ b/test/JsonMachineTest/RecursiveItemsTest.json @@ -0,0 +1 @@ +{"path": {"key":["value"]}} diff --git a/test/JsonMachineTest/RecursiveItemsTest.php b/test/JsonMachineTest/RecursiveItemsTest.php new file mode 100644 index 0000000..2cf678c --- /dev/null +++ b/test/JsonMachineTest/RecursiveItemsTest.php @@ -0,0 +1,75 @@ + $args[1], + 'decoder' => $args[2], + 'debug' => $args[3], + ], + ]); + $this->assertSame($expected, iterator_to_array($iterator)); + } + + public function data_testFactories() + { + foreach ([true, false] as $debug) { + foreach ([ + [RecursiveItems::class, 'fromStream', fopen('data://text/plain,{"path": {"key":["value"]}}', 'r'), '/path', null, $debug], + [RecursiveItems::class, 'fromString', '{"path": {"key":["value"]}}', '/path', null, $debug], + [RecursiveItems::class, 'fromFile', __DIR__.'/RecursiveItemsTest.json', '/path', null, $debug], + [RecursiveItems::class, 'fromIterable', ['{"path": {"key', '":["value"]}}'], '/path', null, $debug], + [RecursiveItems::class, 'fromIterable', new \ArrayIterator(['{"path": {"key', '":["value"]}}']), '/path', null, $debug], + ] as $case) { + yield $case; + } + } + } + + public function testRecursiveIteration() + { + $items = RecursiveItems::fromString('[[":)"]]'); + + foreach ($items as $emojis) { + $this->assertInstanceOf(RecursiveItems::class, $emojis); + foreach ($emojis as $emoji) { + $this->assertSame(':)', $emoji); + } + } + } + + public function testGetChildrenReturnsNestedIterator() + { + $iterator = RecursiveItems::fromString("[1,[],1]"); + + $result = []; + foreach ($iterator as $item) { + $result[] = $iterator->getChildren(); + } + + $this->assertSame(null, $result[0]); + $this->assertInstanceOf(RecursiveItems::class, $result[1]); + $this->assertSame(null, $result[2]); + } + + public function testCurrentReturnsSameInstanceOfParser() + { + + } +}