Skip to content

Stable rearrange #24

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Sep 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
*.dat -text
/tests export-ignore
/stubs export-ignore
/tools export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/.gitmodules export-ignore
/.scrutinizer.yml export-ignore
/.travis.yml export-ignore
/.gitlab-ci.yml
/phpunit.xml export-ignore
/phpstan.neon export-ignore
/changelog.md export-ignore
/Makefile export-ignore
/.gitlab-ci.yml
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
/vendor
/.idea
/composer.phar
/clover.xml
/clover.xml
build
/xhprof_report.*
benchmark-result.json
/coverage.xml
/phpbench-candidate.xml
/phpbench-master.xml
17 changes: 5 additions & 12 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
language: php
php:
- nightly
- hhvm
- 7.3
- 7.2
- 7.1
Expand All @@ -16,26 +15,20 @@ dist: trusty
## Cache composer bits
cache:
directories:
- $HOME/.composer/cache
- $HOME/.cache/composer

# execute any number of scripts before the test run, custom env's are available as variables
before_script:
- composer install --dev --no-interaction --prefer-dist
- 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
- 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
- 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
- if [[ $(phpenv version-name) =~ 7.2 ]] ; then $HOME/.composer/cache/cctr before-build; fi
- if ! [[ $(phpenv version-name) =~ 7.3 ]] ; then phpenv config-rm xdebug.ini || true ; fi

matrix:
allow_failures:
- php: hhvm
- php: nightly
fast_finish: true

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

after_script:
- if [[ $(phpenv version-name) =~ 7.2 ]] ; then php $HOME/.composer/cache/ocular.phar code-coverage:upload --format=php-clover clover.xml; fi
- if [[ $(phpenv version-name) =~ 7.2 ]] ; then $HOME/.composer/cache/cctr after-build --exit-code $TRAVIS_TEST_RESULT; fi
- if [[ $(phpenv version-name) =~ 7.3 ]] ; then bash <(curl -s https://codecov.io/bash); fi
37 changes: 30 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,9 +1,32 @@
phar:
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
PHPSTAN_VERSION ?= 0.11.15
PHPBENCH_VERSION ?= 0.16.10

docker56-composer-update:
test -f ./composer.phar || wget https://getcomposer.org/composer.phar
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"
deps:
@git submodule init && git submodule update

docker56-test:
docker run --rm -v $$(pwd):/code -w /code php:5.6-cli php vendor/bin/phpunit
lint:
@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)
@php $$HOME/.cache/composer/phpstan-${PHPSTAN_VERSION}.phar analyze -l 7 -c phpstan.neon ./src

docker-lint:
@docker run -v $$PWD:/app --rm phpstan/phpstan analyze -l 7 -c phpstan.neon ./src

test:
@php -derror_reporting="E_ALL & ~E_DEPRECATED" vendor/bin/phpunit --configuration phpunit.xml

test-coverage:
@php -derror_reporting="E_ALL & ~E_DEPRECATED" -dzend_extension=xdebug.so vendor/bin/phpunit --configuration phpunit.xml --coverage-text --coverage-clover=coverage.xml

phpbench:
@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)

bench: phpbench
@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

bench-master: phpbench
@git checkout --detach && git fetch origin '+refs/heads/master:refs/heads/master' && git checkout master -- ./src
@composer install --dev --no-interaction --prefer-dist
@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

bench-compare: phpbench
@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"]'
109 changes: 109 additions & 0 deletions benchmarks/DiffBench.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

use Swaggest\JsonDiff\JsonDiff;

class DiffBench
{
static $simpleOriginal;
static $simpleNew;

static $original;
static $new;

public function benchSimpleSkipPatch()
{
new JsonDiff(self::$simpleOriginal, self::$simpleNew, JsonDiff::REARRANGE_ARRAYS);
}

public function benchSimpleRearrange()
{
new JsonDiff(self::$simpleOriginal, self::$simpleNew, JsonDiff::REARRANGE_ARRAYS);
}

public function benchSkipPatch()
{
new JsonDiff(self::$original, self::$new, JsonDiff::REARRANGE_ARRAYS);
}

public function benchRearrange()
{
new JsonDiff(self::$original, self::$new, JsonDiff::REARRANGE_ARRAYS);
}

public function benchStopOnDiff()
{
new JsonDiff(self::$original, self::$new, JsonDiff::STOP_ON_DIFF);
}


static function init()
{
self::$simpleOriginal = (object)(array("root" => (object)array("a" => 1, "b" => 2)));
self::$simpleNew = (object)(array("root" => (object)array("b" => 3, "c" => 4)));
self::$original = json_decode(<<<'JSON'
{
"key1": [
4,
1,
2,
3
],
"key2": 2,
"key3": {
"sub0": 0,
"sub1": "a",
"sub2": "b"
},
"key4": [
{
"a": 1,
"b": true
},
{
"a": 2,
"b": false
},
{
"a": 3
}
]
}
JSON
);

self::$new = json_decode(<<<'JSON'
{
"key5": "wat",
"key1": [
5,
1,
2,
3
],
"key4": [
{
"c": false,
"a": 2
},
{
"a": 1,
"b": true
},
{
"c": 1,
"a": 3
}
],
"key3": {
"sub3": 0,
"sub2": false,
"sub1": "c"
}
}
JSON
);
}

}

DiffBench::init();
Empty file added phpstan.neon
Empty file.
72 changes: 44 additions & 28 deletions src/JsonDiff.php
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,11 @@ private function process($original, $new)
return is_array($new) ? $newOrdered : (object)$newOrdered;
}

/**
* @param array $original
* @param array $new
* @return array
*/
private function rearrangeArray(array $original, array $new)
{
$first = reset($original);
Expand All @@ -412,7 +417,7 @@ private function rearrangeArray(array $original, array $new)

$keyIsUnique = true;
$uniqueIdx = array();
foreach ($original as $item) {
foreach ($original as $idx => $item) {
if (!$item instanceof \stdClass) {
return $new;
}
Expand All @@ -430,7 +435,7 @@ private function rearrangeArray(array $original, array $new)
$keyIsUnique = false;
break;
}
$uniqueIdx[$value] = true;
$uniqueIdx[$value] = $idx;
}

if ($keyIsUnique) {
Expand All @@ -439,43 +444,54 @@ private function rearrangeArray(array $original, array $new)
}
}

if ($uniqueKey) {
$newIdx = array();
foreach ($new as $item) {
if (!$item instanceof \stdClass) {
return $new;
}
if (!$uniqueKey) {
return $new;
}

if (!property_exists($item, $uniqueKey)) {
return $new;
}
$newRearranged = [];
$changedItems = [];

$value = $item->$uniqueKey;
foreach ($new as $item) {
if (!$item instanceof \stdClass) {
return $new;
}

if ($value instanceof \stdClass || is_array($value)) {
return $new;
}
if (!property_exists($item, $uniqueKey)) {
return $new;
}

if (isset($newIdx[$value])) {
return $new;
}
$value = $item->$uniqueKey;

$newIdx[$value] = $item;
if ($value instanceof \stdClass || is_array($value)) {
return $new;
}

$newRearranged = array();
foreach ($uniqueIdx as $key => $item) {
if (isset($newIdx[$key])) {
$newRearranged [] = $newIdx[$key];
unset($newIdx[$key]);

if (isset($uniqueIdx[$value])) {
$idx = $uniqueIdx[$value];
// Abandon rearrangement if key is not unique in new array.
if (isset($newRearranged[$idx])) {
return $new;
}

$newRearranged[$idx] = $item;
} else {
$changedItems[] = $item;
}
foreach ($newIdx as $item) {
$newRearranged [] = $item;

$newIdx[$value] = $item;
}

$idx = 0;
foreach ($changedItems as $item) {
while (array_key_exists($idx, $newRearranged)) {
$idx++;
}
return $newRearranged;
$newRearranged[$idx] = $item;
}

return $new;
ksort($newRearranged);
$newRearranged = array_values($newRearranged);
return $newRearranged;
}
}
5 changes: 3 additions & 2 deletions src/JsonPatch.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,11 @@ public static function import(array $data)
}
$op->path = $operation->path;
if ($op instanceof OpPathValue) {
if (!array_key_exists('value', (array)$operation)) {
if (property_exists($operation, 'value')) {
$op->value = $operation->value;
} else {
throw new Exception('Missing "value" in operation data');
}
$op->value = $operation->value;
} elseif ($op instanceof OpPathFrom) {
if (!isset($operation->from)) {
throw new Exception('Missing "from" in operation data');
Expand Down
6 changes: 3 additions & 3 deletions src/JsonPointer.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,16 +136,16 @@ public static function add(&$holder, $pathItems, $value, $flags = self::RECURSIV
if ('-' === $key) {
$ref = &$ref[];
} else {
if (is_array($ref) && array_key_exists($key, $ref) && empty($pathItems)) {
array_splice($ref, $key, 0, array($value));
}
if (false === $intKey) {
if (0 === ($flags & self::TOLERATE_ASSOCIATIVE_ARRAYS)) {
throw new Exception('Invalid key for array operation');
}
$ref = &$ref[$key];
continue;
}
if (is_array($ref) && array_key_exists($key, $ref) && empty($pathItems)) {
array_splice($ref, $intKey, 0, array($value));
}
if (0 === ($flags & self::TOLERATE_ASSOCIATIVE_ARRAYS)) {
if ($intKey > count($ref) && 0 === ($flags & self::RECURSIVE_KEY_CREATION)) {
throw new Exception('Index is greater than number of items in array');
Expand Down
1 change: 1 addition & 0 deletions src/JsonValueReplace.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public function process($data)
}
}

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

if ($check) {
Expand Down
Loading