dot-access-data/composer.json000064400000003401150250175140012220 0ustar00{ "name": "dflydev/dot-access-data", "type": "library", "description": "Given a deep data structure, access data by dot notation.", "homepage": "https://github.com/dflydev/dflydev-dot-access-data", "keywords": ["dot", "access", "data", "notation"], "license": "MIT", "authors": [ { "name": "Dragonfly Development Inc.", "email": "info@dflydev.com", "homepage": "http://dflydev.com" }, { "name": "Beau Simensen", "email": "beau@dflydev.com", "homepage": "http://beausimensen.com" }, { "name": "Carlos Frutos", "email": "carlos@kiwing.it", "homepage": "https://github.com/cfrutos" }, { "name": "Colin O'Dell", "email": "colinodell@gmail.com", "homepage": "https://www.colinodell.com" } ], "require": { "php": "^7.1 || ^8.0" }, "require-dev": { "phpstan/phpstan": "^0.12.42", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", "scrutinizer/ocular": "1.6.0", "squizlabs/php_codesniffer": "^3.5", "vimeo/psalm": "^4.0.0" }, "autoload": { "psr-4": { "Dflydev\\DotAccessData\\": "src/" } }, "autoload-dev": { "psr-4": { "Dflydev\\DotAccessData\\": "tests/" } }, "extra": { "branch-alias": { "dev-main": "3.x-dev" } }, "scripts": { "phpcs": "phpcs", "phpstan": "phpstan analyse", "phpunit": "phpunit --no-coverage", "psalm": "psalm", "test": [ "@phpcs", "@phpstan", "@psalm", "@phpunit" ] } } dot-access-data/CHANGELOG.md000064400000004446150250175140011321 0ustar00# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [3.0.2] - 2022-10-27 ### Fixed - Added missing return types to docblocks (#44, #45) ## [3.0.1] - 2021-08-13 ### Added - Adds ReturnTypeWillChange to suppress PHP 8.1 warnings (#40) ## [3.0.0] - 2021-01-01 ### Added - Added support for both `.` and `/`-delimited key paths (#24) - Added parameter and return types to everything; enabled strict type checks (#18) - Added new exception classes to better identify certain types of errors (#20) - `Data` now implements `ArrayAccess` (#17) - Added ability to merge non-associative array values (#31, #32) ### Changed - All thrown exceptions are now instances or subclasses of `DataException` (#20) - Calling `get()` on a missing key path without providing a default will throw a `MissingPathException` instead of returning `null` (#29) - Bumped supported PHP versions to 7.1 - 8.x (#18) ### Fixed - Fixed incorrect merging of array values into string values (#32) - Fixed `get()` method behaving as if keys with `null` values didn't exist ## [2.0.0] - 2017-12-21 ### Changed - Bumped supported PHP versions to 7.0 - 7.4 (#12) - Switched to PSR-4 autoloading ## [1.1.0] - 2017-01-20 ### Added - Added new `has()` method to check for the existence of the given key (#4, #7) ## [1.0.1] - 2015-08-12 ### Added - Added new optional `$default` parameter to the `get()` method (#2) ## [1.0.0] - 2012-07-17 **Initial release!** [Unreleased]: https://github.com/dflydev/dflydev-dot-access-data/compare/v3.0.2...main [3.0.2]: https://github.com/dflydev/dflydev-dot-access-data/compare/v3.0.1...v3.0.2 [3.0.1]: https://github.com/dflydev/dflydev-dot-access-data/compare/v3.0.0...v3.0.1 [3.0.0]: https://github.com/dflydev/dflydev-dot-access-data/compare/v2.0.0...v3.0.0 [2.0.0]: https://github.com/dflydev/dflydev-dot-access-data/compare/v1.1.0...v2.0.0 [1.1.0]: https://github.com/dflydev/dflydev-dot-access-data/compare/v1.0.1...v1.1.0 [1.0.1]: https://github.com/dflydev/dflydev-dot-access-data/compare/v1.0.0...v1.0.1 [1.0.0]: https://github.com/dflydev/dflydev-dot-access-data/releases/tag/v1.0.0 dot-access-data/src/Util.php000064400000003604150250175140011720 0ustar00 $arr * * @return bool * * @psalm-pure */ public static function isAssoc(array $arr): bool { return !count($arr) || count(array_filter(array_keys($arr), 'is_string')) == count($arr); } /** * Merge contents from one associtative array to another * * @param mixed $to * @param mixed $from * @param DataInterface::PRESERVE|DataInterface::REPLACE|DataInterface::MERGE $mode * * @return mixed * * @psalm-pure */ public static function mergeAssocArray($to, $from, int $mode = DataInterface::REPLACE) { if ($mode === DataInterface::MERGE && self::isList($to) && self::isList($from)) { return array_merge($to, $from); } if (is_array($from) && is_array($to)) { foreach ($from as $k => $v) { if (!isset($to[$k])) { $to[$k] = $v; } else { $to[$k] = self::mergeAssocArray($to[$k], $v, $mode); } } return $to; } return $mode === DataInterface::PRESERVE ? $to : $from; } /** * @param mixed $value * * @return bool * * @psalm-pure */ private static function isList($value): bool { return is_array($value) && array_values($value) === $value; } } dot-access-data/src/Data.php000064400000015127150250175140011657 0ustar00 */ class Data implements DataInterface, ArrayAccess { private const DELIMITERS = ['.', '/']; /** * Internal representation of data data * * @var array */ protected $data; /** * Constructor * * @param array $data */ public function __construct(array $data = []) { $this->data = $data; } /** * {@inheritdoc} */ public function append(string $key, $value = null): void { $currentValue =& $this->data; $keyPath = self::keyToPathArray($key); $endKey = array_pop($keyPath); foreach ($keyPath as $currentKey) { if (! isset($currentValue[$currentKey])) { $currentValue[$currentKey] = []; } $currentValue =& $currentValue[$currentKey]; } if (!isset($currentValue[$endKey])) { $currentValue[$endKey] = []; } if (!is_array($currentValue[$endKey])) { // Promote this key to an array. // TODO: Is this really what we want to do? $currentValue[$endKey] = [$currentValue[$endKey]]; } $currentValue[$endKey][] = $value; } /** * {@inheritdoc} */ public function set(string $key, $value = null): void { $currentValue =& $this->data; $keyPath = self::keyToPathArray($key); $endKey = array_pop($keyPath); foreach ($keyPath as $currentKey) { if (!isset($currentValue[$currentKey])) { $currentValue[$currentKey] = []; } if (!is_array($currentValue[$currentKey])) { throw new DataException(sprintf('Key path "%s" within "%s" cannot be indexed into (is not an array)', $currentKey, self::formatPath($key))); } $currentValue =& $currentValue[$currentKey]; } $currentValue[$endKey] = $value; } /** * {@inheritdoc} */ public function remove(string $key): void { $currentValue =& $this->data; $keyPath = self::keyToPathArray($key); $endKey = array_pop($keyPath); foreach ($keyPath as $currentKey) { if (!isset($currentValue[$currentKey])) { return; } $currentValue =& $currentValue[$currentKey]; } unset($currentValue[$endKey]); } /** * {@inheritdoc} * * @psalm-mutation-free */ public function get(string $key, $default = null) { /** @psalm-suppress ImpureFunctionCall */ $hasDefault = \func_num_args() > 1; $currentValue = $this->data; $keyPath = self::keyToPathArray($key); foreach ($keyPath as $currentKey) { if (!is_array($currentValue) || !array_key_exists($currentKey, $currentValue)) { if ($hasDefault) { return $default; } throw new MissingPathException($key, sprintf('No data exists at the given path: "%s"', self::formatPath($keyPath))); } $currentValue = $currentValue[$currentKey]; } return $currentValue === null ? $default : $currentValue; } /** * {@inheritdoc} * * @psalm-mutation-free */ public function has(string $key): bool { $currentValue = $this->data; foreach (self::keyToPathArray($key) as $currentKey) { if ( !is_array($currentValue) || !array_key_exists($currentKey, $currentValue) ) { return false; } $currentValue = $currentValue[$currentKey]; } return true; } /** * {@inheritdoc} * * @psalm-mutation-free */ public function getData(string $key): DataInterface { $value = $this->get($key); if (is_array($value) && Util::isAssoc($value)) { return new Data($value); } throw new DataException(sprintf('Value at "%s" could not be represented as a DataInterface', self::formatPath($key))); } /** * {@inheritdoc} */ public function import(array $data, int $mode = self::REPLACE): void { $this->data = Util::mergeAssocArray($this->data, $data, $mode); } /** * {@inheritdoc} */ public function importData(DataInterface $data, int $mode = self::REPLACE): void { $this->import($data->export(), $mode); } /** * {@inheritdoc} * * @psalm-mutation-free */ public function export(): array { return $this->data; } /** * {@inheritdoc} * * @return bool */ #[\ReturnTypeWillChange] public function offsetExists($key) { return $this->has($key); } /** * {@inheritdoc} * * @return mixed */ #[\ReturnTypeWillChange] public function offsetGet($key) { return $this->get($key, null); } /** * {@inheritdoc} * * @param string $key * @param mixed $value * * @return void */ #[\ReturnTypeWillChange] public function offsetSet($key, $value) { $this->set($key, $value); } /** * {@inheritdoc} * * @return void */ #[\ReturnTypeWillChange] public function offsetUnset($key) { $this->remove($key); } /** * @param string $path * * @return string[] * * @psalm-return non-empty-list * * @psalm-pure */ protected static function keyToPathArray(string $path): array { if (\strlen($path) === 0) { throw new InvalidPathException('Path cannot be an empty string'); } $path = \str_replace(self::DELIMITERS, '.', $path); return \explode('.', $path); } /** * @param string|string[] $path * * @return string * * @psalm-pure */ protected static function formatPath($path): string { if (is_string($path)) { $path = self::keyToPathArray($path); } return implode(' » ', $path); } } dot-access-data/src/DataInterface.php000064400000006664150250175140013506 0ustar00 $data * @param self::PRESERVE|self::REPLACE|self::MERGE $mode */ public function import(array $data, int $mode = self::REPLACE): void; /** * Import data from an external data into existing data * * @param DataInterface $data * @param self::PRESERVE|self::REPLACE|self::MERGE $mode */ public function importData(DataInterface $data, int $mode = self::REPLACE): void; /** * Export data as raw data * * @return array * * @psalm-mutation-free */ public function export(): array; } dot-access-data/src/Exception/DataException.php000064400000000637150250175140015474 0ustar00path = $path; parent::__construct($message, $code, $previous); } public function getPath(): string { return $this->path; } } dot-access-data/src/Exception/InvalidPathException.php000064400000000656150250175140017027 0ustar00 For PHP (5.3+) please refer to version `1.0`. Usage ----- Abstract example: ```php use Dflydev\DotAccessData\Data; $data = new Data; $data->set('a.b.c', 'C'); $data->set('a.b.d', 'D1'); $data->append('a.b.d', 'D2'); $data->set('a.b.e', ['E0', 'E1', 'E2']); // C $data->get('a.b.c'); // ['D1', 'D2'] $data->get('a.b.d'); // ['E0', 'E1', 'E2'] $data->get('a.b.e'); // true $data->has('a.b.c'); // false $data->has('a.b.d.j'); // 'some-default-value' $data->get('some.path.that.does.not.exist', 'some-default-value'); // throws a MissingPathException because no default was given $data->get('some.path.that.does.not.exist'); ``` A more concrete example: ```php use Dflydev\DotAccessData\Data; $data = new Data([ 'hosts' => [ 'hewey' => [ 'username' => 'hman', 'password' => 'HPASS', 'roles' => ['web'], ], 'dewey' => [ 'username' => 'dman', 'password' => 'D---S', 'roles' => ['web', 'db'], 'nick' => 'dewey dman', ], 'lewey' => [ 'username' => 'lman', 'password' => 'LP@$$', 'roles' => ['db'], ], ], ]); // hman $username = $data->get('hosts.hewey.username'); // HPASS $password = $data->get('hosts.hewey.password'); // ['web'] $roles = $data->get('hosts.hewey.roles'); // dewey dman $nick = $data->get('hosts.dewey.nick'); // Unknown $nick = $data->get('hosts.lewey.nick', 'Unknown'); // DataInterface instance $dewey = $data->getData('hosts.dewey'); // dman $username = $dewey->get('username'); // D---S $password = $dewey->get('password'); // ['web', 'db'] $roles = $dewey->get('roles'); // No more lewey $data->remove('hosts.lewey'); // Add DB to hewey's roles $data->append('hosts.hewey.roles', 'db'); $data->set('hosts.april', [ 'username' => 'aman', 'password' => '@---S', 'roles' => ['web'], ]); // Check if a key exists (true to this case) $hasKey = $data->has('hosts.dewey.username'); ``` `Data` may be used as an array, since it implements `ArrayAccess` interface: ```php // Get $data->get('name') === $data['name']; // true $data['name'] = 'Dewey'; // is equivalent to $data->set($name, 'Dewey'); isset($data['name']) === $data->has('name'); // Remove key unset($data['name']); ``` `/` can also be used as a path delimiter: ```php $data->set('a/b/c', 'd'); echo $data->get('a/b/c'); // "d" $data->get('a/b/c') === $data->get('a.b.c'); // true ``` License ------- This library is licensed under the MIT License - see the LICENSE file for details. Community --------- If you have questions or want to help out, join us in the [#dflydev](irc://irc.freenode.net/#dflydev) channel on irc.freenode.net. dot-access-data/LICENSE000064400000002056150250175140010510 0ustar00Copyright (c) 2012 Dragonfly Development Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.