Skip to content

Commit 203014d

Browse files
authored
Merge pull request #24 from swaggest/stable-rearrange
Stable rearrange
2 parents 53c412a + 4905e0a commit 203014d

11 files changed

+252
-56
lines changed

.gitattributes

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
*.dat -text
44
/tests export-ignore
55
/stubs export-ignore
6+
/tools export-ignore
67
/.gitattributes export-ignore
78
/.gitignore export-ignore
9+
/.gitmodules export-ignore
810
/.scrutinizer.yml export-ignore
911
/.travis.yml export-ignore
1012
/.gitlab-ci.yml
1113
/phpunit.xml export-ignore
14+
/phpstan.neon export-ignore
1215
/changelog.md export-ignore
1316
/Makefile export-ignore
14-
/.gitlab-ci.yml

.gitignore

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
/vendor
22
/.idea
33
/composer.phar
4-
/clover.xml
4+
/clover.xml
5+
build
6+
/xhprof_report.*
7+
benchmark-result.json
8+
/coverage.xml
9+
/phpbench-candidate.xml
10+
/phpbench-master.xml

.travis.yml

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
language: php
22
php:
33
- nightly
4-
- hhvm
54
- 7.3
65
- 7.2
76
- 7.1
@@ -16,26 +15,20 @@ dist: trusty
1615
## Cache composer bits
1716
cache:
1817
directories:
19-
- $HOME/.composer/cache
18+
- $HOME/.cache/composer
2019

21-
# execute any number of scripts before the test run, custom env's are available as variables
2220
before_script:
2321
- composer install --dev --no-interaction --prefer-dist
24-
- if [[ $(phpenv version-name) =~ 7.2 ]] ; then test -f $HOME/.composer/cache/phpstan.phar || wget https://github.com/phpstan/phpstan/releases/download/0.9.2/phpstan.phar -O $HOME/.composer/cache/phpstan.phar; fi
25-
- if [[ $(phpenv version-name) =~ 7.2 ]] ; then test -f $HOME/.composer/cache/ocular.phar || wget https://scrutinizer-ci.com/ocular.phar -O $HOME/.composer/cache/ocular.phar; fi
26-
- if [[ $(phpenv version-name) =~ 7.2 ]] ; then test -f $HOME/.composer/cache/cctr || wget https://codeclimate.com/downloads/test-reporter/test-reporter-0.1.4-linux-amd64 -O $HOME/.composer/cache/cctr && chmod +x $HOME/.composer/cache/cctr; fi
27-
- if [[ $(phpenv version-name) =~ 7.2 ]] ; then $HOME/.composer/cache/cctr before-build; fi
22+
- if ! [[ $(phpenv version-name) =~ 7.3 ]] ; then phpenv config-rm xdebug.ini || true ; fi
2823

2924
matrix:
3025
allow_failures:
31-
- php: hhvm
3226
- php: nightly
3327
fast_finish: true
3428

3529
script:
36-
- ./vendor/bin/phpunit -v --configuration phpunit.xml --coverage-text --coverage-clover clover.xml
37-
- if [[ $(phpenv version-name) =~ 7.2 ]] ; then php $HOME/.composer/cache/phpstan.phar analyze -l 7 ./src; fi
30+
- if [[ $(phpenv version-name) =~ 7.3 ]] ; then make test-coverage; else make test; fi
31+
- if [[ $(phpenv version-name) =~ 7.2 ]] ; then make lint bench bench-master bench-compare; fi
3832

3933
after_script:
40-
- if [[ $(phpenv version-name) =~ 7.2 ]] ; then php $HOME/.composer/cache/ocular.phar code-coverage:upload --format=php-clover clover.xml; fi
41-
- if [[ $(phpenv version-name) =~ 7.2 ]] ; then $HOME/.composer/cache/cctr after-build --exit-code $TRAVIS_TEST_RESULT; fi
34+
- if [[ $(phpenv version-name) =~ 7.3 ]] ; then bash <(curl -s https://codecov.io/bash); fi

Makefile

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,32 @@
1-
phar:
2-
composer require php-yaoi/php-yaoi:^1;composer install --no-dev;rm -rf tests/;rm ./json-diff;rm ./json-diff.tar.gz;phar-composer build;mv ./json-diff.phar ./json-diff;tar -zcvf ./json-diff.tar.gz ./json-diff;git reset --hard;composer install
1+
PHPSTAN_VERSION ?= 0.11.15
2+
PHPBENCH_VERSION ?= 0.16.10
33

4-
docker56-composer-update:
5-
test -f ./composer.phar || wget https://getcomposer.org/composer.phar
6-
docker run --rm -v $$(pwd):/code php:5.6-cli bash -c "apt-get update;apt-get install -y unzip;cd /code;php composer.phar update --prefer-source"
4+
deps:
5+
@git submodule init && git submodule update
76

8-
docker56-test:
9-
docker run --rm -v $$(pwd):/code -w /code php:5.6-cli php vendor/bin/phpunit
7+
lint:
8+
@test -f ${HOME}/.cache/composer/phpstan-${PHPSTAN_VERSION}.phar || (mkdir -p ${HOME}/.cache/composer/ && wget https://github.com/phpstan/phpstan/releases/download/${PHPSTAN_VERSION}/phpstan.phar -O ${HOME}/.cache/composer/phpstan-${PHPSTAN_VERSION}.phar)
9+
@php $$HOME/.cache/composer/phpstan-${PHPSTAN_VERSION}.phar analyze -l 7 -c phpstan.neon ./src
10+
11+
docker-lint:
12+
@docker run -v $$PWD:/app --rm phpstan/phpstan analyze -l 7 -c phpstan.neon ./src
13+
14+
test:
15+
@php -derror_reporting="E_ALL & ~E_DEPRECATED" vendor/bin/phpunit --configuration phpunit.xml
16+
17+
test-coverage:
18+
@php -derror_reporting="E_ALL & ~E_DEPRECATED" -dzend_extension=xdebug.so vendor/bin/phpunit --configuration phpunit.xml --coverage-text --coverage-clover=coverage.xml
19+
20+
phpbench:
21+
@test -f ${HOME}/.cache/composer/phpbench-${PHPBENCH_VERSION}.phar || (mkdir -p ${HOME}/.cache/composer/ && wget https://github.com/phpbench/phpbench/releases/download/${PHPBENCH_VERSION}/phpbench.phar -O ${HOME}/.cache/composer/phpbench-${PHPBENCH_VERSION}.phar)
22+
23+
bench: phpbench
24+
@php $$HOME/.cache/composer/phpbench-${PHPBENCH_VERSION}.phar run benchmarks --tag=candidate --progress=travis --bootstrap=vendor/autoload.php --revs=50 --iterations=5 --retry-threshold=3 --dump-file=phpbench-candidate.xml
25+
26+
bench-master: phpbench
27+
@git checkout --detach && git fetch origin '+refs/heads/master:refs/heads/master' && git checkout master -- ./src
28+
@composer install --dev --no-interaction --prefer-dist
29+
@php $$HOME/.cache/composer/phpbench-${PHPBENCH_VERSION}.phar run benchmarks --tag=master --progress=none --bootstrap=vendor/autoload.php --revs=50 --iterations=5 --retry-threshold=3 --dump-file=phpbench-master.xml
30+
31+
bench-compare: phpbench
32+
@php $$HOME/.cache/composer/phpbench-${PHPBENCH_VERSION}.phar report --file phpbench-master.xml --file phpbench-candidate.xml --report='generator: "table", cols: [ "set" ], compare: "tag", compare_fields: ["mean"], break: ["benchmark"]'

benchmarks/DiffBench.php

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<?php
2+
3+
use Swaggest\JsonDiff\JsonDiff;
4+
5+
class DiffBench
6+
{
7+
static $simpleOriginal;
8+
static $simpleNew;
9+
10+
static $original;
11+
static $new;
12+
13+
public function benchSimpleSkipPatch()
14+
{
15+
new JsonDiff(self::$simpleOriginal, self::$simpleNew, JsonDiff::REARRANGE_ARRAYS);
16+
}
17+
18+
public function benchSimpleRearrange()
19+
{
20+
new JsonDiff(self::$simpleOriginal, self::$simpleNew, JsonDiff::REARRANGE_ARRAYS);
21+
}
22+
23+
public function benchSkipPatch()
24+
{
25+
new JsonDiff(self::$original, self::$new, JsonDiff::REARRANGE_ARRAYS);
26+
}
27+
28+
public function benchRearrange()
29+
{
30+
new JsonDiff(self::$original, self::$new, JsonDiff::REARRANGE_ARRAYS);
31+
}
32+
33+
public function benchStopOnDiff()
34+
{
35+
new JsonDiff(self::$original, self::$new, JsonDiff::STOP_ON_DIFF);
36+
}
37+
38+
39+
static function init()
40+
{
41+
self::$simpleOriginal = (object)(array("root" => (object)array("a" => 1, "b" => 2)));
42+
self::$simpleNew = (object)(array("root" => (object)array("b" => 3, "c" => 4)));
43+
self::$original = json_decode(<<<'JSON'
44+
{
45+
"key1": [
46+
4,
47+
1,
48+
2,
49+
3
50+
],
51+
"key2": 2,
52+
"key3": {
53+
"sub0": 0,
54+
"sub1": "a",
55+
"sub2": "b"
56+
},
57+
"key4": [
58+
{
59+
"a": 1,
60+
"b": true
61+
},
62+
{
63+
"a": 2,
64+
"b": false
65+
},
66+
{
67+
"a": 3
68+
}
69+
]
70+
}
71+
JSON
72+
);
73+
74+
self::$new = json_decode(<<<'JSON'
75+
{
76+
"key5": "wat",
77+
"key1": [
78+
5,
79+
1,
80+
2,
81+
3
82+
],
83+
"key4": [
84+
{
85+
"c": false,
86+
"a": 2
87+
},
88+
{
89+
"a": 1,
90+
"b": true
91+
},
92+
{
93+
"c": 1,
94+
"a": 3
95+
}
96+
],
97+
"key3": {
98+
"sub3": 0,
99+
"sub2": false,
100+
"sub1": "c"
101+
}
102+
}
103+
JSON
104+
);
105+
}
106+
107+
}
108+
109+
DiffBench::init();

phpstan.neon

Whitespace-only changes.

src/JsonDiff.php

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,11 @@ private function process($original, $new)
392392
return is_array($new) ? $newOrdered : (object)$newOrdered;
393393
}
394394

395+
/**
396+
* @param array $original
397+
* @param array $new
398+
* @return array
399+
*/
395400
private function rearrangeArray(array $original, array $new)
396401
{
397402
$first = reset($original);
@@ -412,7 +417,7 @@ private function rearrangeArray(array $original, array $new)
412417

413418
$keyIsUnique = true;
414419
$uniqueIdx = array();
415-
foreach ($original as $item) {
420+
foreach ($original as $idx => $item) {
416421
if (!$item instanceof \stdClass) {
417422
return $new;
418423
}
@@ -430,7 +435,7 @@ private function rearrangeArray(array $original, array $new)
430435
$keyIsUnique = false;
431436
break;
432437
}
433-
$uniqueIdx[$value] = true;
438+
$uniqueIdx[$value] = $idx;
434439
}
435440

436441
if ($keyIsUnique) {
@@ -439,43 +444,54 @@ private function rearrangeArray(array $original, array $new)
439444
}
440445
}
441446

442-
if ($uniqueKey) {
443-
$newIdx = array();
444-
foreach ($new as $item) {
445-
if (!$item instanceof \stdClass) {
446-
return $new;
447-
}
447+
if (!$uniqueKey) {
448+
return $new;
449+
}
448450

449-
if (!property_exists($item, $uniqueKey)) {
450-
return $new;
451-
}
451+
$newRearranged = [];
452+
$changedItems = [];
452453

453-
$value = $item->$uniqueKey;
454+
foreach ($new as $item) {
455+
if (!$item instanceof \stdClass) {
456+
return $new;
457+
}
454458

455-
if ($value instanceof \stdClass || is_array($value)) {
456-
return $new;
457-
}
459+
if (!property_exists($item, $uniqueKey)) {
460+
return $new;
461+
}
458462

459-
if (isset($newIdx[$value])) {
460-
return $new;
461-
}
463+
$value = $item->$uniqueKey;
462464

463-
$newIdx[$value] = $item;
465+
if ($value instanceof \stdClass || is_array($value)) {
466+
return $new;
464467
}
465468

466-
$newRearranged = array();
467-
foreach ($uniqueIdx as $key => $item) {
468-
if (isset($newIdx[$key])) {
469-
$newRearranged [] = $newIdx[$key];
470-
unset($newIdx[$key]);
469+
470+
if (isset($uniqueIdx[$value])) {
471+
$idx = $uniqueIdx[$value];
472+
// Abandon rearrangement if key is not unique in new array.
473+
if (isset($newRearranged[$idx])) {
474+
return $new;
471475
}
476+
477+
$newRearranged[$idx] = $item;
478+
} else {
479+
$changedItems[] = $item;
472480
}
473-
foreach ($newIdx as $item) {
474-
$newRearranged [] = $item;
481+
482+
$newIdx[$value] = $item;
483+
}
484+
485+
$idx = 0;
486+
foreach ($changedItems as $item) {
487+
while (array_key_exists($idx, $newRearranged)) {
488+
$idx++;
475489
}
476-
return $newRearranged;
490+
$newRearranged[$idx] = $item;
477491
}
478492

479-
return $new;
493+
ksort($newRearranged);
494+
$newRearranged = array_values($newRearranged);
495+
return $newRearranged;
480496
}
481497
}

src/JsonPatch.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,11 @@ public static function import(array $data)
9292
}
9393
$op->path = $operation->path;
9494
if ($op instanceof OpPathValue) {
95-
if (!array_key_exists('value', (array)$operation)) {
95+
if (property_exists($operation, 'value')) {
96+
$op->value = $operation->value;
97+
} else {
9698
throw new Exception('Missing "value" in operation data');
9799
}
98-
$op->value = $operation->value;
99100
} elseif ($op instanceof OpPathFrom) {
100101
if (!isset($operation->from)) {
101102
throw new Exception('Missing "from" in operation data');

src/JsonPointer.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,16 +136,16 @@ public static function add(&$holder, $pathItems, $value, $flags = self::RECURSIV
136136
if ('-' === $key) {
137137
$ref = &$ref[];
138138
} else {
139-
if (is_array($ref) && array_key_exists($key, $ref) && empty($pathItems)) {
140-
array_splice($ref, $key, 0, array($value));
141-
}
142139
if (false === $intKey) {
143140
if (0 === ($flags & self::TOLERATE_ASSOCIATIVE_ARRAYS)) {
144141
throw new Exception('Invalid key for array operation');
145142
}
146143
$ref = &$ref[$key];
147144
continue;
148145
}
146+
if (is_array($ref) && array_key_exists($key, $ref) && empty($pathItems)) {
147+
array_splice($ref, $intKey, 0, array($value));
148+
}
149149
if (0 === ($flags & self::TOLERATE_ASSOCIATIVE_ARRAYS)) {
150150
if ($intKey > count($ref) && 0 === ($flags & self::RECURSIVE_KEY_CREATION)) {
151151
throw new Exception('Index is greater than number of items in array');

src/JsonValueReplace.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public function process($data)
4848
}
4949
}
5050

51+
/** @var string[] $originalKeys */
5152
$originalKeys = $data instanceof \stdClass ? get_object_vars($data) : $data;
5253

5354
if ($check) {

0 commit comments

Comments
 (0)