diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..3a9f913 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,43 @@ +name: build + +on: + push: + branches: [ php72 ] + pull_request: + branches: [ php72 ] + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + php: [7.2, 7.3, 7.4] + + steps: + - uses: actions/checkout@v2 + + - name: Validate dependencies + run: composer validate + + - name: Get dependency cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.json') }} + restore-keys: | + ${{ runner.os }}-php- + + - name: Install dependencies + run: composer install --prefer-dist --no-progress --no-suggest + + - name: Run static analysis + run: vendor/bin/phpstan analyse + + - name: Run test suite + run: vendor/bin/phpunit --configuration phpunit.xml.dist --coverage-clover=coverage.xml + + - uses: codecov/codecov-action@v1 \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0af1ab4..0000000 --- a/.travis.yml +++ /dev/null @@ -1,18 +0,0 @@ -language: php - -php: - - '7.2' - - '7.3' - -cache: - directories: - - vendor - -install: composer install - -script: - - vendor/bin/phpstan analyse --level 7 src - - vendor/bin/phpunit --configuration phpunit.xml.dist --coverage-clover=coverage.xml - -after_success: - - bash <(curl -s https://codecov.io/bash) diff --git a/README.md b/README.md index c471201..e8e29b2 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,15 @@ A utility for traversing PHP arrays with an XPath-like syntax. -[![travis-ci.org](https://travis-ci.org/modethirteen/XArray.php.svg?branch=master)](https://travis-ci.org/modethirteen/XArray.php) -[![codecov.io](https://codecov.io/github/modethirteen/XArray.php/coverage.svg?branch=master)](https://codecov.io/github/modethirteen/XArray.php?branch=master) +[![github.com](https://github.com/modethirteen/XArray/workflows/build/badge.svg)](https://github.com/modethirteen/XArray/actions?query=workflow%3Abuild) +[![codecov.io](https://codecov.io/github/modethirteen/XArray/coverage.svg?branch=main)](https://codecov.io/github/modethirteen/XArray?branch=main) [![Latest Stable Version](https://poser.pugx.org/modethirteen/xarray/version.svg)](https://packagist.org/packages/modethirteen/xarray) [![Latest Unstable Version](https://poser.pugx.org/modethirteen/xarray/v/unstable)](https://packagist.org/packages/modethirteen/xarray) ## Requirements -* PHP 5.4, 5.5, 5.6 (0.1.x) -* PHP 7.2+ (master, 1.x) +* PHP 7.2+ (php72, 1.x) +* PHP 7.4+ (main, 2.x) ## Installation @@ -19,7 +19,7 @@ Use [Composer](https://getcomposer.org/). There are two ways to add XArray to yo From the composer CLI: ```sh -$ ./composer.phar require modethirteen/xarray +./composer.phar require modethirteen/xarray ``` Or add modethirteen/xarray to your project's composer.json: @@ -27,14 +27,14 @@ Or add modethirteen/xarray to your project's composer.json: ```json { "require": { - "modethirteen/xarray": "dev-master" + "modethirteen/xarray": "dev-main" } } ``` -"dev-master" is the master development branch. If you are using XArray in a production environment, it is advised that you use a stable release. +`dev-main` is the main development branch. If you are using XArray in a production environment, it is advised that you use a stable release. -Assuming you have setup Composer's autoloader, XArray can be found in the modethirteen\XArray\ namespace. +Assuming you have setup Composer's autoloader, XArray can be found in the `modethirteen\XArray\` namespace. ## Usage @@ -53,7 +53,13 @@ $result = $x1->getVal('foo/bar'); // 'baz' $result = $x1->getVal('qux'); // 'fred' $results = $x1->getAll('qux'); // ['fred', 'quxx'] -// get the array +// which key paths have been defined in the XArray? +$keys = $x1->getKeys(); // ['foo', 'foo/bar', 'qux'] + +// reduce output to the key paths that have values +$flattened = $x1->toFlattenedArray(); // ['foo/bar' => 'baz', 'qux' => ['fred', 'quxx']] + +// get the array (the underlying array data structure) $array1 = $x1->toArray(); // create a new XArray from the existing array @@ -130,3 +136,91 @@ $array2 = $x->toArray(); // MutableXArray mutates the source array assert($array1 === $array2); ``` + +### SchemaLockedArray.php (extends XArray.php) + +```php +// SchemaLockedArray will block the setting of any values if the key path is not allowlisted in a schema +$x = new SchemaLockedArray(new SchemaBuilder()); + +// throws SchemaLockedArrayUndefinedKeyException +$x->setVal('foo', 'bar'); + +// a schema can be built with a fluent API +$schemaBuilder = (new SchemaBuilder()) + ->with('foo') + + // also allowlists bar + ->with('bar/baz') + + // also allowlists plugh and plugh/xyzzy + ->with('plugh/xyzzy/fred'); + +// a schema can also be inferred from another XArray by analyzing the array's defined key paths +$x = new XArray([ + 'foo' => 'qux', + 'bar' => [ + 'baz' => true + ], + 'plugh' => [ + 'xyzzy' => [ + 'fred' => [ + 'sodium', + 'iodine' + ] + ] + ] +]); +$schemaBuilder = SchemaBuilder::newFromXArray($x); + +// either way, the SchemaLockedArray will only ever have the key paths that are defined in the schema +$x = new SchemaLockedArray($schemaBuilder); + +// ...leading to a very predictable array tree structure when outputted +$array = $x->toArray(); +``` + +### JsonArray.php (extends XArray.php) + +```php +// Like XArray, JsonArray can be built from a source array +$array = [ + 'foo' => [ + 'bar', + 'baz' + ] +]; +$x = new JsonArray($array); + +// JsonArray can also be built from a JSON string +$json = <<withUnescapedSlashes() + ->withPrettyPrint() + ->toJson(); +``` diff --git a/composer.json b/composer.json index 50428d6..f0b8809 100644 --- a/composer.json +++ b/composer.json @@ -10,16 +10,16 @@ "role": "maintainer" } ], - "minimum-stability": "dev", "support": { "issues": "https://github.com/modethirteen/XArray.php/issues" }, "require": { - "php": ">=7.2.0" + "php": ">=7.2.0", + "ext-json": "*" }, "require-dev": { - "phpstan/phpstan": "0.11.1", - "phpunit/phpunit": "7.4.3" + "phpstan/phpstan": "~0.12", + "phpunit/phpunit": "~7.4.3" }, "autoload": { "psr-4": { @@ -29,7 +29,7 @@ }, "autoload-dev": { "psr-4": { - "modethirteen\\XArray\\tests\\": ["tests/"] + "modethirteen\\XArray\\Tests\\": ["tests/"] } } } diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..ed64646 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,7 @@ +parameters: + bootstrapFiles: + - %currentWorkingDirectory%/_bootstrap.php + checkMissingIterableValueType: false + level: 7 + paths: + - src \ No newline at end of file diff --git a/src/Exception/SchemaLockedArrayUndefinedKeyException.php b/src/Exception/SchemaLockedArrayUndefinedKeyException.php new file mode 100755 index 0000000..84e0a90 --- /dev/null +++ b/src/Exception/SchemaLockedArrayUndefinedKeyException.php @@ -0,0 +1,30 @@ +isPrettyPrintEnabled) { + $options = $options | JSON_PRETTY_PRINT; + } + if($this->isUnescapedSlashesEnabled) { + $options = $options | JSON_UNESCAPED_SLASHES; + } + $result = $options > 0 ? json_encode($this->array, $options) : json_encode($this->array); + return is_string($result) ? $result : '{}'; + } + + /** + * Remove forward slash escaping with serializing to JSON text + * + * @note slashes should always remain escaped if JSON is embedded in, or contains, HTML + * @return JsonArray + */ + public function withUnescapedSlashes() : JsonArray { + + // even though this is a clone, we should not create a new array reference - there is no change to the underlying array data + $instance = clone $this; + $instance->isUnescapedSlashesEnabled = true; + return $instance; + } + + /** + * Add line spacing and indentation when serializing to JSON text + * + * @note Even though this is a clone, we should not create a new array reference - there is no change to the underlying array data + * @return JsonArray + */ + public function withPrettyPrint() : JsonArray { + + // even though this is a clone, we should not create a new array reference - there is no change to the underlying array data + $instance = clone $this; + $instance->isPrettyPrintEnabled = true; + return $instance; + } +} diff --git a/src/SchemaBuilder.php b/src/SchemaBuilder.php new file mode 100644 index 0000000..a8fa21d --- /dev/null +++ b/src/SchemaBuilder.php @@ -0,0 +1,72 @@ +getKeys() as $key) { + $schema->data->setVal($key, true); + } + return $schema; + } + + /** + * @var XArray + */ + private $data; + + public function __construct() { + $this->data = new XArray(); + } + + /** + * @note This is a fluent interface that will mutate the schema under construction and return the builder instance + * @param string $key + * @return SchemaBuilder + */ + public function with(string $key) : SchemaBuilder { + $this->data->setVal($key, true); + return $this; + } + + /** + * @return XArray + */ + public function getSchema() : XArray { + return $this->data; + } +} diff --git a/src/SchemaLockedArray.php b/src/SchemaLockedArray.php new file mode 100755 index 0000000..8d3d249 --- /dev/null +++ b/src/SchemaLockedArray.php @@ -0,0 +1,49 @@ +schema = $builder->getSchema(); + parent::__construct([]); + } + + /** + * Set or replace a key value + * + * @param string $key + * @param mixed $value + * @throws SchemaLockedArrayUndefinedKeyException + */ + public function setVal(string $key, $value = null) : void { + if(!in_array($key, $this->schema->getKeys())) { + throw new SchemaLockedArrayUndefinedKeyException($key); + } + parent::setVal($key, $value); + } +} diff --git a/src/XArray.php b/src/XArray.php index f4a8080..ed4b1db 100644 --- a/src/XArray.php +++ b/src/XArray.php @@ -16,6 +16,8 @@ */ namespace modethirteen\XArray; +use Closure; + /** * Class XArray - get/set accessors for arrays * @@ -23,6 +25,44 @@ */ class XArray { + /** + * @param array $array + * @param string $key - the array path to return, i.e. /pages/content + * @param bool $isArrayReturnAllowed - can return array or first element of array + * @param mixed $default - if the key is not found, this value will be returned + * @return mixed|null + * @noinspection PhpMissingReturnTypeInspection + */ + private static function getValHelper(array $array, string $key, bool $isArrayReturnAllowed, $default) { + if($key === '') { + return $default; + } + $keys = explode('/', $key); + $count = count($keys); + $i = 0; + foreach($keys as $k => $val) { + $i++; + if($val === '') { + continue; + } + if(isset($array[$val]) && !is_array($array[$val])) { + if($array[$val] !== null && $i === $count) { + return $array[$val]; + } + return $default; + } + if(isset($array[$val])) { + $array = $array[$val]; + } else { + return $default; + } + if(!$isArrayReturnAllowed && is_array($array) && key($array) === 0) { + $array = current($array); + } + } + return $array; + } + /** * Get string representation of value * @@ -30,10 +70,24 @@ class XArray { * @return string */ private static function getStringValue($value) : string { + if($value === null) { + return ''; + } + if(is_string($value)) { + return $value; + } if(is_bool($value)) { - return $value === true ? 'true' : 'false'; + return $value ? 'true' : 'false'; + } + if(is_array($value)) { + return implode(',', array_map(function($v) { + return self::getStringValue($v); + }, $value)); + } + if($value instanceof Closure) { + return strval($value()); } - return !is_string($value) ? strval($value) : $value; + return strval($value); } /** @@ -48,7 +102,7 @@ private static function setValHelper(array &$array, string $key, $value) : void $i = 0; foreach($keys as $key) { $i++; - if($i == $count) { + if($i === $count) { if($value === null) { unset($array[$key]); return; @@ -57,8 +111,13 @@ private static function setValHelper(array &$array, string $key, $value) : void } else if(!isset($array[$key])) { $array[$key] = []; } - if(is_string($array[$key])) { - return; + if(!is_array($array[$key]) ) { + if($i === $count) { + + // we're at a leaf path segment with no intention to replace the segment with a collection + return; + } + $array[$key] = []; } $array = &$array[$key]; } @@ -70,14 +129,24 @@ private static function setValHelper(array &$array, string $key, $value) : void protected $array = []; /** - * @param array $array - array values to create XArray from. If not supplied, XArray will start empty + * Lazy initialized list of array keys is reset whenever array is mutated + * + * @var array|null */ - public function __construct(array $array = null) { $this->array = $array !== null ? $array : []; } + private $keys = null; + + /** + * @param array|null $array $array - array values to create XArray from. If not supplied, XArray will start empty + */ + public function __construct(array $array = null) { + $this->array = $array !== null ? $array : []; + } /** * Find $key in the XArray, which is delimited by / - * If the found value is itself an array of multiple values, the array is returned. - * If the found value is a single value, it is wrapped in an array then returned. + * + * If the found value is itself an array of multiple values, the array is returned + * If the found value is a single value, it is wrapped in an array then returned * * @param string $key - the array path to return, i.e. /pages/content * @param array|null $default - if the key is not found, this array or null will be returned @@ -103,7 +172,7 @@ public function getAll(string $key = '', ?array $default = []) : ?array { return [$array[$val]]; } $array = $array[$val]; - if($i == $count) { + if($i === $count) { if(key($array) !== 0) { $array = [$array]; } @@ -112,47 +181,35 @@ public function getAll(string $key = '', ?array $default = []) : ?array { return $array; } + /** + * Retrieve all possible key paths in the array + * + * @return string[] + */ + public function getKeys() : array { + if($this->keys === null) { + $this->keys = $this->getKeysHelper('', $this->array); + } + return $this->keys; + } + /** * Find $key in the XArray, which is delimited by / - * If the found value is itself an array of multiple values, it will return the value of array key 0. + * + * If the found value is itself an array of multiple values, it will return the value of array key 0 * * @param string $key - the array path to return, i.e. /pages/content * @param mixed $default - if the key is not found, this value will be returned * @return mixed|null + * @noinspection PhpMissingReturnTypeInspection */ public function getVal(string $key, $default = null) { - $array = $this->array; - if($key === '') { - return $default; - } - $keys = explode('/', $key); - $count = count($keys); - $i = 0; - foreach($keys as $k => $val) { - $i++; - if($val === '') { - continue; - } - if(isset($array[$val]) && !is_array($array[$val])) { - if($array[$val] !== null && $i === $count) { - return $array[$val]; - } - return $default; - } - if(isset($array[$val])) { - $array = $array[$val]; - } else { - return $default; - } - if(is_array($array) && key($array) === 0) { - $array = current($array); - } - } - return $array; + return self::getValHelper($this->array, $key, false, $default); } /** * Find $key in the XArray, which is delimited by / + * * If the found value is itself an array of multiple values, it will return the value of array key 0 * * @param string $key - the array path to return, i.e. /pages/content @@ -164,18 +221,23 @@ public function getString(string $key, string $default = '') : string { } /** - * Set or replace a key value. + * Set or replace a key value * * @param string $key * @param mixed $value * @return void */ - public function setVal(string $key, $value = null) : void { $this->setValHelper($this->array, $key, $value); } + public function setVal(string $key, $value = null) : void { + self::setValHelper($this->array, $key, $value); + + // array has been mutated, reset key path cache + $this->keys = null; + } /** * Return the array as an XML string * - * @param string $outer - optional output tag, used for recursion + * @param string|null $outer - optional output tag, used for recursion * @return string - xml string representation of the array */ public function toXml(string $outer = null) : string { @@ -227,5 +289,52 @@ public function toXml(string $outer = null) : string { * * @return array */ - public function toArray() : array { return $this->array; } + public function toArray() : array { + return $this->array; + } + + /** + * Returns a collection of mapped key paths to values + * + * @return array + */ + public function toFlattenedArray() : array { + $values = []; + foreach($this->getKeys() as $key) { + $value = self::getValHelper($this->array, $key, true, null); + if(is_array($value) && count(array_filter(array_keys($value), 'is_string')) > 0) { + + // a collection that has string indices: it is safe assumption that it is key path segment + continue; + } + $values[$key] = $value; + } + return $values; + } + + /** + * Internal helper to walk array recursively, building a list of xarray keys + * + * @param string $key + * @param array $array + * @return string[] + */ + protected function getKeysHelper(string $key, array $array) : array { + $keys = []; + foreach($array as $k => $val) { + if(!is_string($k)) { + + // keys are array path segments, it's safe to assume non-string keys are indexes for simple collections + continue; + } + if($key !== null && $key !== '') { + $k = "{$key}/{$k}"; + } + $keys[] = $k; + if(is_array($val)) { + $keys = array_merge($keys, $this->getKeysHelper($k, $val)); + } + } + return $keys; + } } \ No newline at end of file diff --git a/tests/JsonArray/__ctor_Test.php b/tests/JsonArray/__ctor_Test.php new file mode 100644 index 0000000..bb7f3e0 --- /dev/null +++ b/tests/JsonArray/__ctor_Test.php @@ -0,0 +1,39 @@ + 'bar']; + + // act + $x = new JsonArray($source); + $x->setVal('foo', 'baz'); + + // assert + $this->assertNotSame($source, $x->toArray()); + } +} diff --git a/tests/JsonArray/getAll_Test.php b/tests/JsonArray/getAll_Test.php new file mode 100644 index 0000000..6fc3022 --- /dev/null +++ b/tests/JsonArray/getAll_Test.php @@ -0,0 +1,27 @@ +assertEquals([ + 'shown' => [ + 'coffee' => [ + 'once' => false, + 'previous' => 1334548570, + 'lady' => 'stems', + 'planned' => 216983651.27143478, + 'alike' => 'verb', + 'can' => 'creature' + ], + 'dance' => false, + 'pick' => true, + 'diagram' => 1635807415.745319, + 'return' => true, + 'worry' => false + ], + 'piece' => false, + 'noise' => 'book', + 'pipe' => 654136905.9745188, + 'unknown' => 'led', + 'moment' => false + ], $x->toArray()); + } +} \ No newline at end of file diff --git a/tests/JsonArray/setVal_Test.php b/tests/JsonArray/setVal_Test.php new file mode 100644 index 0000000..fce56c4 --- /dev/null +++ b/tests/JsonArray/setVal_Test.php @@ -0,0 +1,43 @@ + ['bar' => 'baz']]; + $x = new JsonArray($array); + + // act + $x->setVal('qux', 'fred'); + + // assert + $this->assertEquals(['foo' => ['bar' => 'baz']], $array); + } +} \ No newline at end of file diff --git a/tests/JsonArray/toArray_Test.php b/tests/JsonArray/toArray_Test.php new file mode 100644 index 0000000..b101e32 --- /dev/null +++ b/tests/JsonArray/toArray_Test.php @@ -0,0 +1,27 @@ + [false, false, '{"foo":{"bar":["\/\/baz","qux"]},"plugh":"xyzzy"}'], + 'with pretty print and without unescaped slashes' => [true, false, << [false, true, '{"foo":{"bar":["//baz","qux"]},"plugh":"xyzzy"}'], + 'with pretty print and with unescaped slashes' => [true, true, << [ + 'bar' => [ + "//baz", + 'qux' + ] + ], + 'plugh' => 'xyzzy' + ]); + if($withPrettyPrint) { + $x = $x->withPrettyPrint(); + } + if($withUnescapedSlashes) { + $x = $x->withUnescapedSlashes(); + } + + // act + $result = $x->toJson(); + + // assert + static::assertEquals($expected, $result); + } +} diff --git a/tests/JsonArray/toXml_Test.php b/tests/JsonArray/toXml_Test.php new file mode 100644 index 0000000..79c2267 --- /dev/null +++ b/tests/JsonArray/toXml_Test.php @@ -0,0 +1,27 @@ + 'bar']; @@ -36,19 +36,4 @@ public function Constructor_holds_reference_to_source_array() { // assert $this->assertSame($source, $x->toArray()); } - - /** - * @test - */ - public function Can_construct_compatibility_class() { - - // arrange - $source = ['foo' => 'bar']; - - // act - $x = new \MindTouch\XArray\MutableXArray($source); - - // assert - $this->assertTrue(is_subclass_of($x, MutableXArray::class)); - } } \ No newline at end of file diff --git a/tests/MutableXArray/getAll_Test.php b/tests/MutableXArray/getAll_Test.php index 1e7b640..50d07ff 100644 --- a/tests/MutableXArray/getAll_Test.php +++ b/tests/MutableXArray/getAll_Test.php @@ -14,11 +14,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -namespace modethirteen\XArray\tests\MutableXArray; +namespace modethirteen\XArray\Tests\MutableXArray; use modethirteen\XArray\MutableXArray; -class getAll_Test extends \modethirteen\XArray\tests\XArrayBase\getAll_Test { +class getAll_Test extends \modethirteen\XArray\Tests\XArrayBase\getAll_Test { /** * @var string diff --git a/tests/MutableXArray/getKeys_Test.php b/tests/MutableXArray/getKeys_Test.php new file mode 100644 index 0000000..c9854fe --- /dev/null +++ b/tests/MutableXArray/getKeys_Test.php @@ -0,0 +1,53 @@ + [ + 'bar' => 'baz' + ] + ]; + $x = new MutableXArray($source); + + // act + $source['qux'] = ['plugh' => 'xyzzy']; + $result = $x->getKeys(); + + // assert + static::assertEquals([ + 'foo', + 'foo/bar', + 'qux', + 'qux/plugh' + ], $result); + } +} \ No newline at end of file diff --git a/tests/MutableXArray/getString_Test.php b/tests/MutableXArray/getString_Test.php index e62c69c..2059749 100644 --- a/tests/MutableXArray/getString_Test.php +++ b/tests/MutableXArray/getString_Test.php @@ -14,11 +14,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -namespace modethirteen\XArray\tests\MutableXArray; +namespace modethirteen\XArray\Tests\MutableXArray; use modethirteen\XArray\MutableXArray; -class getString_Test extends \modethirteen\XArray\tests\XArrayBase\getString_Test { +class getString_Test extends \modethirteen\XArray\Tests\XArrayBase\getString_Test { /** * @var string diff --git a/tests/MutableXArray/getVal_Test.php b/tests/MutableXArray/getVal_Test.php index eaba8a4..4d59808 100644 --- a/tests/MutableXArray/getVal_Test.php +++ b/tests/MutableXArray/getVal_Test.php @@ -14,11 +14,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -namespace modethirteen\XArray\tests\MutableXArray; +namespace modethirteen\XArray\Tests\MutableXArray; use modethirteen\XArray\MutableXArray; -class getVal_Test extends \modethirteen\XArray\tests\XArrayBase\getVal_Test { +class getVal_Test extends \modethirteen\XArray\Tests\XArrayBase\getVal_Test { /** * @var string diff --git a/tests/MutableXArray/setVal_Test.php b/tests/MutableXArray/setVal_Test.php index 129cef6..3329ece 100644 --- a/tests/MutableXArray/setVal_Test.php +++ b/tests/MutableXArray/setVal_Test.php @@ -14,11 +14,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -namespace modethirteen\XArray\tests\MutableXArray; +namespace modethirteen\XArray\Tests\MutableXArray; use modethirteen\XArray\MutableXArray; -class setVal_Test extends \modethirteen\XArray\tests\XArrayBase\setVal_Test { +class setVal_Test extends \modethirteen\XArray\Tests\XArrayBase\setVal_Test { /** * @var string @@ -28,14 +28,14 @@ class setVal_Test extends \modethirteen\XArray\tests\XArrayBase\setVal_Test { /** * @test */ - public function Can_mutate_original_array() { + public function Can_mutate_original_array() : void { // arrange $array = ['foo' => ['bar' => 'baz']]; - $X = new MutableXArray($array); + $x = new MutableXArray($array); // act - $X->setVal('qux', 'fred'); + $x->setVal('qux', 'fred'); // assert $this->assertEquals(['foo' => ['bar' => 'baz'], 'qux' => 'fred'], $array); diff --git a/tests/MutableXArray/toArray_Test.php b/tests/MutableXArray/toArray_Test.php index cf9d19b..4d063a3 100644 --- a/tests/MutableXArray/toArray_Test.php +++ b/tests/MutableXArray/toArray_Test.php @@ -14,11 +14,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -namespace modethirteen\XArray\tests\MutableXArray; +namespace modethirteen\XArray\Tests\MutableXArray; use modethirteen\XArray\MutableXArray; -class toArray_Test extends \modethirteen\XArray\tests\XArrayBase\toArray_Test { +class toArray_Test extends \modethirteen\XArray\Tests\XArrayBase\toArray_Test { /** * @var string diff --git a/tests/MutableXArray/toFlattenedArray_Test.php b/tests/MutableXArray/toFlattenedArray_Test.php new file mode 100644 index 0000000..dd8d9d0 --- /dev/null +++ b/tests/MutableXArray/toFlattenedArray_Test.php @@ -0,0 +1,27 @@ + [ + 'foo', + 'bar', + ['foo' => 'bar'] + ], + 'string level two' => [ + 'foo/bar', + 'baz', + ['foo' => ['bar' => 'baz']] + ], + 'string level three' => [ + 'foo/bar/baz', + 'qux', + ['foo' => ['bar' => ['baz' => 'qux']]] + ], + 'array level one' => [ + 'foo', + ['bar', 'baz'], + ['foo' => ['bar', 'baz']] + ], + 'array level two' => [ + 'foo/bar', + ['baz', 'qux'], + ['foo' => ['bar' => ['baz', 'qux']]] + ], + 'array level three' => [ + 'foo/bar/baz', + ['qux', 'fred'], + ['foo' => ['bar' => ['baz' => ['qux', 'fred']]]] + ], + 'null level one' => [ + 'foo', + null, + [] + ], + 'null level two' => [ + 'foo/bar', + null, + ['foo' => []] + ], + 'null level three' => [ + 'foo/bar/baz', + null, + ['foo' => ['bar' => []]] + ], + 'bool level one' => [ + 'foo', + true, + ['foo' => true] + ], + 'bool level two' => [ + 'foo/bar', + true, + ['foo' => ['bar' => true]] + ], + 'bool level three' => [ + 'foo/bar/baz', + true, + ['foo' => ['bar' => ['baz' => true]]] + ], + ]; + } + + /** + * @test + * @dataProvider key_value_expected_Provider + * @param string $key + * @param mixed $value + * @param array $expected + * @throws SchemaLockedArrayUndefinedKeyException + */ + public function Can_set_value_for_allowed_keys(string $key, $value, array $expected) : void { + + // arrange + $schema = (new SchemaBuilder()) + ->with($key); + $x = new SchemaLockedArray($schema); + + // act + $x->setVal($key, $value); + + // assert + $this->assertEquals($expected, $x->toArray()); + } + + /** + * @test + */ + public function Cannot_set_value_for_key() : void { + + // assert + static::expectException(SchemaLockedArrayUndefinedKeyException::class); + + // arrange + $schema = (new SchemaBuilder()) + ->with('foo'); + $x = new SchemaLockedArray($schema); + + // act + $x->setVal('bar', 'baz'); + } +} \ No newline at end of file diff --git a/tests/SchemaLockedArray/toArray_Test.php b/tests/SchemaLockedArray/toArray_Test.php new file mode 100644 index 0000000..c5e6c3a --- /dev/null +++ b/tests/SchemaLockedArray/toArray_Test.php @@ -0,0 +1,27 @@ + 'bar']; @@ -36,16 +36,4 @@ public function Constructor_does_not_hold_reference_to_source_array() { // assert $this->assertNotSame($source, $x->toArray()); } - - /** - * @test - */ - public function Can_construct_compatibility_class() { - - // act - $x = new \MindTouch\XArray\XArray(); - - // assert - $this->assertTrue(is_subclass_of($x, XArray::class)); - } } \ No newline at end of file diff --git a/tests/XArray/getAll_Test.php b/tests/XArray/getAll_Test.php index 8bfadbc..a099d1a 100644 --- a/tests/XArray/getAll_Test.php +++ b/tests/XArray/getAll_Test.php @@ -14,11 +14,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -namespace modethirteen\XArray\tests\XArray; +namespace modethirteen\XArray\Tests\XArray; use modethirteen\XArray\XArray; -class getAll_Test extends \modethirteen\XArray\tests\XArrayBase\getAll_Test { +class getAll_Test extends \modethirteen\XArray\Tests\XArrayBase\getAll_Test { /** * @var string diff --git a/tests/XArray/getKeys_Test.php b/tests/XArray/getKeys_Test.php new file mode 100644 index 0000000..a196ef7 --- /dev/null +++ b/tests/XArray/getKeys_Test.php @@ -0,0 +1,27 @@ + ['bar' => 'baz']]; - $X = new XArray($array); + $x = new XArray($array); // act - $X->setVal('qux', 'fred'); + $x->setVal('qux', 'fred'); // assert $this->assertEquals(['foo' => ['bar' => 'baz']], $array); diff --git a/tests/XArray/toArray_Test.php b/tests/XArray/toArray_Test.php index ab205fb..b5d427b 100644 --- a/tests/XArray/toArray_Test.php +++ b/tests/XArray/toArray_Test.php @@ -14,11 +14,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -namespace modethirteen\XArray\tests\XArray; +namespace modethirteen\XArray\Tests\XArray; use modethirteen\XArray\XArray; -class toArray_Test extends \modethirteen\XArray\tests\XArrayBase\toArray_Test { +class toArray_Test extends \modethirteen\XArray\Tests\XArrayBase\toArray_Test { /** * @var string diff --git a/tests/XArray/toFlattenedArray_Test.php b/tests/XArray/toFlattenedArray_Test.php new file mode 100644 index 0000000..e635274 --- /dev/null +++ b/tests/XArray/toFlattenedArray_Test.php @@ -0,0 +1,27 @@ +toFlattenedArray() as $key => $value) { + try { + $x->setVal($key, $value); + } catch(SchemaLockedArrayUndefinedKeyException $e) { + static::fail($e->getMessage()); + } + } + return $x; } } \ No newline at end of file diff --git a/tests/XArrayBase/getAll_Test.php b/tests/XArrayBase/getAll_Test.php index d02f177..efb142d 100644 --- a/tests/XArrayBase/getAll_Test.php +++ b/tests/XArrayBase/getAll_Test.php @@ -14,48 +14,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -namespace modethirteen\XArray\tests\XArrayBase; +namespace modethirteen\XArray\Tests\XArrayBase; -abstract class getAll_Test extends XArrayUnitTestCaseBase { - - /** - * @test - * @dataProvider source_xpath_expected_Provider - * @param array $source - * @param string $xpath - * @param array $expected - */ - public function Can_get_all_values(array $source, string $xpath, array $expected) { - - // arrange - $x = $this->newXArray($source); - - // act - $result = $x->getAll($xpath); - - // assert - $this->assertEquals($expected, $result); - } +use modethirteen\XArray\SchemaLockedArray; - /** - * @test - */ - public function Can_get_null_default_value() { - - // arrange - $x = $this->newXArray(['foo' => ['bar', 'baz']]); - - // act - $result = $x->getAll('qux', null); +abstract class getAll_Test extends XArrayUnitTestCaseBase { - // assert - $this->assertNull($result); - } - /** * @return array */ - public static function source_xpath_expected_Provider() : array { + public static function source_key_expected_Provider() : array { return [ 'empty level zero' => [ [], @@ -116,7 +84,68 @@ public static function source_xpath_expected_Provider() : array { ['foo' => ['bar' => ['baz' => ['qux', 'fred']]]], 'foo/bar/baz', ['qux', 'fred'] + ], + 'array of arrays level zero' => [ + [['foo', 'bar'], ['baz'], ['qux']], + '', + + // cannot setup schema locked array with values without an allowlisted key path + static::$class === SchemaLockedArray::class ? [] : [['foo', 'bar'], ['baz'], ['qux']] + ], + 'array of arrays level one' => [ + ['plugh' => [['foo', 'bar'], ['baz'], ['qux']]], + 'plugh', + [['foo', 'bar'], ['baz'], ['qux']] + ], + 'array of arrays level two' => [ + ['plugh' => ['xyzzy' => [['foo', 'bar'], ['baz'], ['qux']]]], + 'plugh/xyzzy', + [['foo', 'bar'], ['baz'], ['qux']] + ], + 'array of arrays level three' => [ + ['plugh' => ['xyzzy' => ['ogre' => [['foo', 'bar'], ['baz'], ['qux']]]]], + 'plugh/xyzzy/ogre', + [['foo', 'bar'], ['baz'], ['qux']] + ], + 'extra preceding key path segment' => [ + ['foo' => ['bar' => 'baz']], + '/foo/bar', + ['baz'] ] ]; } + + /** + * @test + * @dataProvider source_key_expected_Provider + * @param array $source + * @param string $key + * @param array $expected + */ + public function Can_get_all_values(array $source, string $key, array $expected) : void { + + // arrange + $x = $this->newXArray($source); + + // act + $result = $x->getAll($key); + + // assert + $this->assertEquals($expected, $result); + } + + /** + * @test + */ + public function Can_get_array_default_value() : void { + + // arrange + $x = $this->newXArray(['foo' => ['bar', 'baz']]); + + // act + $result = $x->getAll('qux'); + + // assert + $this->assertEquals([], $result); + } } \ No newline at end of file diff --git a/tests/XArrayBase/getKeys_Test.php b/tests/XArrayBase/getKeys_Test.php new file mode 100644 index 0000000..3c4acb7 --- /dev/null +++ b/tests/XArrayBase/getKeys_Test.php @@ -0,0 +1,116 @@ + [ + [], + 'foo', + ['foo'] + ], + 'empty level two' => [ + ['foo' => ''], + 'foo/bar', + ['foo', 'foo/bar'] + ], + 'empty level three' => [ + ['foo' => ['bar' => '']], + 'foo/bar/baz', + ['foo', 'foo/bar', 'foo/bar/baz'] + ], + 'replace string level one' => [ + ['qux' => 'fred', 'foo' => 'bar'], + 'foo', + ['qux', 'foo'] + ], + 'replace string level two' => [ + ['qux' => 'fred', 'foo' => ['bar' => 'baz']], + 'foo/bar', + ['qux', 'foo', 'foo/bar'] + ], + 'replace string level three' => [ + ['qux' => 'fred', 'foo' => ['bar' => ['baz' => 'qux']]], + 'foo/bar/baz', + ['qux', 'foo', 'foo/bar', 'foo/bar/baz'] + ], + 'replace array level one' => [ + ['qux' => 'fred', 'foo' => ['bar', 'baz']], + 'foo', + ['qux', 'foo'] + ], + 'replace array level two' => [ + ['qux' => 'fred', 'foo' => ['bar' => ['baz', 'qux']]], + 'foo/bar', + ['qux', 'foo', 'foo/bar'] + ], + 'replace array level three' => [ + ['qux' => 'fred', 'foo' => ['bar' => ['baz' => ['qux', 'fred']]]], + 'foo/bar/baz', + ['qux', 'foo', 'foo/bar', 'foo/bar/baz'] + ], + 'new string level one' => [ + ['qux' => 'fred', 'foo' => 'bar'], + 'plugh', + ['qux', 'foo', 'plugh'] + ], + 'new string level two' => [ + ['qux' => 'fred', 'foo' => ['bar' => 'baz']], + 'plugh/xyzzy', + ['qux', 'foo', 'foo/bar', 'plugh', 'plugh/xyzzy'] + ], + 'new string level three' => [ + ['qux' => 'fred', 'foo' => ['bar' => ['baz' => 'qux']]], + 'plugh/xyzzy/fred', + ['qux', 'foo', 'foo/bar', 'foo/bar/baz', 'plugh', 'plugh/xyzzy', 'plugh/xyzzy/fred'] + ], + 'branching keys' => [ + ['qux' => 'fred', 'foo' => ['bar' => ['baz' => ['qux', 'fred']]]], + 'foo/plugh/xyzzy', + ['qux', 'foo', 'foo/bar', 'foo/bar/baz', 'foo/plugh', 'foo/plugh/xyzzy'] + ] + ]; + } + + /** + * @test + * @dataProvider source_key_expected_Provider + * @param array $source + * @param string $key + * @param array $expected + */ + public function Can_get_keys(array $source, string $key, array $expected): void { + + // arrange + $x = $this->newXArray($source, SchemaBuilder::newFromXArray(new XArray($source))->with($key)); + + // act + $x->setVal($key, 'abcdf'); + $result = $x->getKeys(); + + // assert + $this->assertEquals($expected, $result); + } +} \ No newline at end of file diff --git a/tests/XArrayBase/getString_Test.php b/tests/XArrayBase/getString_Test.php index c2d0f5c..ee371ae 100644 --- a/tests/XArrayBase/getString_Test.php +++ b/tests/XArrayBase/getString_Test.php @@ -14,63 +14,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -namespace modethirteen\XArray\tests\XArrayBase; +namespace modethirteen\XArray\Tests\XArrayBase; abstract class getString_Test extends XArrayUnitTestCaseBase { - /** - * @test - * @dataProvider source_xpath_expected_Provider - * @param array $source - * @param string $xpath - * @param string $expected - */ - public function Can_get_string_value(array $source, string $xpath, $expected) { - - // arrange - $x = $this->newXArray($source); - - // act - $result = $x->getString($xpath); - - // assert - $this->assertEquals($expected, $result); - } - - /** - * @test - */ - public function Can_get_default() { - - // arrange - $x = $this->newXArray(['foo' => 'bar']); - - // act - $result = $x->getString('qux', 'fred'); - - // assert - $this->assertEquals('fred', $result); - } - - /** - * @test - */ - public function Empty_key_returns_empty_string() { - - // arrange - $x = $this->newXArray(['foo' => 'bar']); - - // act - $result = $x->getString(''); - - // assert - $this->assertEquals('', $result); - } - /** * @return array */ - public static function source_xpath_expected_Provider() : array { + public static function source_key_expected_Provider() : array { return [ 'empty level one' => [ [], @@ -102,7 +53,7 @@ public static function source_xpath_expected_Provider() : array { 'foo/bar/baz', 'qux', ], - + // XArray::getVal(...) only gets first element of array value 'array level one' => [ ['foo' => ['bar', 'baz']], @@ -119,9 +70,49 @@ public static function source_xpath_expected_Provider() : array { 'foo/bar/baz', 'qux' ], + 'array of arrays level zero' => [ + [['foo', 'bar'], ['baz'], ['qux']], + '', + '' + ], + 'array of arrays level one' => [ + ['plugh' => [['foo', 'bar'], ['baz'], ['qux']]], + 'plugh', + 'foo,bar' + ], + 'array of arrays level two' => [ + ['plugh' => ['xyzzy' => [['foo', 'bar'], ['baz'], ['qux']]]], + 'plugh/xyzzy', + 'foo,bar' + ], + 'array of arrays level three' => [ + ['plugh' => ['xyzzy' => ['ogre' => [['foo', 'bar'], ['baz'], ['qux']]]]], + 'plugh/xyzzy/ogre', + 'foo,bar' + ], + 'array of arrays level four' => [ + ['plugh' => ['xyzzy' => ['ogre' => ['nivek' => [['foo', 'bar'], ['baz'], ['qux']]]]]], + 'plugh/xyzzy/ogre/nivek', + 'foo,bar' + ], + 'array of arrays level two with level one key' => [ + ['plugh' => ['xyzzy' => [['foo', 'bar'], ['baz'], ['qux']]]], + 'plugh', + 'foo,bar,baz,qux' + ], + 'array of arrays level three with level two key' => [ + ['plugh' => ['xyzzy' => ['ogre' => [['foo', 'bar'], ['baz'], ['qux']]]]], + 'plugh/xyzzy', + 'foo,bar,baz,qux' + ], + 'array of arrays level four with level three key' => [ + ['plugh' => ['xyzzy' => ['ogre' => ['nivek' => [['foo', 'bar'], ['baz'], ['qux']]]]]], + 'plugh/xyzzy/ogre', + 'foo,bar,baz,qux' + ], 'object with __toString' => [ ['foo' => new class { - public function __toString() { + public function __toString() : string { return 'asdf'; } }], @@ -147,7 +138,68 @@ public function __toString() { ['foo' => 1.23], 'foo', '1.23' + ], + 'function' => [ + ['foo' => function() { + return 'qux'; + }], + 'foo', + 'qux' + ], + 'null' => [ + ['foo' => null], + 'foo', + '' ] ]; } + + /** + * @test + * @dataProvider source_key_expected_Provider + * @param array $source + * @param string $key + * @param string $expected + */ + public function Can_get_string_value(array $source, string $key, string $expected) : void { + + // arrange + $x = $this->newXArray($source); + + // act + $result = $x->getString($key); + + // assert + $this->assertEquals($expected, $result); + } + + /** + * @test + */ + public function Can_get_default() : void { + + // arrange + $x = $this->newXArray(['foo' => 'bar']); + + // act + $result = $x->getString('qux', 'fred'); + + // assert + $this->assertEquals('fred', $result); + } + + /** + * @test + */ + public function Empty_key_returns_empty_string() : void { + + // arrange + $x = $this->newXArray(['foo' => 'bar']); + + // act + $result = $x->getString(''); + + // assert + $this->assertEquals('', $result); + } } \ No newline at end of file diff --git a/tests/XArrayBase/getVal_Test.php b/tests/XArrayBase/getVal_Test.php index 411f156..1b8c479 100644 --- a/tests/XArrayBase/getVal_Test.php +++ b/tests/XArrayBase/getVal_Test.php @@ -14,63 +14,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -namespace modethirteen\XArray\tests\XArrayBase; +namespace modethirteen\XArray\Tests\XArrayBase; abstract class getVal_Test extends XArrayUnitTestCaseBase { - /** - * @test - * @dataProvider source_xpath_expected_Provider - * @param array $source - * @param string $xpath - * @param mixed $expected - */ - public function Can_get_value(array $source, string $xpath, $expected) { - - // arrange - $x = $this->newXArray($source); - - // act - $result = $x->getVal($xpath); - - // assert - $this->assertEquals($expected, $result); - } - - /** - * @test - */ - public function Can_get_default() { - - // arrange - $x = $this->newXArray(['foo' => 'bar']); - - // act - $result = $x->getVal('qux', true); - - // assert - $this->assertEquals(true, $result); - } - - /** - * @test - */ - public function Empty_key_returns_null() { - - // arrange - $x = $this->newXArray(['foo' => 'bar']); - - // act - $result = $x->getVal(''); - - // assert - $this->assertNull($result); - } - /** * @return array */ - public static function source_xpath_expected_Provider() : array { + public static function source_key_expected_Provider() : array { return [ 'empty level one' => [ [], @@ -102,12 +53,12 @@ public static function source_xpath_expected_Provider() : array { 'foo/bar/baz', 'qux', ], - + // XArray::getVal(...) only gets first element of array value 'array level one' => [ ['foo' => ['bar', 'baz']], 'foo', - 'bar', + 'bar' ], 'array level two' => [ ['foo' => ['bar' => ['baz', 'qux']]], @@ -118,7 +69,101 @@ public static function source_xpath_expected_Provider() : array { ['foo' => ['bar' => ['baz' => ['qux', 'fred']]]], 'foo/bar/baz', 'qux' + ], + 'array of arrays level zero' => [ + [['foo', 'bar'], ['baz'], ['qux']], + '', + null + ], + 'array of arrays level one' => [ + ['plugh' => [['foo', 'bar'], ['baz'], ['qux']]], + 'plugh', + ['foo', 'bar'] + ], + 'array of arrays level two' => [ + ['plugh' => ['xyzzy' => [['foo', 'bar'], ['baz'], ['qux']]]], + 'plugh/xyzzy', + ['foo', 'bar'] + ], + 'array of arrays level three' => [ + ['plugh' => ['xyzzy' => ['ogre' => [['foo', 'bar'], ['baz'], ['qux']]]]], + 'plugh/xyzzy/ogre', + ['foo', 'bar'] + ], + 'array of arrays level four' => [ + ['plugh' => ['xyzzy' => ['ogre' => ['nivek' => [['foo', 'bar'], ['baz'], ['qux']]]]]], + 'plugh/xyzzy/ogre/nivek', + ['foo', 'bar'] + ], + 'array of arrays level two with level one key' => [ + ['plugh' => ['xyzzy' => [['foo', 'bar'], ['baz'], ['qux']]]], + 'plugh', + ['xyzzy' => [['foo', 'bar'], ['baz'], ['qux']]] + ], + 'array of arrays level three with level two key' => [ + ['plugh' => ['xyzzy' => ['ogre' => [['foo', 'bar'], ['baz'], ['qux']]]]], + 'plugh/xyzzy', + ['ogre' => [['foo', 'bar'], ['baz'], ['qux']]] + ], + 'array of arrays level four with level three key' => [ + ['plugh' => ['xyzzy' => ['ogre' => ['nivek' => [['foo', 'bar'], ['baz'], ['qux']]]]]], + 'plugh/xyzzy/ogre', + ['nivek' => [['foo', 'bar'], ['baz'], ['qux']]] + ], + 'extra preceding key path segment' => [ + ['foo' => ['bar' => 'baz']], + '/foo/bar', + 'baz' ] ]; } + + /** + * @test + * @dataProvider source_key_expected_Provider + * @param array $source + * @param string $key + * @param mixed $expected + */ + public function Can_get_value(array $source, string $key, $expected) : void { + + // arrange + $x = $this->newXArray($source); + + // act + $result = $x->getVal($key); + + // assert + $this->assertEquals($expected, $result); + } + + /** + * @test + */ + public function Can_get_default() : void { + + // arrange + $x = $this->newXArray(['foo' => 'bar']); + + // act + $result = $x->getVal('qux', true); + + // assert + $this->assertEquals(true, $result); + } + + /** + * @test + */ + public function Empty_key_returns_null() : void { + + // arrange + $x = $this->newXArray(['foo' => 'bar']); + + // act + $result = $x->getVal(''); + + // assert + $this->assertNull($result); + } } \ No newline at end of file diff --git a/tests/XArrayBase/setVal_Test.php b/tests/XArrayBase/setVal_Test.php index 905c9ce..f3d05e2 100644 --- a/tests/XArrayBase/setVal_Test.php +++ b/tests/XArrayBase/setVal_Test.php @@ -14,34 +14,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -namespace modethirteen\XArray\tests\XArrayBase; +namespace modethirteen\XArray\Tests\XArrayBase; abstract class setVal_Test extends XArrayUnitTestCaseBase { - /** - * @test - * @dataProvider source_xpath_value_expected_Provider - * @param array $source - * @param string $xpath - * @param mixed $value - * @param array $expected - */ - public function Can_set_value(array $source, string $xpath, $value, array $expected) { - - // arrange - $x = $this->newXArray($source); - - // act - $x->setVal($xpath, $value); - - // assert - $this->assertEquals($expected, $x->toArray()); - } - /** * @return array */ - public static function source_xpath_value_expected_Provider() : array { + public static function source_key_value_expected_Provider() : array { return [ 'empty with string level one' => [ [], @@ -79,6 +59,42 @@ public static function source_xpath_value_expected_Provider() : array { ['qux', 'fred'], ['foo' => ['bar' => ['baz' => ['qux', 'fred']]]] ], + 'empty with bool level one' => [ + [], + 'foo', + true, + ['foo' => true] + ], + 'empty with bool level two' => [ + [], + 'foo/bar', + true, + ['foo' => ['bar' => true]] + ], + 'empty with bool level three' => [ + [], + 'foo/bar/baz', + true, + ['foo' => ['bar' => ['baz' => true]]] + ], + 'empty with int level one' => [ + [], + 'foo', + 123, + ['foo' => 123] + ], + 'empty with int level two' => [ + [], + 'foo/bar', + 123, + ['foo' => ['bar' => 123]] + ], + 'empty with int level three' => [ + [], + 'foo/bar/baz', + 123, + ['foo' => ['bar' => ['baz' => 123]]] + ], 'string with string level one' => [ ['foo' => 'bar'], 'foo', @@ -186,7 +202,45 @@ public static function source_xpath_value_expected_Provider() : array { 'foo/bar/baz', ['apple', 'pear'], ['foo' => ['bar' => ['baz' => ['apple', 'pear']]]] + ], + 'replace leaf bool with key' => [ + ['foo' => true], + 'foo/bar', + 'qux', + ['foo' => ['bar' => 'qux']] + ], + 'replace leaf string with key' => [ + ['foo' => 'bar'], + 'foo/bar', + 'qux', + ['foo' => ['bar' => 'qux']] + ], + 'replace leaf int with key' => [ + ['foo' => 'bar'], + 'foo/bar', + 'qux', + ['foo' => ['bar' => 'qux']] ] ]; } + + /** + * @test + * @dataProvider source_key_value_expected_Provider + * @param array $source + * @param string $key + * @param mixed $value + * @param array $expected + */ + public function Can_set_value(array $source, string $key, $value, array $expected) : void { + + // arrange + $x = $this->newXArray($source); + + // act + $x->setVal($key, $value); + + // assert + $this->assertEquals($expected, $x->toArray()); + } } \ No newline at end of file diff --git a/tests/XArrayBase/toArray_Test.php b/tests/XArrayBase/toArray_Test.php index 498e340..eeba911 100644 --- a/tests/XArrayBase/toArray_Test.php +++ b/tests/XArrayBase/toArray_Test.php @@ -14,21 +14,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -namespace modethirteen\XArray\tests\XArrayBase; +namespace modethirteen\XArray\Tests\XArrayBase; + +use modethirteen\XArray\SchemaBuilder; abstract class toArray_Test extends XArrayUnitTestCaseBase { /** * @test */ - public function Can_return_new_array_created_from_empty_ctor() { + public function Can_return_new_array_created_from_empty_ctor() : void { // arrange - $X = $this->newXArray(); - $X->setVal('foo/bar', 'baz'); + $x = $this->newXArray([], (new SchemaBuilder())->with('foo/bar')); + $x->setVal('foo/bar', 'baz'); // act - $result = $X->toArray(); + $result = $x->toArray(); // assert $this->assertEquals(['foo' => ['bar' => 'baz']], $result); @@ -37,14 +39,14 @@ public function Can_return_new_array_created_from_empty_ctor() { /** * @test */ - public function Can_return_non_modified_array_created_from_array() { + public function Can_return_non_modified_array_created_from_array() : void { // arrange $array = ['foo' => ['bar' => 'baz']]; - $X = $this->newXArray($array); + $x = $this->newXArray($array); // act - $result = $X->toArray(); + $result = $x->toArray(); // assert $this->assertEquals(['foo' => ['bar' => 'baz']], $result); @@ -53,15 +55,18 @@ public function Can_return_non_modified_array_created_from_array() { /** * @test */ - public function Can_return_modified_array_created_from_array() { + public function Can_return_modified_array_created_from_array() : void { // arrange $array = ['foo' => ['bar' => 'baz']]; - $X = $this->newXArray($array); - $X->setVal('qux', 'fred'); + $x = $this->newXArray($array, (new SchemaBuilder()) + ->with('foo/bar') + ->with('qux/fred') + ); + $x->setVal('qux', 'fred'); // act - $result = $X->toArray(); + $result = $x->toArray(); // assert $this->assertEquals(['foo' => ['bar' => 'baz'], 'qux' => 'fred'], $result); diff --git a/tests/XArrayBase/toFlattenedArray_Test.php b/tests/XArrayBase/toFlattenedArray_Test.php new file mode 100644 index 0000000..e3b87b0 --- /dev/null +++ b/tests/XArrayBase/toFlattenedArray_Test.php @@ -0,0 +1,91 @@ +newXArray([], (new SchemaBuilder())->with('foo/bar')); + $x->setVal('foo/bar', 'baz'); + + // act + $result = $x->toFlattenedArray(); + + // assert + $this->assertEquals(['foo/bar' => 'baz'], $result); + } + + /** + * @test + */ + public function Can_return_non_modified_array_created_from_array() : void { + + // arrange + $array = ['foo' => ['bar' => 'baz']]; + $x = $this->newXArray($array); + + // act + $result = $x->toFlattenedArray(); + + // assert + $this->assertEquals(['foo/bar' => 'baz'], $result); + } + + /** + * @test + */ + public function Can_return_modified_array_created_from_array() : void { + + // arrange + $array = [ + 'foo' => ['bar' => 'baz'], + 541 => ['attr' => '#text'] + ]; + $x = $this->newXArray($array, (new SchemaBuilder()) + ->with('foo/bar') + ->with('qux/fred') + ->with('a/b/c/d/e') + ->with('a/b/f/g') + ->with('qux/plugh') + ->with('bazz/foo/foobar/fred') + ); + $x->setVal('qux', 'fred'); + $x->setVal('a/b/c/d/e', true); + $x->setVal('a/b/f/g', false); + $x->setVal('qux/plugh', 'xyzzy'); + $x->setVal('bazz/foo/foobar/fred', ['sodium', 'argon', 'iodine']); + + // act + $result = $x->toFlattenedArray(); + + // assert + $this->assertEquals([ + 'foo/bar' => 'baz', + 'qux/plugh' => 'xyzzy', + 'a/b/c/d/e' => true, + 'a/b/f/g' => false, + 'bazz/foo/foobar/fred' => ['sodium', 'argon', 'iodine'] + ], $result); + } +} diff --git a/tests/XArrayBase/toXml_Test.php b/tests/XArrayBase/toXml_Test.php index ea06277..5f9ee40 100644 --- a/tests/XArrayBase/toXml_Test.php +++ b/tests/XArrayBase/toXml_Test.php @@ -14,14 +14,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -namespace modethirteen\XArray\tests\XArrayBase; +namespace modethirteen\XArray\Tests\XArrayBase; + +use modethirteen\XArray\SchemaLockedArray; abstract class toXml_Test extends XArrayUnitTestCaseBase { /** * @test */ - public function Simple_array_with_attributes_and_text() { + public function Simple_array_with_attributes_and_text() : void { // arrange $source = ['p' => ['@attr1' => 'val1', '@attr2' => 'val2', '#text' => 'text']]; @@ -37,7 +39,7 @@ public function Simple_array_with_attributes_and_text() { /** * @test */ - public function Nested_array_with_attributes_and_text() { + public function Nested_array_with_attributes_and_text() : void { // arrange $source = [ @@ -64,7 +66,7 @@ public function Nested_array_with_attributes_and_text() { /** * @test */ - public function Nested_array_with_attributes_and_text_and_outer_xml() { + public function Nested_array_with_attributes_and_text_and_outer_xml() : void { // arrange $source = [ @@ -93,7 +95,7 @@ public function Nested_array_with_attributes_and_text_and_outer_xml() { * * @test */ - public function Attributes_with_special_characters_are_encoded() { + public function Attributes_with_special_characters_are_encoded() : void { // arrange $source = ['p' => ['@attr1"&\'<>' => 'val1', '#text' => 'text']]; @@ -111,7 +113,7 @@ public function Attributes_with_special_characters_are_encoded() { * * @test */ - public function Attribute_values_with_special_characters_are_encoded() { + public function Attribute_values_with_special_characters_are_encoded() : void { // arrange $source = ['p' => ['@attr1' => 'val1"&\'<>', '#text' => 'text']]; @@ -129,7 +131,7 @@ public function Attribute_values_with_special_characters_are_encoded() { * * @test */ - public function Text_with_special_characters_are_encoded() { + public function Text_with_special_characters_are_encoded() : void { // arrange $source = ['p' => ['#text' => 'text"&\'<>']]; @@ -147,7 +149,7 @@ public function Text_with_special_characters_are_encoded() { * * @test */ - public function Xml_tags_with_special_characters_are_encoded() { + public function Xml_tags_with_special_characters_are_encoded() : void { // arrange $source = ['p"&\'<>' => ['#text' => 'text']]; @@ -165,7 +167,7 @@ public function Xml_tags_with_special_characters_are_encoded() { * * @test */ - public function Xml_values_with_special_characters_are_encoded() { + public function Xml_values_with_special_characters_are_encoded() : void { // arrange $source = ['p' => 'val"&\'<>']; @@ -181,7 +183,7 @@ public function Xml_values_with_special_characters_are_encoded() { /** * @test */ - public function Can_handle_numeric_arrays() { + public function Can_handle_numeric_arrays() : void { // arrange $source = ['foo' => [ @@ -199,7 +201,7 @@ public function Can_handle_numeric_arrays() { /** * @test */ - public function Can_handle_non_string_types() { + public function Can_handle_non_string_types() : void { // arrange $source = [ @@ -210,7 +212,7 @@ public function Can_handle_non_string_types() { 'p' => [ '@attr4' => 1.45, '@attr5' => new class { - public function __toString() { + public function __toString() : string { return 'zxcv'; } }, @@ -227,6 +229,12 @@ public function __toString() { $xml = $x->toXml(); // assert - $this->assertEquals('
text

text2

<541>foo
', $xml, 'XML output was incorrect'); + if(static::$class === SchemaLockedArray::class) { + + // schema can not allowlist an integer as a segment of a key path + $this->assertEquals('
text

text2

', $xml, 'XML output was incorrect'); + } else { + $this->assertEquals('
text

text2

<541>foo
', $xml, 'XML output was incorrect'); + } } } \ No newline at end of file