PK n[U|"ʼ psalm.xml.distnu ٘
PK n[Uq5E E README.mdnu ٘ Dot Access Data
===============
[![Latest Version](https://img.shields.io/packagist/v/dflydev/dot-access-data.svg?style=flat-square)](https://packagist.org/packages/dflydev/dot-access-data)
[![Total Downloads](https://img.shields.io/packagist/dt/dflydev/dot-access-data.svg?style=flat-square)](https://packagist.org/packages/dflydev/dot-access-data)
[![Software License](https://img.shields.io/badge/License-MIT-brightgreen.svg?style=flat-square)](LICENSE)
[![Build Status](https://img.shields.io/github/workflow/status/dflydev/dflydev-dot-access-data/Tests/main.svg?style=flat-square)](https://github.com/dflydev/dflydev-dot-access-data/actions?query=workflow%3ATests+branch%3Amain)
[![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/dflydev/dflydev-dot-access-data.svg?style=flat-square)](https://scrutinizer-ci.com/g/dflydev/dflydev-dot-access-data/code-structure/)
[![Quality Score](https://img.shields.io/scrutinizer/g/dflydev/dflydev-dot-access-data.svg?style=flat-square)](https://scrutinizer-ci.com/g/dflydev/dflydev-dot-access-data)
Given a deep data structure, access data by dot notation.
Requirements
------------
* PHP (7.1+)
> 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.
PK n[U>e e
.gitignorenu ٘ .phpcs-cache
.phpunit.result.cache
composer.lock
phpcs.xml
phpstan.neon
phpunit.xml
psalm.xml
vendor
PK n[Ue src/Exception/DataException.phpnu ٘ path = $path;
parent::__construct($message, $code, $previous);
}
public function getPath(): string
{
return $this->path;
}
}
PK n[U18"F src/Util.phpnu ٘ $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;
}
}
PK n[Urh
src/DataInterface.phpnu ٘ $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;
}
PK n[UϲW W src/Data.phpnu ٘
*/
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);
}
}
PK n[U( phpcs.xml.distnu ٘
src
tests
PK n[UL L .github/CONTRIBUTING.mdnu ٘ # Contributing
Contributions are **welcome** and will be fully **credited**. We accept contributions via Pull Requests on [GitHub](https://github.com/dflydev/dflydev-dot-access-data).
## Pull Requests
- **[PSR-12 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-12-extended-coding-style-guide.md)** - The easiest way to apply the conventions is to run `./vendor/bin/phpcbf`.
- **Add tests!** - Your patch won't be accepted if it doesn't have tests.
- **Tests must pass** - All automated tests, including things like code style checks, but be passing before we'll consider merging the change.
- **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.
- **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option.
- **Create feature branches** - Don't ask us to pull from your default branch.
- **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
- **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please squash them before submitting.
## Running Tests
``` bash
$ composer test
```
**Happy coding**!
PK n[U4ٔ> > .github/workflows/tests.ymlnu ٘ name: Tests
on:
push: ~
pull_request: ~
jobs:
phpcs:
name: PHPCS
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@v2
with:
php-version: 7.1
extensions: curl, mbstring
coverage: none
tools: composer:v2, cs2pr
- run: composer update --no-progress
- run: vendor/bin/phpcs -q --report=checkstyle | cs2pr
phpunit:
name: PHPUnit on ${{ matrix.php }} ${{ matrix.composer-flags }}
runs-on: ubuntu-latest
strategy:
matrix:
php: ['7.2', '7.3', '7.4']
coverage: [pcov]
composer-flags: ['']
include:
- php: '7.1'
coverage: xdebug
composer-flags: ''
- php: '8.0'
coverage: false
composer-flags: '--ignore-platform-req=php'
- php: '8.1'
coverage: false
composer-flags: '--ignore-platform-req=php'
steps:
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: curl
coverage: ${{ matrix.coverage }}
tools: composer:v2
- run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
- name: "Use PHPUnit 9.3+ on PHP 8.0 & PHP 8.1"
run: composer require --no-update --dev phpunit/phpunit:^9.3
if: "matrix.php == '8.0' || matrix.php == '8.1'"
- run: composer update --no-progress ${{ matrix.composer-flags }}
- run: vendor/bin/phpunit --no-coverage
if: ${{ matrix.coverage == 'none' }}
- run: vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover
if: ${{ matrix.coverage != 'none' }}
- run: php vendor/bin/ocular code-coverage:upload --format=php-clover coverage.clover
if: ${{ matrix.coverage != 'none' }}
phpstan:
name: PHPStan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@v2
with:
php-version: 7.1
extensions: curl
coverage: none
tools: composer:v2
- run: composer update --no-progress
- run: vendor/bin/phpstan analyse --no-progress
psalm:
name: Psalm
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@v2
with:
php-version: 7.1
extensions: curl
coverage: none
tools: composer:v2
- run: composer update --no-progress
- run: vendor/bin/psalm --no-progress --output-format=github
PK n[U
ϼ .gitattributesnu ٘ # Path-based git attributes
# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html
# Ignore all test and documentation with "export-ignore".
/.editorconfig export-ignore
/.gitattributes export-ignore
/.github export-ignore
/.gitignore export-ignore
/.scrutinizer.yml export-ignore
/phpcs.xml.dist export-ignore
/phpstan.neon.dist export-ignore
/phpunit.xml.dist export-ignore
/psalm.xml.dist export-ignore
/tests export-ignore
PK n[UGv, , phpstan.neon.distnu ٘ parameters:
level: max
paths:
- src
PK n[Uߛ phpunit.xml.distnu ٘
./tests
./src
PK n[U
.editorconfignu ٘ ; top-most EditorConfig file
root = true
# All files.
[*]
end_of_line = LF
indent_style = space
indent_size = 4
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
PK n[U4& & CHANGELOG.mdnu ٘ # 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
PK n[Uz tests/UtilTest.phpnu ٘ assertTrue(Util::isAssoc(['a' => 'A']));
$this->assertTrue(Util::isAssoc([]));
$this->assertFalse(Util::isAssoc([1 => 'One']));
}
/**
* @dataProvider mergeAssocArrayProvider
*/
public function testMergeAssocArray($message, $to, $from, $mode, $expectedResult)
{
if ($mode === null) {
$result = Util::mergeAssocArray($to, $from);
} else {
$result = Util::mergeAssocArray($to, $from, $mode);
}
$this->assertEquals($expectedResult, $result, $message);
}
public function mergeAssocArrayProvider()
{
return [
[
'Overwrite should replace to value with from value for strings (shallow)',
// to
['a' => 'A'],
// from
['a' => 'B'],
// mode
DataInterface::REPLACE,
// expected result
['a' => 'B'],
],
[
'Overwrite should replace to value with from value for strings (deep)',
// to
['a' => ['b' => 'B']],
// from
['a' => ['b' => 'C']],
// mode
DataInterface::REPLACE,
// expected result
['a' => ['b' => 'C']]
],
[
'Existing values are not replaced in preserve mode (shallow)',
// to
['a' => 'A'],
// from
['a' => 'B'],
// mode
DataInterface::PRESERVE,
// expected result
['a' => 'A'],
],
[
'Existing values are not replaced in preserve mode (deep)',
// to
['a' => ['b' => 'B']],
// from
['a' => ['b' => 'C']],
// mode
DataInterface::PRESERVE,
// expected result
['a' => ['b' => 'B']],
],
[
'Associative arrays should be combined',
// to
['a' => ['b' => 'B']],
// from
['a' => ['c' => 'C']],
// mode
null,
// expected result
['a' => ['b' => 'B', 'c' => 'C']],
],
[
'Arrays should be replaced',
// to
['a' => ['b', 'c']],
// from
['a' => ['B', 'C']],
// mode
DataInterface::REPLACE,
// expected result
['a' => ['B', 'C']],
],
[
'Arrays should be preserved',
// to
['a' => ['b', 'c']],
// from
['a' => ['B', 'C']],
// mode
DataInterface::PRESERVE,
// expected result
['a' => ['b', 'c']],
],
[
'Arrays should be merged/appended (when using MERGE)',
// to
['a' => 1, 'b' => 1, 'n' => [1], 'x' => 'string', 'y' => ['stringindex' => 1]],
// from
['a' => 2, 'c' => 2, 'n' => [2], 'x' => ['array'], 'y' => [2]],
// mode
DataInterface::MERGE,
// expected result
['a' => 2, 'b' => 1, 'c' => 2, 'n' => [1, 2], 'x' => ['array'], 'y' => ['stringindex' => 1, 0 => 2]]
],
];
}
}
PK n[Uf:k1 1 tests/DataTest.phpnu ٘ 'A',
'b' => [
'b' => 'B',
'c' => ['C1', 'C2', 'C3'],
'd' => [
'd1' => 'D1',
'd2' => 'D2',
'd3' => 'D3',
],
],
'c' => ['c1', 'c2', 'c3'],
'f' => [
'g' => [
'h' => 'FGH',
],
],
'h' => [
'i' => 'I',
],
'i' => [
'j' => 'J',
],
'n' => null,
'n2' => [
'n' => null,
],
];
}
protected function runSampleDataTests(DataInterface $data)
{
$this->assertEquals('A', $data->get('a'));
$this->assertEquals('B', $data->get('b.b'));
$this->assertEquals('B', $data->get('b/b'));
$this->assertEquals(['C1', 'C2', 'C3'], $data->get('b.c'));
$this->assertEquals(['C1', 'C2', 'C3'], $data->get('b/c'));
$this->assertEquals('D3', $data->get('b.d.d3'));
$this->assertEquals('D3', $data->get('b/d/d3'));
$this->assertEquals(['c1', 'c2', 'c3'], $data->get('c'));
$this->assertEquals('c1', $data->get('c.0'));
$this->assertEquals('c2', $data->get('c/1'));
$this->assertNull($data->get('foo', null), 'Foo should not exist');
$this->assertNull($data->get('f.g.h.i', null));
$this->assertNull($data->get('f/g/h/i', null));
$this->assertEquals($data->get('foo', 'default-value-1'), 'default-value-1', 'Return default value');
$this->assertEquals($data->get('f.g.h.i', 'default-value-2'), 'default-value-2');
$this->assertEquals($data->get('f/g/h/i', 'default-value-2'), 'default-value-2');
$this->assertNull($data->get('n'));
$this->assertNull($data->get('n2/n'));
$this->expectException(InvalidPathException::class);
$this->expectExceptionMessage('Path cannot be an empty string');
$data->get('', 'broken');
}
public function testAppend()
{
$data = new Data($this->getSampleData());
$data->append('a', 'B');
$data->append('c', 'c4');
$data->append('b.c', 'C4');
$data->append('b/d/d3', 'D3b');
$data->append('b.d.d4', 'D');
$data->append('e', 'E');
$data->append('f/a', 'b');
$data->append('h.i', 'I2');
$data->append('i/k/l', 'L');
$data->append('n', 'N');
$data->append('n2/n', 'N');
$this->assertEquals(['A', 'B'], $data->get('a'));
$this->assertEquals(['c1', 'c2', 'c3', 'c4'], $data->get('c'));
$this->assertEquals('c4', $data->get('c.3'));
$this->assertEquals(['C1', 'C2', 'C3', 'C4'], $data->get('b.c'));
$this->assertEquals(['D3', 'D3b'], $data->get('b.d.d3'));
$this->assertEquals(['D'], $data->get('b.d.d4'));
$this->assertEquals(['E'], $data->get('e'));
$this->assertEquals(['b'], $data->get('f.a'));
$this->assertEquals(['I', 'I2'], $data->get('h.i'));
$this->assertEquals(['L'], $data->get('i.k.l'));
$this->assertEquals(['N'], $data->get('n'));
$this->assertEquals(['N'], $data->get('n2/n'));
$this->expectException(InvalidPathException::class);
$this->expectExceptionMessage('Path cannot be an empty string');
$data->append('', 'broken');
}
public function testSet()
{
$data = new Data();
$this->assertNull($data->get('a', null));
$this->assertNull($data->get('b/c', null));
$this->assertNull($data->get('d.e', null));
$this->assertNull($data->get('c.3', null));
$data->set('a', 'A');
$data->set('b/c', 'C');
$data->set('d.e', ['f' => 'F', 'g' => 'G']);
$data->set('c.3', 'c4');
$this->assertEquals('A', $data->get('a'));
$this->assertEquals(['c' => 'C'], $data->get('b'));
$this->assertEquals('C', $data->get('b.c'));
$this->assertEquals('F', $data->get('d/e/f'));
$this->assertEquals(['e' => ['f' => 'F', 'g' => 'G']], $data->get('d'));
$this->assertEquals('c4', $data->get('c.3'));
$data->set('a', null);
$this->assertTrue($data->has('a'), 'Data should exist with a null value');
$this->assertNull($data->get('a'), 'Data should exist with a null value');
$this->expectException(InvalidPathException::class);
$this->expectExceptionMessage('Path cannot be an empty string');
$data->set('', 'broken');
}
public function testSetClobberStringInPath()
{
$data = new Data();
$data->set('a.b.c', 'Should not be able to write to a.b.c.d.e');
$this->expectException(DataException::class);
$this->expectExceptionMessage('Key path "c" within "a » b » c » d » e" cannot be indexed into (is not an array)');
$data->set('a.b.c.d.e', 'broken');
}
public function testRemove()
{
$data = new Data($this->getSampleData());
$data->remove('a');
$data->remove('b.c');
$data->remove('b/d/d3');
$data->remove('d');
$data->remove('d.e.f');
$data->remove('empty.path');
$data->remove('c.2');
$this->assertFalse($data->has('a'));
$this->assertNull($data->get('a', null));
$this->assertFalse($data->has('b/c'));
$this->assertNull($data->get('b/c', null));
$this->assertFalse($data->has('b.d.d3'));
$this->assertNull($data->get('b.d.d3', null));
$this->assertNull(null);
$this->assertEquals('D2', $data->get('b.d.d2'));
$this->assertFalse($data->has('c.2'));
$this->assertNull($data->get('c.2', null));
$this->expectException(InvalidPathException::class);
$this->expectExceptionMessage('Path cannot be an empty string');
$data->remove('', 'broken');
}
public function testGet()
{
$data = new Data($this->getSampleData());
$this->runSampleDataTests($data);
}
public function testGetWhenValueDoesNotExist()
{
$data = new Data($this->getSampleData());
// With a default parameter given:
$this->assertSame('DEFAULT', $data->get('foo.bar', 'DEFAULT'));
$this->assertFalse($data->get('foo.bar', false));
$this->assertNull($data->get('foo/bar', null));
// Without a default parameter:
$this->expectException(DataException::class);
$this->expectExceptionMessage('No data exists at the given path: "foo » bar"');
$data->get('foo.bar');
}
public function testHas()
{
$data = new Data($this->getSampleData());
foreach (
['a', 'i', 'b.d', 'b/d', 'f.g.h', 'f/g/h', 'h.i', 'h/i', 'b.d.d1', 'b/d/d1', 'n', 'n2/n', 'c.1'] as $existentKey
) {
$this->assertTrue($data->has($existentKey));
}
foreach (
['p', 'b.b1', 'b/b1', 'b.c.C1', 'b/c/C1', 'h.i.I', 'h/i/I', 'b.d.d1.D1', 'b/d/d1/D1', 'c.4'] as $notExistentKey
) {
$this->assertFalse($data->has($notExistentKey));
}
$this->expectException(InvalidPathException::class);
$this->expectExceptionMessage('Path cannot be an empty string');
$data->has('', 'broken');
}
public function testGetData()
{
$wrappedData = new Data([
'wrapped' => [
'sampleData' => $this->getSampleData()
],
]);
$data = $wrappedData->getData('wrapped.sampleData');
$this->runSampleDataTests($data);
}
public function testGetDataOnNonArrayValue()
{
$data = new Data([
'foo' => 'bar',
]);
$this->expectException(DataException::class);
$this->expectExceptionMessage('Value at "foo" could not be represented as a DataInterface');
$data->getData('foo');
}
public function testImport()
{
$data = new Data();
$data->import($this->getSampleData());
$this->runSampleDataTests($data);
}
public function testImportData()
{
$data = new Data();
$data->importData(new Data($this->getSampleData()));
$this->runSampleDataTests($data);
}
public function testExport()
{
$data = new Data($this->getSampleData());
$this->assertEquals($this->getSampleData(), $data->export());
}
public function testOffsetExists()
{
$data = new Data($this->getSampleData());
foreach (
['a', 'i', 'b.d', 'b/d', 'f.g.h', 'f/g/h', 'h.i', 'h/i', 'b.d.d1', 'b/d/d1', 'n', 'n2/n', 'c.2'] as $existentKey
) {
$this->assertTrue(isset($data[$existentKey]));
}
foreach (
['p', 'b.b1', 'b/b1', 'b.c.C1', 'b/c/C1', 'h.i.I', 'h/i/I', 'b.d.d1.D1', 'b/d/d1/D1', 'c.4'] as $notExistentKey
) {
$this->assertFalse(isset($data[$notExistentKey]));
}
}
public function testOffsetGet()
{
$wrappedData = new Data([
'wrapped' => [
'sampleData' => $this->getSampleData()
],
]);
$data = $wrappedData->getData('wrapped.sampleData');
$this->assertEquals('A', $data['a']);
$this->assertEquals('B', $data['b.b']);
$this->assertEquals('B', $data['b/b']);
$this->assertEquals(['C1', 'C2', 'C3'], $data['b.c']);
$this->assertEquals(['C1', 'C2', 'C3'], $data['b/c']);
$this->assertEquals('D3', $data['b.d.d3']);
$this->assertEquals('D3', $data['b/d/d3']);
$this->assertEquals(['c1', 'c2', 'c3'], $data['c']);
$this->assertEquals('c3', $data['c.2']);
$this->assertNull($data['foo'], 'Foo should not exist');
$this->assertNull($data['f.g.h.i']);
$this->assertNull($data['f/g/h/i']);
$this->assertNull($data['n']);
$this->assertNull($data['n2/n']);
}
public function testOffsetSet()
{
$data = new Data();
$this->assertNull($data['a']);
$this->assertNull($data['b.c']);
$this->assertNull($data['d.e']);
$data['a'] = 'A';
$data['b/c'] = 'C';
$data['d.e'] = ['f' => 'F', 'g' => 'G'];
$data['foo'] = null;
$data['x.1'] = 'foo';
$this->assertEquals('A', $data['a']);
$this->assertEquals(['c' => 'C'], $data['b']);
$this->assertEquals('C', $data['b.c']);
$this->assertEquals('F', $data['d/e/f']);
$this->assertEquals(['e' => ['f' => 'F', 'g' => 'G']], $data['d']);
$this->assertNull($data['foo']);
$this->assertEquals([1 => 'foo'], $data['x']);
$this->assertEquals('foo', $data['x.1']);
$this->expectException(InvalidPathException::class);
$this->expectExceptionMessage('Path cannot be an empty string');
$data->set('', 'broken');
}
public function testOffsetUnset()
{
$data = new Data($this->getSampleData());
unset($data['a']);
unset($data['b/c']);
unset($data['b.d.d3']);
unset($data['d']);
unset($data['d.e.f']);
unset($data['empty.path']);
unset($data['c.2']);
$this->assertNull($data['a']);
$this->assertNull($data['b.c']);
$this->assertNull($data['b/d/d3']);
$this->assertNull(null);
$this->assertEquals('D2', $data['b.d.d2']);
$this->assertNull($data['c.2']);
$this->assertTrue($data->has('n'));
$this->assertNull($data->get('n'));
unset($data['n']);
$this->assertFalse($data->has('n'));
$this->assertTrue($data->has('n2/n'));
$this->assertNull($data->get('n2/n'));
unset($data['n2/n']);
$this->assertFalse($data->has('n2/n'));
$this->expectException(InvalidPathException::class);
$this->expectExceptionMessage('Path cannot be an empty string');
unset($data['']);
}
}
PK n[U8wu5 5 .scrutinizer.ymlnu ٘ filter:
excluded_paths: [tests/*]
build:
nodes:
analysis:
tests:
override:
- php-scrutinizer-run
checks:
php:
code_rating: true
remove_extra_empty_lines: true
remove_php_closing_tag: true
remove_trailing_whitespace: true
fix_use_statements:
remove_unused: true
preserve_multiple: false
preserve_blanklines: true
order_alphabetically: true
fix_php_opening_tag: true
fix_linefeed: true
fix_line_ending: true
fix_identation_4spaces: true
fix_doc_comments: true
tools:
external_code_coverage:
timeout: 1800
runs: 3
php_analyzer: true
php_code_coverage: false
php_code_sniffer:
config:
standard: PSR12
filter:
paths: ['src']
php_cpd:
enabled: true
excluded_dirs: [vendor, tests]
php_loc:
enabled: true
excluded_dirs: [vendor, tests]
php_pdepend: true
php_sim: true
PK n[U6w.. . LICENSEnu ٘ Copyright (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.
PK n[Ur
composer.jsonnu ٘ {
"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"
]
}
}
PK n[U|"ʼ psalm.xml.distnu ٘ PK n[Uq5E E README.mdnu ٘ PK n[U>e e
x .gitignorenu ٘ PK n[Ue src/Exception/DataException.phpnu ٘ PK n[U)ծ &