diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..2454616
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,11 @@
+; 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
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..7a6ef08
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,13 @@
+# 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
+/phpcs.xml.dist export-ignore
+/phpstan.neon.dist export-ignore
+/phpunit.xml.dist export-ignore
+/psalm.xml.dist export-ignore
+/tests export-ignore
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
new file mode 100644
index 0000000..665dc6c
--- /dev/null
+++ b/.github/CONTRIBUTING.md
@@ -0,0 +1,31 @@
+# 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**!
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 0000000..60c38ea
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,100 @@
+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'
+
+ 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"
+ run: composer require --no-update --dev phpunit/phpunit:^9.3
+ if: "matrix.php == '8.0'"
+
+ - run: composer update --no-progress ${{ matrix.composer-flags }}
+
+ - run: vendor/bin/phpunit --no-coverage
+ if: ${{ matrix.coverage == 'none' }}
+
+ - run: vendor/bin/phpunit --coverage-text
+ 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
diff --git a/.gitignore b/.gitignore
index 987e2a2..a0a4f20 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,8 @@
+.phpcs-cache
+.phpunit.result.cache
composer.lock
+phpcs.xml
+phpstan.neon
+phpunit.xml
+psalm.xml
vendor
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 7dc0a79..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-language: php
-
-php:
- - 7.0
- - 7.1
- - 7.2
-
-before_script:
- - wget -nc http://getcomposer.org/composer.phar
- - php composer.phar install
-
-script: ./vendor/bin/phpunit --coverage-text
diff --git a/README.md b/README.md
index ebcbbbb..9a73d6f 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ Given a deep data structure, access data by dot notation.
Requirements
------------
- * PHP (7.0+)
+ * PHP (7.1+)
> For PHP (5.3+) please refer to version `1.0`.
diff --git a/composer.json b/composer.json
index 9911c53..7dcc1a2 100644
--- a/composer.json
+++ b/composer.json
@@ -23,10 +23,13 @@
}
],
"require": {
- "php": "^7.0"
+ "php": "^7.1 || ^8.0"
},
"require-dev": {
- "phpunit/phpunit": "^6.0"
+ "phpstan/phpstan": "^0.12.42",
+ "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3",
+ "squizlabs/php_codesniffer": "^3.5",
+ "vimeo/psalm": "^3.14"
},
"autoload": {
"psr-4": {
@@ -42,5 +45,17 @@
"branch-alias": {
"dev-master": "2.0-dev"
}
+ },
+ "scripts": {
+ "phpcs": "phpcs",
+ "phpstan": "phpstan analyse",
+ "phpunit": "phpunit --no-coverage",
+ "psalm": "psalm",
+ "test": [
+ "@phpcs",
+ "@phpstan",
+ "@psalm",
+ "@phpunit"
+ ]
}
}
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
new file mode 100644
index 0000000..d67debb
--- /dev/null
+++ b/phpcs.xml.dist
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ src
+ tests
+
+
+
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
new file mode 100644
index 0000000..a1c637a
--- /dev/null
+++ b/phpstan.neon.dist
@@ -0,0 +1,4 @@
+parameters:
+ level: max
+ paths:
+ - src
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index ce9bb37..b2c4433 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -1,5 +1,14 @@
-
+
./tests
diff --git a/psalm.xml.dist b/psalm.xml.dist
new file mode 100644
index 0000000..729e9a9
--- /dev/null
+++ b/psalm.xml.dist
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/Data.php b/src/Data.php
index 1cec9a6..53ea64a 100644
--- a/src/Data.php
+++ b/src/Data.php
@@ -18,24 +18,24 @@ class Data implements DataInterface
/**
* Internal representation of data data
*
- * @var array
+ * @var array
*/
protected $data;
/**
* Constructor
*
- * @param array|null $data
+ * @param array $data
*/
- public function __construct(array $data = null)
+ public function __construct(array $data = [])
{
- $this->data = $data ?: [];
+ $this->data = $data;
}
/**
* {@inheritdoc}
*/
- public function append($key, $value = null)
+ public function append(string $key, $value = null): void
{
if (0 == strlen($key)) {
throw new RuntimeException("Key cannot be an empty string");
@@ -59,9 +59,9 @@ public function append($key, $value = null)
}
$endKey = array_pop($keyPath);
- for ( $i = 0; $i < count($keyPath); $i++ ) {
+ for ($i = 0; $i < count($keyPath); $i++) {
$currentKey =& $keyPath[$i];
- if ( ! isset($currentValue[$currentKey]) ) {
+ if (! isset($currentValue[$currentKey])) {
$currentValue[$currentKey] = [];
}
$currentValue =& $currentValue[$currentKey];
@@ -81,7 +81,7 @@ public function append($key, $value = null)
/**
* {@inheritdoc}
*/
- public function set($key, $value = null)
+ public function set(string $key, $value = null): void
{
if (0 == strlen($key)) {
throw new RuntimeException("Key cannot be an empty string");
@@ -97,7 +97,7 @@ public function set($key, $value = null)
}
$endKey = array_pop($keyPath);
- for ( $i = 0; $i < count($keyPath); $i++ ) {
+ for ($i = 0; $i < count($keyPath); $i++) {
$currentKey =& $keyPath[$i];
if (!isset($currentValue[$currentKey])) {
$currentValue[$currentKey] = [];
@@ -113,7 +113,7 @@ public function set($key, $value = null)
/**
* {@inheritdoc}
*/
- public function remove($key)
+ public function remove(string $key): void
{
if (0 == strlen($key)) {
throw new RuntimeException("Key cannot be an empty string");
@@ -129,7 +129,7 @@ public function remove($key)
}
$endKey = array_pop($keyPath);
- for ( $i = 0; $i < count($keyPath); $i++ ) {
+ for ($i = 0; $i < count($keyPath); $i++) {
$currentKey =& $keyPath[$i];
if (!isset($currentValue[$currentKey])) {
return;
@@ -141,15 +141,17 @@ public function remove($key)
/**
* {@inheritdoc}
+ *
+ * @psalm-mutation-free
*/
- public function get($key, $default = null)
+ public function get(string $key, $default = null)
{
$currentValue = $this->data;
$keyPath = explode('.', $key);
- for ( $i = 0; $i < count($keyPath); $i++ ) {
+ for ($i = 0; $i < count($keyPath); $i++) {
$currentKey = $keyPath[$i];
- if (!isset($currentValue[$currentKey]) ) {
+ if (!isset($currentValue[$currentKey])) {
return $default;
}
if (!is_array($currentValue)) {
@@ -163,13 +165,15 @@ public function get($key, $default = null)
/**
* {@inheritdoc}
+ *
+ * @psalm-mutation-free
*/
- public function has($key)
+ public function has(string $key): bool
{
$currentValue = &$this->data;
$keyPath = explode('.', $key);
- for ( $i = 0; $i < count($keyPath); $i++ ) {
+ for ($i = 0; $i < count($keyPath); $i++) {
$currentKey = $keyPath[$i];
if (
!is_array($currentValue) ||
@@ -185,8 +189,10 @@ public function has($key)
/**
* {@inheritdoc}
+ *
+ * @psalm-mutation-free
*/
- public function getData($key)
+ public function getData(string $key): DataInterface
{
$value = $this->get($key);
if (is_array($value) && Util::isAssoc($value)) {
@@ -199,7 +205,7 @@ public function getData($key)
/**
* {@inheritdoc}
*/
- public function import(array $data, $clobber = true)
+ public function import(array $data, bool $clobber = true): void
{
$this->data = Util::mergeAssocArray($this->data, $data, $clobber);
}
@@ -207,15 +213,17 @@ public function import(array $data, $clobber = true)
/**
* {@inheritdoc}
*/
- public function importData(DataInterface $data, $clobber = true)
+ public function importData(DataInterface $data, bool $clobber = true): void
{
$this->import($data->export(), $clobber);
}
/**
* {@inheritdoc}
+ *
+ * @psalm-mutation-free
*/
- public function export()
+ public function export(): array
{
return $this->data;
}
diff --git a/src/DataInterface.php b/src/DataInterface.php
index 3498f26..fa20196 100644
--- a/src/DataInterface.php
+++ b/src/DataInterface.php
@@ -19,7 +19,7 @@ interface DataInterface
* @param string $key
* @param mixed $value
*/
- public function append($key, $value = null);
+ public function append(string $key, $value = null): void;
/**
* Set a value for a key
@@ -27,14 +27,14 @@ public function append($key, $value = null);
* @param string $key
* @param mixed $value
*/
- public function set($key, $value = null);
+ public function set(string $key, $value = null): void;
/**
* Remove a key
*
* @param string $key
*/
- public function remove($key);
+ public function remove(string $key): void;
/**
* Get the raw value for a key
@@ -43,8 +43,10 @@ public function remove($key);
* @param mixed $default
*
* @return mixed
+ *
+ * @psalm-mutation-free
*/
- public function get($key, $default = null);
+ public function get(string $key, $default = null);
/**
* Check if the key exists
@@ -52,8 +54,10 @@ public function get($key, $default = null);
* @param string $key
*
* @return bool
+ *
+ * @psalm-mutation-free
*/
- public function has($key);
+ public function has(string $key): bool;
/**
* Get a data instance for a key
@@ -61,16 +65,18 @@ public function has($key);
* @param string $key
*
* @return DataInterface
+ *
+ * @psalm-mutation-free
*/
- public function getData($key);
+ public function getData(string $key): DataInterface;
/**
* Import data into existing data
*
- * @param array $data
- * @param bool $clobber
+ * @param array $data
+ * @param bool $clobber
*/
- public function import(array $data, $clobber = true);
+ public function import(array $data, bool $clobber = true): void;
/**
* Import data from an external data into existing data
@@ -78,12 +84,14 @@ public function import(array $data, $clobber = true);
* @param DataInterface $data
* @param bool $clobber
*/
- public function importData(DataInterface $data, $clobber = true);
+ public function importData(DataInterface $data, bool $clobber = true): void;
/**
* Export data as raw data
*
- * @return array
+ * @return array
+ *
+ * @psalm-mutation-free
*/
- public function export();
+ public function export(): array;
}
diff --git a/src/Util.php b/src/Util.php
index 546da22..a9cc2cc 100644
--- a/src/Util.php
+++ b/src/Util.php
@@ -19,25 +19,31 @@ class Util
* Note that this function will return true if an array is empty. Meaning
* empty arrays will be treated as if they are associative arrays.
*
- * @param array $arr
+ * @param array $arr
*
- * @return boolean
+ * @return bool
+ *
+ * @psalm-pure
*/
- public static function isAssoc(array $arr)
+ public static function isAssoc(array $arr): bool
{
- return (is_array($arr) && (!count($arr) || count(array_filter(array_keys($arr),'is_string')) == count($arr)));
+ return !count($arr) || count(array_filter(array_keys($arr), 'is_string')) == count($arr);
}
/**
* Merge contents from one associtative array to another
*
- * @param array $to
- * @param array $from
+ * @param mixed $to
+ * @param mixed $from
* @param bool $clobber
+ *
+ * @return mixed
+ *
+ * @psalm-pure
*/
public static function mergeAssocArray($to, $from, $clobber = true)
{
- if ( is_array($from) ) {
+ if (is_array($from)) {
foreach ($from as $k => $v) {
if (!isset($to[$k])) {
$to[$k] = $v;
diff --git a/tests/DataTest.php b/tests/DataTest.php
index d80e052..b3b1ad1 100644
--- a/tests/DataTest.php
+++ b/tests/DataTest.php
@@ -88,7 +88,7 @@ public function testAppend()
public function testSet()
{
- $data = new Data;
+ $data = new Data();
$this->assertNull($data->get('a'));
$this->assertNull($data->get('b.c'));
@@ -111,7 +111,7 @@ public function testSet()
public function testSetClobberStringInPath()
{
- $data = new Data;
+ $data = new Data();
$data->set('a.b.c', 'Should not be able to write to a.b.c.d.e');