From 114edf2b7ba966db186e667d318bc4f4aafd2dad Mon Sep 17 00:00:00 2001 From: Viacheslav Poturaev Date: Fri, 19 Apr 2019 23:54:34 +0300 Subject: [PATCH] Add support for PHP classes in JsonPointer --- Makefile | 4 +- README.md | 6 ++ composer.lock | 117 +++++++++++++++++----------------- src/JsonPointer.php | 16 ++++- tests/src/JsonPointerTest.php | 61 +++++++++++++++++- 5 files changed, 139 insertions(+), 65 deletions(-) diff --git a/Makefile b/Makefile index cac1d55..08b65a5 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ phar: docker56-composer-update: test -f ./composer.phar || wget https://getcomposer.org/composer.phar - docker run -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" + 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" docker56-test: - docker run -v $$(pwd):/code php:5.6-cli bash -c "cd /code;php vendor/bin/phpunit" + docker run --rm -v $$(pwd):/code -w /code php:5.6-cli php vendor/bin/phpunit diff --git a/README.md b/README.md index ae3ec2f..b6a1322 100644 --- a/README.md +++ b/README.md @@ -231,6 +231,12 @@ $patch->apply($original); $this->assertEquals($diff->getRearranged(), $original); ``` +## PHP Classes as JSON objects + +Due to magical methods and other restrictions PHP classes can not be reliably mapped to/from JSON objects. +There is support for objects of PHP classes in `JsonPointer` with limitations: +* `null` is equal to non-existent + ## CLI tool Moved to [`swaggest/json-cli`](https://github.com/swaggest/json-cli) \ No newline at end of file diff --git a/composer.lock b/composer.lock index 2d67e17..8f8188d 100644 --- a/composer.lock +++ b/composer.lock @@ -68,16 +68,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.1.2", + "version": "1.1.4", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "46afded9720f40b9dc63542af4e3e43a1177acb0" + "reference": "558f321c52faeb4828c03e7dc0cfe39a09e09a2d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/46afded9720f40b9dc63542af4e3e43a1177acb0", - "reference": "46afded9720f40b9dc63542af4e3e43a1177acb0", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/558f321c52faeb4828c03e7dc0cfe39a09e09a2d", + "reference": "558f321c52faeb4828c03e7dc0cfe39a09e09a2d", "shasum": "" }, "require": { @@ -120,7 +120,7 @@ "ssl", "tls" ], - "time": "2018-08-08T08:57:40+00:00" + "time": "2019-01-28T09:30:10+00:00" }, { "name": "doctrine/instantiator", @@ -883,16 +883,16 @@ }, { "name": "psr/log", - "version": "1.0.2", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", "shasum": "" }, "require": { @@ -926,7 +926,7 @@ "psr", "psr-3" ], - "time": "2016-10-10T12:19:37+00:00" + "time": "2018-11-20T15:27:04+00:00" }, { "name": "satooshi/php-coveralls", @@ -987,7 +987,6 @@ "github", "test" ], - "abandoned": "php-coveralls/php-coveralls", "time": "2017-12-06T23:17:56+00:00" }, { @@ -1364,16 +1363,16 @@ }, { "name": "symfony/config", - "version": "v2.8.45", + "version": "v2.8.50", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "06c0be4cdd8363f3ec8d592c9a4d1b981d5052af" + "reference": "7dd5f5040dc04c118d057fb5886563963eb70011" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/06c0be4cdd8363f3ec8d592c9a4d1b981d5052af", - "reference": "06c0be4cdd8363f3ec8d592c9a4d1b981d5052af", + "url": "https://api.github.com/repos/symfony/config/zipball/7dd5f5040dc04c118d057fb5886563963eb70011", + "reference": "7dd5f5040dc04c118d057fb5886563963eb70011", "shasum": "" }, "require": { @@ -1417,20 +1416,20 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2018-07-26T11:13:39+00:00" + "time": "2018-11-26T09:38:12+00:00" }, { "name": "symfony/console", - "version": "v2.8.45", + "version": "v2.8.50", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "0c1fcbb9afb5cff992c982ff99c0434f0146dcfc" + "reference": "cbcf4b5e233af15cd2bbd50dee1ccc9b7927dc12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/0c1fcbb9afb5cff992c982ff99c0434f0146dcfc", - "reference": "0c1fcbb9afb5cff992c982ff99c0434f0146dcfc", + "url": "https://api.github.com/repos/symfony/console/zipball/cbcf4b5e233af15cd2bbd50dee1ccc9b7927dc12", + "reference": "cbcf4b5e233af15cd2bbd50dee1ccc9b7927dc12", "shasum": "" }, "require": { @@ -1478,20 +1477,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2018-07-26T11:13:39+00:00" + "time": "2018-11-20T15:55:20+00:00" }, { "name": "symfony/debug", - "version": "v2.8.45", + "version": "v2.8.50", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "cbb8a5f212148964efbc414838c527229f9951b7" + "reference": "74251c8d50dd3be7c4ce0c7b862497cdc641a5d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/cbb8a5f212148964efbc414838c527229f9951b7", - "reference": "cbb8a5f212148964efbc414838c527229f9951b7", + "url": "https://api.github.com/repos/symfony/debug/zipball/74251c8d50dd3be7c4ce0c7b862497cdc641a5d0", + "reference": "74251c8d50dd3be7c4ce0c7b862497cdc641a5d0", "shasum": "" }, "require": { @@ -1535,20 +1534,20 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2018-08-03T09:45:57+00:00" + "time": "2018-11-11T11:18:13+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v2.8.45", + "version": "v2.8.50", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "84ae343f39947aa084426ed1138bb96bf94d1f12" + "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/84ae343f39947aa084426ed1138bb96bf94d1f12", - "reference": "84ae343f39947aa084426ed1138bb96bf94d1f12", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a77e974a5fecb4398833b0709210e3d5e334ffb0", + "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0", "shasum": "" }, "require": { @@ -1595,20 +1594,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2018-07-26T09:03:18+00:00" + "time": "2018-11-21T14:20:20+00:00" }, { "name": "symfony/filesystem", - "version": "v2.8.45", + "version": "v2.8.50", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "0b252f4e25b7da17abb5a98eb60755b71d082c9c" + "reference": "7ae46872dad09dffb7fe1e93a0937097339d0080" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/0b252f4e25b7da17abb5a98eb60755b71d082c9c", - "reference": "0b252f4e25b7da17abb5a98eb60755b71d082c9c", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/7ae46872dad09dffb7fe1e93a0937097339d0080", + "reference": "7ae46872dad09dffb7fe1e93a0937097339d0080", "shasum": "" }, "require": { @@ -1645,20 +1644,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2018-08-07T09:12:42+00:00" + "time": "2018-11-11T11:18:13+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.9.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19" + "reference": "82ebae02209c21113908c229e9883c419720738a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", + "reference": "82ebae02209c21113908c229e9883c419720738a", "shasum": "" }, "require": { @@ -1670,7 +1669,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -1692,7 +1691,7 @@ }, { "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" + "email": "backendtea@gmail.com" } ], "description": "Symfony polyfill for ctype functions", @@ -1703,20 +1702,20 @@ "polyfill", "portable" ], - "time": "2018-08-06T14:22:27+00:00" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.9.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8" + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d0cd638f4634c16d8df4508e847f14e9e43168b8", - "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609", + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609", "shasum": "" }, "require": { @@ -1728,7 +1727,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -1762,20 +1761,20 @@ "portable", "shim" ], - "time": "2018-08-06T14:22:27+00:00" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "symfony/stopwatch", - "version": "v2.8.45", + "version": "v2.8.50", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "12a4b0c2a1788adf16a5548ab18ab9e8801d71d8" + "reference": "752586c80af8a85aeb74d1ae8202411c68836663" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/12a4b0c2a1788adf16a5548ab18ab9e8801d71d8", - "reference": "12a4b0c2a1788adf16a5548ab18ab9e8801d71d8", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/752586c80af8a85aeb74d1ae8202411c68836663", + "reference": "752586c80af8a85aeb74d1ae8202411c68836663", "shasum": "" }, "require": { @@ -1811,20 +1810,20 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2018-07-24T10:05:38+00:00" + "time": "2018-11-11T11:18:13+00:00" }, { "name": "symfony/yaml", - "version": "v2.8.45", + "version": "v2.8.50", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "fbf876678e29dc634430dcf0096e216eb0004467" + "reference": "02c1859112aa779d9ab394ae4f3381911d84052b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/fbf876678e29dc634430dcf0096e216eb0004467", - "reference": "fbf876678e29dc634430dcf0096e216eb0004467", + "url": "https://api.github.com/repos/symfony/yaml/zipball/02c1859112aa779d9ab394ae4f3381911d84052b", + "reference": "02c1859112aa779d9ab394ae4f3381911d84052b", "shasum": "" }, "require": { @@ -1861,7 +1860,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2018-07-26T09:03:18+00:00" + "time": "2018-11-11T11:18:13+00:00" } ], "aliases": [], diff --git a/src/JsonPointer.php b/src/JsonPointer.php index 1413cb3..c83c0be 100644 --- a/src/JsonPointer.php +++ b/src/JsonPointer.php @@ -98,7 +98,7 @@ public static function add(&$holder, $pathItems, $value, $flags = self::RECURSIV { $ref = &$holder; while (null !== $key = array_shift($pathItems)) { - if ($ref instanceof \stdClass) { + if ($ref instanceof \stdClass || is_object($ref)) { if (PHP_VERSION_ID < 71000 && '' === $key) { throw new Exception('Empty property name is not supported by PHP <7.1', Exception::EMPTY_PROPERTY_NAME_UNSUPPORTED); @@ -208,6 +208,12 @@ public static function get($holder, $pathItems) } else { throw new Exception('Key not found: ' . $key); } + } elseif (is_object($ref)) { + if (isset($ref->$key)) { + $ref = $ref->$key; + } else { + throw new Exception('Key not found: ' . $key); + } } else { throw new Exception('Key not found: ' . $key); } @@ -244,6 +250,12 @@ public static function remove(&$holder, $pathItems) } else { throw new Exception('Key not found: ' . $key); } + } elseif (is_object($ref)) { + if (isset($ref->$key)) { + $ref = &$ref->$key; + } else { + throw new Exception('Key not found: ' . $key); + } } else { if (array_key_exists($key, $ref)) { $ref = &$ref[$key]; @@ -254,7 +266,7 @@ public static function remove(&$holder, $pathItems) } if (isset($parent) && isset($refKey)) { - if ($parent instanceof \stdClass) { + if ($parent instanceof \stdClass || is_object($parent)) { unset($parent->$refKey); } else { unset($parent[$refKey]); diff --git a/tests/src/JsonPointerTest.php b/tests/src/JsonPointerTest.php index 9658bfd..d17a2fc 100644 --- a/tests/src/JsonPointerTest.php +++ b/tests/src/JsonPointerTest.php @@ -14,7 +14,7 @@ class JsonPointerTest extends \PHPUnit_Framework_TestCase public function testProcess() { $json = new \stdClass(); - JsonPointer::add($json, ['l1','l2','l3'], 'hello!'); + JsonPointer::add($json, ['l1', 'l2', 'l3'], 'hello!'); $this->assertSame('{"l1":{"l2":{"l3":"hello!"}}}', json_encode($json)); JsonPointer::add($json, ['l1', 'l2', 'l3'], 'hello again!', JsonPointer::SKIP_IF_ISSET); @@ -34,7 +34,7 @@ public function testProcess() $this->assertSame('Key not found: non-existent', $exception->getMessage()); } - JsonPointer::remove($json, ['l1','l2']); + JsonPointer::remove($json, ['l1', 'l2']); $this->assertSame('{"l1":{}}', json_encode($json)); JsonPointer::add($json, JsonPointer::splitPath('/l1/l2/0/0'), 0); @@ -71,4 +71,61 @@ public function testBuildPath() $this->assertSame('#/key1/~1project~1%7Busername%7D~1%7Bproject%7D/key2', JsonPointer::buildPath($pathItems, true)); } + + public function testGetSetDeleteObject() + { + $s = new Sample(); + $s->one = new Sample(); + $s->one->two = 2; + + $this->assertEquals(2, JsonPointer::get($s, ['one', 'two'])); + + + JsonPointer::add($s, ['one', 'two'], 22); + $this->assertEquals(22, JsonPointer::get($s, ['one', 'two'])); + $this->assertEquals(22, $s->one->two); + + JsonPointer::remove($s, ['one', 'two']); + try { + JsonPointer::get($s, ['one', 'two']); + $this->fail('Exception expected'); + } catch (Exception $e) { + $this->assertEquals('Key not found: two', $e->getMessage()); + } + $this->assertEquals(null, $s->one->two); + } + +} + +class Sample +{ + public $declared; + + private $_data = []; + + public function __isset($name) + { + return isset($this->_data[$name]); + } + + public function &__get($name) + { + if (array_key_exists($name, $this->_data)) { + return $this->_data[$name]; + } else { + $tmp = null; + return $tmp;; + } + } + + public function __set($name, $value) + { + $this->_data[$name] = $value; + } + + public function __unset($name) + { + unset($this->_data[$name]); + } + }