Skip to content

Commit f9798fa

Browse files
committed
Basic patch functionality implemented.
1 parent faa7193 commit f9798fa

File tree

3 files changed

+239
-1
lines changed

3 files changed

+239
-1
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
],
1414
"require": {
1515
"php": ">=7.0",
16-
"remorhaz/php-json-data": "0.1.*"
16+
"remorhaz/php-json-data": "0.1.*",
17+
"remorhaz/php-json-pointer": "0.2.*"
1718
},
1819
"require-dev": {
1920
"phpunit/phpunit": "@stable",

src/Patch.php

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
3+
namespace Remorhaz\JSON\Patch;
4+
5+
use Remorhaz\JSON\Data\SelectableReaderInterface;
6+
use Remorhaz\JSON\Pointer\Pointer;
7+
8+
class Patch
9+
{
10+
11+
private $reader;
12+
13+
14+
public function __construct(SelectableReaderInterface $reader)
15+
{
16+
$this->reader = $reader;
17+
}
18+
19+
20+
public function apply(SelectableReaderInterface $patchReader)
21+
{
22+
$patchReader->selectRoot();
23+
if (!$patchReader->isArraySelected()) {
24+
throw new \Exception("Patch must be an array");
25+
}
26+
$operationCount = $patchReader->getElementCount();
27+
for ($operationIndex = 0; $operationIndex < $operationCount; $operationIndex++) {
28+
$this->performOperation($operationIndex, $patchReader);
29+
}
30+
return $this;
31+
}
32+
33+
34+
protected function getReader(): SelectableReaderInterface
35+
{
36+
return $this->reader;
37+
}
38+
39+
40+
protected function performOperation(int $index, SelectableReaderInterface $patchReader)
41+
{
42+
$patchPointer = new Pointer($patchReader);
43+
$op = $patchPointer->read("/{$index}/op")->getData();
44+
$path = $patchPointer->read("/{$index}/path")->getData();
45+
$dataPointer = new Pointer($this->getReader());
46+
switch ($op) {
47+
case 'add':
48+
$valueReader = $patchPointer->read("/{$index}/value");
49+
$dataPointer->add($path, $valueReader);
50+
break;
51+
52+
case 'remove':
53+
$dataPointer->remove($path);
54+
break;
55+
56+
case 'replace':
57+
$valueReader = $patchPointer->read("/{$index}/value");
58+
$dataPointer->replace($path, $valueReader);
59+
break;
60+
61+
case 'test':
62+
$expectedValueReader = $patchPointer->read("/{$index}/value");
63+
$actualValueReader = $dataPointer->read($path);
64+
if ($expectedValueReader->getData() !== $actualValueReader->getData()) {
65+
throw new \Exception("Test operation failed");
66+
}
67+
break;
68+
69+
default:
70+
throw new \Exception("Unknown operation '{$op}'");
71+
}
72+
return $this;
73+
}
74+
}

tests/PatchTest.php

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
<?php
2+
3+
namespace Remorhaz\JSON\Test\Patch;
4+
5+
use Remorhaz\JSON\Data\RawSelectableReader;
6+
use Remorhaz\JSON\Data\RawSelectableWriter;
7+
use Remorhaz\JSON\Patch\Patch;
8+
9+
class PatchTest extends \PHPUnit_Framework_TestCase
10+
{
11+
12+
13+
/**
14+
* @param mixed $data
15+
* @param array $patchData
16+
* @param mixed $expectedData
17+
* @dataProvider providerValidPatch_Result
18+
*/
19+
public function testApply_ValidPatch_Applied($data, array $patchData, $expectedData)
20+
{
21+
$dataWriter = new RawSelectableWriter($data);
22+
$patchDataReader = new RawSelectableReader($patchData);
23+
(new Patch($dataWriter))->apply($patchDataReader);
24+
$this->assertEquals($expectedData, $data);
25+
}
26+
27+
28+
public function providerValidPatch_Result(): array
29+
{
30+
return [
31+
'A.1' => [
32+
(object) ['foo' => 'bar'],
33+
[
34+
(object) ['op' => 'add', 'path' => '/baz', 'value' => 'qux'],
35+
],
36+
(object) ['baz' => 'qux', 'foo' => 'bar'],
37+
],
38+
'A.2' => [
39+
(object) ['foo' => ['bar', 'baz']],
40+
[
41+
(object) ['op' => 'add', 'path' => '/foo/1', 'value' => 'qux'],
42+
],
43+
(object) ['foo' => ['bar', 'qux', 'baz']],
44+
],
45+
'A.3' => [
46+
(object) ['baz' => 'qux', 'foo' => 'bar'],
47+
[
48+
(object) ['op' => 'remove', 'path' => '/baz'],
49+
],
50+
(object) ['foo' => 'bar'],
51+
],
52+
'A.4' => [
53+
(object) ['foo' => ['bar', 'qux', 'baz']],
54+
[
55+
(object) ['op' => 'remove', 'path' => '/foo/1'],
56+
],
57+
(object) ['foo' => ['bar', 'baz']],
58+
],
59+
'A.5' => [
60+
(object) ['baz' => 'qux', 'foo' => 'bar'],
61+
[
62+
(object) ['op' => 'replace', 'path' => '/baz', 'value' => 'boo'],
63+
],
64+
(object) ['baz' => 'boo', 'foo' => 'bar'],
65+
],
66+
// TODO: Implement A.6-7.
67+
'A.10' => [
68+
(object) ['foo' => 'bar'],
69+
[
70+
(object) ['op' => 'add', 'path' => '/child', 'value' => (object) ['grandchild' => (object) []]],
71+
],
72+
(object) ['foo' => 'bar', 'child' => (object) ['grandchild' => (object) []]],
73+
],
74+
'A.11' => [
75+
(object) ['foo' => 'bar'],
76+
[
77+
(object) ['op' => 'add', 'path' => '/baz', 'value' => 'qux', 'xyz' => 123],
78+
],
79+
(object) ['foo' => 'bar', 'baz' => 'qux'],
80+
],
81+
'A.16' => [
82+
(object) ['foo' => ['bar']],
83+
[
84+
(object) ['op' => 'add', 'path' => '/foo/-', 'value' => ['abc', 'def']],
85+
],
86+
(object) ['foo' => ['bar', ['abc', 'def']]],
87+
],
88+
];
89+
}
90+
91+
92+
/**
93+
* @param $data
94+
* @param array $patchData
95+
* @dataProvider providerValidPatch_Success
96+
*/
97+
public function testApply_ValidPatch_Success($data, array $patchData)
98+
{
99+
$dataWriter = new RawSelectableWriter($data);
100+
$patchDataReader = new RawSelectableReader($patchData);
101+
(new Patch($dataWriter))->apply($patchDataReader);
102+
}
103+
104+
105+
public function providerValidPatch_Success(): array
106+
{
107+
return [
108+
'A.8' => [
109+
(object) ['baz' => 'qux', 'foo' => ["a", 2, "c"]],
110+
[
111+
(object) ['op' => 'test', 'path' => '/baz', 'value' => 'qux'],
112+
(object) ['op' => 'test', 'path' => '/foo/1', 'value' => 2],
113+
],
114+
],
115+
'A.14' => [
116+
(object) ['/' => 9, '~1' => 10],
117+
[
118+
(object) ['op' => 'test', 'path' => '/~01', 'value' => 10],
119+
],
120+
],
121+
];
122+
}
123+
124+
125+
/**
126+
* @param $data
127+
* @param array $patchData
128+
* @dataProvider providerInvalidPatch
129+
* @expectedException \Exception
130+
*/
131+
public function testApply_InvalidPatch_ExceptionThrown($data, array $patchData)
132+
{
133+
$dataWriter = new RawSelectableWriter($data);
134+
$patchDataReader = new RawSelectableReader($patchData);
135+
(new Patch($dataWriter))->apply($patchDataReader);
136+
}
137+
138+
139+
public function providerInvalidPatch(): array
140+
{
141+
return [
142+
'A.9' => [
143+
(object) ['baz' => 'qux'],
144+
[
145+
(object) ['op' => 'test', 'path' => '/baz', 'value' => 'bar'],
146+
],
147+
],
148+
'A.12' => [
149+
(object) ['foo' => 'bar'],
150+
[
151+
(object) ['op' => 'add', 'path' => '/baz/bat', 'value' => 'qux'],
152+
],
153+
],
154+
// A.13 cannot be tested.
155+
'A.14' => [
156+
(object) ['/' => 9, '~1' => 10],
157+
[
158+
(object) ['op' => 'test', 'path' => '/~01', 'value' => "10"],
159+
],
160+
],
161+
];
162+
}
163+
}

0 commit comments

Comments
 (0)