Skip to content

Commit 9683f3f

Browse files
committed
Issue #2: indexes broken on deletion fixed
1 parent 531553e commit 9683f3f

File tree

7 files changed

+381
-51
lines changed

7 files changed

+381
-51
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88
### Fixed
9-
- All query exceptions have `getSource()` method now.
9+
- All query exceptions have `getSource()` method now.
10+
- Issue #2: delete mutation breaks array indexes.
1011

1112
## [0.6.0] - 2019-11-05
1213
### Added
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Remorhaz\JSON\Pointer\Processor\Mutator;
5+
6+
use ArrayIterator;
7+
use Iterator;
8+
use Remorhaz\JSON\Data\Event\AfterElementEvent;
9+
use Remorhaz\JSON\Data\Event\AfterElementEventInterface;
10+
use Remorhaz\JSON\Data\Event\BeforeElementEvent;
11+
use Remorhaz\JSON\Data\Event\BeforeElementEventInterface;
12+
use Remorhaz\JSON\Data\Event\EventInterface;
13+
use Remorhaz\JSON\Data\Export\EventDecoder;
14+
use Remorhaz\JSON\Data\Path\PathInterface;
15+
use Remorhaz\JSON\Data\Walker\MutationInterface;
16+
use Remorhaz\JSON\Data\Walker\ValueWalkerInterface;
17+
18+
final class DeleteElementMutation implements MutationInterface
19+
{
20+
21+
private $arrayPath;
22+
23+
private $elementPath;
24+
25+
private $elementIndex;
26+
27+
private $elementCounter;
28+
29+
private $eventDecoder;
30+
31+
public function __construct(PathInterface $arrayPath, PathInterface $elementPath)
32+
{
33+
$this->arrayPath = $arrayPath;
34+
$this->elementPath = $elementPath;
35+
$this->eventDecoder = new EventDecoder;
36+
}
37+
38+
public function __invoke(EventInterface $event, ValueWalkerInterface $valueWalker): Iterator
39+
{
40+
if ($this->elementPath->equals($event->getPath())) {
41+
if ($event instanceof AfterElementEventInterface) {
42+
$this->elementCounter = $event->getIndex();
43+
}
44+
}
45+
if ($this->elementPath->contains($event->getPath())) {
46+
return;
47+
}
48+
if (!$this->parentPathMatches($event)) {
49+
yield $event;
50+
51+
return;
52+
}
53+
54+
if (!isset($this->elementCounter)) {
55+
yield $event;
56+
57+
return;
58+
}
59+
60+
switch (true) {
61+
case $event instanceof BeforeElementEventInterface:
62+
yield new BeforeElementEvent(
63+
$this->elementCounter,
64+
$this
65+
->arrayPath
66+
->copyWithElement($this->elementCounter)
67+
);
68+
69+
return;
70+
71+
case $event instanceof AfterElementEventInterface:
72+
yield new AfterElementEvent(
73+
$this->elementCounter,
74+
$this
75+
->arrayPath
76+
->copyWithElement($this->elementCounter)
77+
);
78+
$this->elementCounter++;
79+
80+
return;
81+
82+
default:
83+
yield from $valueWalker->createEventIterator(
84+
$this
85+
->eventDecoder
86+
->exportExistingEvents(new ArrayIterator([$event])),
87+
$this
88+
->arrayPath
89+
->copyWithElement($this->elementCounter)
90+
);
91+
}
92+
93+
return;
94+
}
95+
96+
public function reset(): void
97+
{
98+
unset($this->elementIndex, $this->elementCounter);
99+
}
100+
101+
private function parentPathMatches(EventInterface $event): bool
102+
{
103+
$pathElements = $event->getPath()->getElements();
104+
if (empty($pathElements)) {
105+
return false;
106+
}
107+
108+
return $event
109+
->getPath()
110+
->copyParent()
111+
->equals($this->arrayPath);
112+
}
113+
}

src/Processor/Mutator/DeleteMutation.php renamed to src/Processor/Mutator/DeletePropertyMutation.php

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,13 @@
33

44
namespace Remorhaz\JSON\Pointer\Processor\Mutator;
55

6-
use Generator;
76
use Iterator;
87
use Remorhaz\JSON\Data\Event\EventInterface;
98
use Remorhaz\JSON\Data\Path\PathInterface;
109
use Remorhaz\JSON\Data\Walker\MutationInterface;
1110
use Remorhaz\JSON\Data\Walker\ValueWalkerInterface;
1211

13-
final class DeleteMutation implements MutationInterface
12+
final class DeletePropertyMutation implements MutationInterface
1413
{
1514

1615
private $path;
@@ -21,19 +20,14 @@ public function __construct(PathInterface $path)
2120
}
2221

2322
public function __invoke(EventInterface $event, ValueWalkerInterface $valueWalker): Iterator
24-
{
25-
return $this->createEventGenerator($event);
26-
}
27-
28-
public function reset(): void
29-
{
30-
}
31-
32-
private function createEventGenerator(EventInterface $event): Generator
3323
{
3424
if ($this->path->contains($event->getPath())) {
3525
return;
3626
}
3727
yield $event;
3828
}
29+
30+
public function reset(): void
31+
{
32+
}
3933
}

src/Processor/Mutator/InsertElementMutation.php

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,14 @@ final class InsertElementMutation implements MutationInterface
2828

2929
private $elementCounter = 0;
3030

31+
private $eventDecoder;
32+
3133
public function __construct(NodeValueInterface $value, PathInterface $path, int $elementIndex)
3234
{
3335
$this->value = $value;
3436
$this->path = $path;
3537
$this->elementIndex = $elementIndex;
38+
$this->eventDecoder = new EventDecoder;
3639
}
3740

3841
public function __invoke(EventInterface $event, ValueWalkerInterface $valueWalker): Iterator
@@ -85,10 +88,10 @@ private function processBeforeElementEvent(
8588
}
8689

8790
if ($event->getIndex() == $this->elementIndex) {
88-
$propertyPath = $this->path->copyWithElement($this->elementIndex);
89-
yield new BeforeElementEvent($this->elementIndex, $propertyPath);
90-
yield from $valueWalker->createEventIterator($this->value, $propertyPath);
91-
yield new AfterElementEvent($this->elementIndex, $propertyPath);
91+
$elementPath = $this->path->copyWithElement($this->elementIndex);
92+
yield new BeforeElementEvent($this->elementIndex, $elementPath);
93+
yield from $valueWalker->createEventIterator($this->value, $elementPath);
94+
yield new AfterElementEvent($this->elementIndex, $elementPath);
9295
}
9396
$shiftedIndex = $event->getIndex() + 1;
9497
$path = $event
@@ -123,7 +126,9 @@ private function processElementValue(EventInterface $event, ValueWalkerInterface
123126
->copyWithElement($this->elementCounter + 1);
124127

125128
yield from $valueWalker->createEventIterator(
126-
(new EventDecoder)->exportExistingEvents(new ArrayIterator([$event])),
129+
$this
130+
->eventDecoder
131+
->exportExistingEvents(new ArrayIterator([$event])),
127132
$path,
128133
);
129134
}

src/Processor/Processor.php

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
use Remorhaz\JSON\Pointer\Locator\ReferenceInterface;
1919
use Remorhaz\JSON\Pointer\Processor\Mutator\AppendElementMutation;
2020
use Remorhaz\JSON\Pointer\Processor\Mutator\AppendPropertyMutation;
21-
use Remorhaz\JSON\Pointer\Processor\Mutator\DeleteMutation;
21+
use Remorhaz\JSON\Pointer\Processor\Mutator\DeleteElementMutation;
22+
use Remorhaz\JSON\Pointer\Processor\Mutator\DeletePropertyMutation;
2223
use Remorhaz\JSON\Pointer\Processor\Mutator\InsertElementMutation;
2324
use Remorhaz\JSON\Pointer\Processor\Mutator\Mutator;
2425
use Remorhaz\JSON\Pointer\Processor\Mutator\MutatorInterface;
@@ -73,17 +74,30 @@ public function select(QueryInterface $query, NodeValueInterface $rootNode): Res
7374

7475
public function delete(QueryInterface $query, NodeValueInterface $rootNode): ResultInterface
7576
{
76-
$queryResult = $query($rootNode);
77+
$mutation = $this->createDeleteMutation($query($rootNode));
7778

78-
return $queryResult->hasSelection()
79-
? $this->getMutationResult(
80-
$query,
81-
$rootNode,
82-
new DeleteMutation($queryResult->getSelection()->getPath())
83-
)
79+
return isset($mutation)
80+
? $this->getMutationResult($query, $rootNode, $mutation)
8481
: new NonExistingResult($query->getSource());
8582
}
8683

84+
private function createDeleteMutation(QueryResultInterface $queryResult): ?MutationInterface
85+
{
86+
if (!$queryResult->hasSelection() || !$queryResult->hasParent()) {
87+
return null;
88+
}
89+
$selection = $queryResult->getSelection();
90+
$parent = $queryResult->getParent();
91+
if ($parent instanceof ArrayValueInterface) {
92+
return new DeleteElementMutation($parent->getPath(), $selection->getPath());
93+
}
94+
if ($parent instanceof ObjectValueInterface) {
95+
return new DeletePropertyMutation($selection->getPath());
96+
}
97+
98+
return null;
99+
}
100+
87101
private function getMutationResult(
88102
QueryInterface $query,
89103
NodeValueInterface $rootNode,

tests/AcceptanceTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,36 @@ public function providerSelect(): array
5252
[$document, "/m~0n", '8'],
5353
];
5454
}
55+
56+
/**
57+
* @param string $document
58+
* @param string $pointer
59+
* @param string $expectedValue
60+
* @dataProvider providerDelete
61+
*/
62+
public function testDelete(string $document, string $pointer, string $expectedValue): void
63+
{
64+
$rootNode = NodeValueFactory::create()->createValue($document);
65+
$query = QueryFactory::create()->createQuery($pointer);
66+
$selection = Processor::create()
67+
->delete($query, $rootNode)
68+
->encode();
69+
self::assertSame($expectedValue, $selection);
70+
}
71+
72+
public function providerDelete(): array
73+
{
74+
return [
75+
'First property' => ['{"a":"b","c":"d"}', '/a', '{"c":"d"}'],
76+
'Last property' => ['{"a":"b","c":"d"}', '/c', '{"a":"b"}'],
77+
'Nested inner property' => [
78+
'{"a":{"b":"c","d":"e","f":"g"},"h":"i"}',
79+
'/a/d',
80+
'{"a":{"b":"c","f":"g"},"h":"i"}',
81+
],
82+
'First element' => ['[1,2]', '/0', '[2]'],
83+
'Last element' => ['[1,2]', '/1', '[1]'],
84+
'Nested inner element' => ['[1,[2,3,4],5]', '/1/1', '[1,[2,4],5]'],
85+
];
86+
}
5587
}

0 commit comments

Comments
 (0)