diff --git a/.gitattributes b/.gitattributes
index ada1786..a3648c5 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -15,6 +15,7 @@
*.bat text
*.sql text
*.yml text
+*.neon text
# Ensure those won't be messed up with
@@ -25,12 +26,14 @@
# Ignore some meta files when creating an archive of this repository
+/.github export-ignore
/.editorconfig export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/phpunit.xml export-ignore
/phpcs.xml export-ignore
/phpmd.xml export-ignore
+/phpstan.neon export-ignore
/.travis.yml export-ignore
/.scrutinizer.yml export-ignore
diff --git a/.github/ISSUE_TEMPLATE/BUG_REPORT.md b/.github/ISSUE_TEMPLATE/BUG_REPORT.md
new file mode 100644
index 0000000..134ec49
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/BUG_REPORT.md
@@ -0,0 +1,23 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: bug
+assignees: ''
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+1. Go to '...'
+2. Execute ..
+3. See error
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md b/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md
new file mode 100644
index 0000000..bbcbbe7
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md
@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..968a845
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,7 @@
+| Q | A
+| ------------- | ---
+| Is bugfix? | yes/no
+| New feature? | yes/no
+| Breaks BC? | yes/no
+| Tests pass? | yes/no
+| Fixed issues | comma-separated list of tickets # fixed by the PR, if any
diff --git a/.readthedocs.yml b/.readthedocs.yml
new file mode 100644
index 0000000..a36db8a
--- /dev/null
+++ b/.readthedocs.yml
@@ -0,0 +1,23 @@
+# .readthedocs.yml
+# Read the Docs configuration file
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+
+# Required
+version: 2
+
+# Build documentation in the docs/ directory with Sphinx
+sphinx:
+ configuration: docs/conf.py
+
+# Build documentation with MkDocs
+#mkdocs:
+# configuration: mkdocs.yml
+
+# Optionally build your docs in additional formats such as PDF and ePub
+formats: all
+
+# Optionally set the version of Python and requirements required to build your docs
+python:
+ version: 3.7
+ install:
+ - requirements: docs/requirements.txt
diff --git a/.travis.yml b/.travis.yml
index ecca587..3621a36 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -20,3 +20,5 @@ script:
after_script:
- bash <(curl -s https://codecov.io/bash) -f "build/phpunit/coverage/clover/index.xml"
+ - wget https://scrutinizer-ci.com/ocular.phar
+ - php ocular.phar code-coverage:upload --format=php-clover build/phpunit/coverage/clover/index.xml
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 48e307a..6993dcd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,24 @@
DotArray Change Log
=====================
+1.1.0 March 02, 2019
+-----------------------------
+- Remove DotArray::uniqueIdentifier
+- Remove DotPathTrait - parts have been moved into DotArray
+- Code Standard Improvements
+- Refactoring DotArray:
+- More Tests.
+
+1.0.5 December 30, 2018
+-----------------------------
+
+- Refactoring DotArray:
+ - Using a Trait (DotFilteringTrait) to split code in more organized units.
+- Refactoring DotPathTrait::flatten
+- using PHPStan.
+- Updating composer.json scripts to use PHPStan.
+- More Tests.
+
1.0.4 December 30, 2018
-----------------------------
diff --git a/README.md b/README.md
index 28345dc..fa542b3 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# DotArray - Sail through array using the dot notation
+# PHP Dot-Array :: Sail through array using the dot notation
~ Enjoy your :coffee: ~
@@ -8,14 +8,13 @@ Accessing PHP Arrays via DOT notation is easy as:
```php
DotArray::create(['config' => ['some.dotted.key' => 'value']])->get('config.{some.dotted.key}')
```
-[][php-site]
-[][packagist]
-[][packagist]
-[](https://travis-ci.org/binary-cube/dot-array)
-[](https://www.codacy.com/app/microThread/dot-array)
-[](https://scrutinizer-ci.com/g/binary-cube/dot-array/?branch=master)
-[](https://scrutinizer-ci.com/g/binary-cube/dot-array/?branch=master)
-[][license]
+[![Minimum PHP Version `PHP >= 7.1`][ico-php-require]][link-php-site]
+[![Latest Stable Version][ico-version]][link-packagist]
+[![Total Downloads][ico-downloads]][link-downloads]
+[![Build Status][ico-travis]][link-travis]
+[![Code Coverage][ico-scrutinizer]][link-scrutinizer]
+[![Quality Score][ico-code-quality]][link-code-quality]
+[![License][ico-license]][link-license]
-----
@@ -103,6 +102,8 @@ DotArray::create(['config' => ['some.dotted.key' => 'value']])->get('config.{som
```
- **clear** *(empty array <=> [])*:
+ > Set the contents of a given key or keys to the given value (default is empty array).
+
- ```php
$dot->clear('books.{sci-fi & fantasy}');
$dot->clear('books.{sci-fi & fantasy}', null);
@@ -111,14 +112,31 @@ DotArray::create(['config' => ['some.dotted.key' => 'value']])->get('config.{som
// Multiple keys.
$dot->clear([
'books.{sci-fi & fantasy}',
- 'books.{childre\'s books}'
+ 'books.{children\'s books}'
]);
// Vanilla PHP.
$dot['books.{sci-fi & fantasy}'] = [];
```
+- **delete** *(unset(...))*:
+ > Delete the given key or keys.
+
+ - ```php
+ $dot->delete('books.{sci-fi & fantasy}');
+ $dot->delete('books.{sci-fi & fantasy}.0.name');
+ $dot->delete(['books.{sci-fi & fantasy}.0', 'books.{children\'s books}.0']);
+ ```
+
- **merge**:
+ > Merges one or more arrays into master recursively.
+ If each array has an element with the same string key value, the latter
+ will overwrite the former (different from array_merge_recursive).
+ Recursive merging will be conducted if both arrays have an element of array
+ type and are having the same key.
+ For integer-keyed elements, the elements from the latter array will
+ be appended to the former array.
+
- ```php
// Example 1.
$dot->merge(['key_1' => ['some_key' => 'some_value']]);
@@ -137,31 +155,22 @@ DotArray::create(['config' => ['some.dotted.key' => 'value']])->get('config.{som
);
```
-- **delete** *(unset(...))*:
- - ```php
- $dot->delete('books.{sci-fi & fantasy}');
- $dot->delete('books.{sci-fi & fantasy}.0.name');
- $dot->delete(['books.{sci-fi & fantasy}.0', 'books.{childre\'s books}.0']);
- ```
-
- **find**:
+ > Find the first item in an array that passes the truth test, otherwise return false.
+ The signature of the callable must be: `function ($value, $key)`.
+
- ```php
- /*
- Find the first item in an array that passes the truth test, otherwise return false
- The signature of the callable must be: `function ($value, $key)`.
- */
- $book = $dot->get('books.{childre\'s books}')->find(function ($value, $key) {
+ $book = $dot->get('books.{children\'s books}')->find(function ($value, $key) {
return $value['price'] > 0;
});
```
- **filter**:
+ > Use a callable function to filter through items.
+ The signature of the callable must be: `function ($value, $key)`
+
- ```php
- /*
- Use a callable function to filter through items.
- The signature of the callable must be: `function ($value, $key)`
- */
- $books = $dot->get('books.{childre\'s books}')->filter(function ($value, $key) {
+ $books = $dot->get('books.{children\'s books}')->filter(function ($value, $key) {
return $value['name'] === 'Harry Potter and the Order of the Phoenix';
});
@@ -186,13 +195,13 @@ DotArray::create(['config' => ['some.dotted.key' => 'value']])->get('config.{som
- [ not-between ]
*/
// Example 1.
- $books = $dot->get('books.{childre\'s books}')->filterBy('price', 'between', 5, 12);
+ $books = $dot->get('books.{children\'s books}')->filterBy('price', 'between', 5, 12);
// Example 2.
- $books = $dot->get('books.{childre\'s books}')->filterBy('price', '>', 10);
+ $books = $dot->get('books.{children\'s books}')->filterBy('price', '>', 10);
// Example 3.
- $books = $dot->get('books.{childre\'s books}')->filterBy('price', 'in', [8.5, 15.49]);
+ $books = $dot->get('books.{children\'s books}')->filterBy('price', 'in', [8.5, 15.49]);
```
- **where**:
@@ -218,24 +227,24 @@ DotArray::create(['config' => ['some.dotted.key' => 'value']])->get('config.{som
*/
// Example 1. (using the signature: [property, comparisonOperator, ...value])
- $books = $dot->get('books.{childre\'s books}')->where(['price', 'between', 5, 12]);
+ $books = $dot->get('books.{children\'s books}')->where(['price', 'between', 5, 12]);
// Example 2. (using the signature: [property, comparisonOperator, ...value])
- $books = $dot->get('books.{childre\'s books}')->where(['price', '>', 10]);
+ $books = $dot->get('books.{children\'s books}')->where(['price', '>', 10]);
// Example 3. (using the signature: [property, comparisonOperator, ...value])
- $books = $dot->get('books.{childre\'s books}')->where(['price', 'in', [8.5, 15.49]]);
+ $books = $dot->get('books.{children\'s books}')->where(['price', 'in', [8.5, 15.49]]);
// Example 4. (using the signature: \Closure)
- $books = $dot->get('books.{childre\'s books}')->where(function ($value, $key) {
+ $books = $dot->get('books.{children\'s books}')->where(function ($value, $key) {
return $value['name'] === 'Harry Potter and the Order of the Phoenix';
});
```
- **toArray**:
- - ```php
- // Getting the internal raw array.
+ > Getting the internal raw array.
+ - ```php
// Example 1.
$dot->toArray();
@@ -244,9 +253,9 @@ DotArray::create(['config' => ['some.dotted.key' => 'value']])->get('config.{som
```
- **toJson**:
- - ```php
- // Getting the internal raw array as JSON.
+ > Getting the internal raw array as JSON.
+ - ```php
// Example 1.
$dot->toJson();
@@ -255,17 +264,25 @@ DotArray::create(['config' => ['some.dotted.key' => 'value']])->get('config.{som
```
- **toFlat**:
+ > Flatten the internal array using the dot delimiter,
+ also the keys are wrapped inside {key} (1 x curly braces).
+
- ```php
$dot = DotArray::create(
[
'a' => [
'b' => 'value',
],
-
+
'b' => [
1,
2,
3,
+ 'array' => [
+ 1,
+ 2,
+ 3,
+ ]
],
]
);
@@ -274,12 +291,14 @@ DotArray::create(['config' => ['some.dotted.key' => 'value']])->get('config.{som
/*
The output will be an array:
-
[
- '{{a}}.{{b}}' => 'value',
- '{{b}}.{{0}}' => 1,
- '{{b}}.{{1}}' => 2,
- '{{b}}.{{2}}' => 3,
+ '{a}.{b}' => 'value',
+ '{b}.{0}' => 1,
+ '{b}.{1}' => 2,
+ '{b}.{2}' => 3,
+ '{b}.{array}.{0}' => 1,
+ '{b}.{array}.{1}' => 2,
+ '{b}.{array}.{2}' => 3,
],
*/
```
@@ -318,7 +337,7 @@ $dummyArray = [
],
],
- 'childre\'s books' =>
+ 'children\'s books' =>
[
[
'name' => 'Harry Potter and the Order of the Phoenix',
@@ -358,7 +377,7 @@ $dummyArray = [
Have a bug or a feature request?
Please first read the issue guidelines and search for existing and closed issues.
-If your problem or idea is not addressed yet, [please open a new issue][new-issue].
+If your problem or idea is not addressed yet, [please open a new issue][link-new-issue].
@@ -372,14 +391,14 @@ In the interest of fostering an open and welcoming environment,
we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone,
regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality,
personal appearance, race, religion, or sexual identity and orientation.
-[Read the full Code of Conduct][code-of-conduct].
+[Read the full Code of Conduct][link-code-of-conduct].
#### Versioning
-Through the development of new versions, we're going use the [Semantic Versioning][semver].
+Through the development of new versions, we're going use the [Semantic Versioning][link-semver].
Example: `1.0.0`.
- Major release: increment the first digit and reset middle and last digits to zero. Introduces major changes that might break backward compatibility. E.g. 2.0.0
@@ -393,25 +412,38 @@ Example: `1.0.0`.
* **Banciu N. Cristian Mihai**
-See also the list of [contributors][contributors] who participated in this project.
+See also the list of [contributors][link-contributors] who participated in this project.
## License
-This project is licensed under the MIT License - see the [LICENSE][license] file for details.
+This project is licensed under the MIT License - see the [LICENSE][link-license] file for details.
+
-[domain]: https://binary-cube.com
-[homepage]: https://binary-cube.com
-[git-source]: https://github.com/binary-cube/dot-array
-[php-site]: https://php.net
-[semver]: https://semver.org
-[code-of-conduct]: https://github.com/binary-cube/dot-array/blob/master/code-of-conduct.md
-[license]: https://github.com/binary-cube/dot-array/blob/master/LICENSE
-[contributors]: https://github.com/binary-cube/dot-array/graphs/contributors
-[new-issue]: https://github.com/binary-cube/dot-array/issues/new
-[packagist]: https://packagist.org/packages/binary-cube/dot-array
+[ico-php-require]: https://img.shields.io/badge/php-%3E%3D%207.1-8892BF.svg?style=flat-square
+[ico-version]: https://img.shields.io/packagist/v/binary-cube/dot-array.svg?style=flat-square
+[ico-downloads]: https://img.shields.io/packagist/dt/binary-cube/dot-array.svg?style=flat-square
+[ico-travis]: https://img.shields.io/travis/binary-cube/dot-array/master.svg?style=flat-square
+[ico-scrutinizer]: https://img.shields.io/scrutinizer/coverage/g/binary-cube/dot-array.svg?style=flat-square
+[ico-code-quality]: https://img.shields.io/scrutinizer/g/binary-cube/dot-array.svg?style=flat-square
+[ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square
+
+[link-domain]: https://binary-cube.com
+[link-homepage]: https://binary-cube.com
+[link-git-source]: https://github.com/binary-cube/dot-array
+[link-packagist]: https://packagist.org/packages/binary-cube/dot-array
+[link-downloads]: https://packagist.org/packages/binary-cube/dot-array
+[link-php-site]: https://php.net
+[link-semver]: https://semver.org
+[link-code-of-conduct]: https://github.com/binary-cube/dot-array/blob/master/code-of-conduct.md
+[link-license]: https://github.com/binary-cube/dot-array/blob/master/LICENSE
+[link-contributors]: https://github.com/binary-cube/dot-array/graphs/contributors
+[link-new-issue]: https://github.com/binary-cube/dot-array/issues/new
+[link-travis]: https://travis-ci.org/binary-cube/dot-array
+[link-scrutinizer]: https://scrutinizer-ci.com/g/binary-cube/dot-array/code-structure
+[link-code-quality]: https://scrutinizer-ci.com/g/binary-cube/dot-array
diff --git a/composer.json b/composer.json
index 4ccf1fc..46eff2e 100644
--- a/composer.json
+++ b/composer.json
@@ -1,9 +1,9 @@
{
"name": "binary-cube/dot-array",
- "description": "Navigate through array using JSON Dot notation path.",
+ "description": "PHP Dot-Array :: Sail through array using the dot notation",
"keywords": [
- "DotArray", "dot-array", "array", "dot", "php-array", "php-array-json-path"
+ "DotArray", "dot-array", "array", "dot", "php-array", "php-array-json-path", "php"
],
"type": "library",
@@ -32,7 +32,8 @@
"require-dev": {
"squizlabs/php_codesniffer": "3.*",
"phpunit/phpunit": "~7.0",
- "phpmd/phpmd": "*"
+ "phpmd/phpmd": "*",
+ "phpstan/phpstan": "*"
},
"suggest": {
@@ -69,12 +70,14 @@
"scripts": {
"check": [
"@cs-check",
+ "@phpstan",
"@tests"
],
"generate-reports": [
"@create-folders",
"@cs-report",
+ "@phpstan-report",
"@phpmd-report",
"@tests-report-html",
"@tests-report-xml",
@@ -87,14 +90,16 @@
"cs-check": "phpcs",
"cs-fix": "phpcbf",
+ "phpstan": "phpstan analyze src --no-progress",
"phpmd": "phpmd src text phpmd.xml.dist",
"tests": "phpunit",
- "cs-report": "phpcs --report=json --report-file=build/phpcs-report.json || exit 0;",
- "phpmd-report": "phpmd src xml phpmd.xml.dist --reportfile build/phpmd-report.xml || exit 0;",
- "tests-report-html": "phpunit --coverage-html build/phpunit/coverage/html || exit 0;",
- "tests-report-xml": "phpunit --coverage-xml build/phpunit/coverage/xml || exit 0;",
- "tests-report-clover": "phpunit --coverage-clover build/phpunit/coverage/clover/index.xml || exit 0;"
+ "cs-report": "phpcs --report=json --report-file=build/phpcs-report.json || exit 0;",
+ "phpstan-report": "phpstan analyze src --error-format=checkstyle > build/phpstan-check-style.xml --no-progress || exit 0;",
+ "phpmd-report": "phpmd src xml phpmd.xml.dist --reportfile build/phpmd-report.xml || exit 0;",
+ "tests-report-html": "phpunit --coverage-html build/phpunit/coverage/html || exit 0;",
+ "tests-report-xml": "phpunit --coverage-xml build/phpunit/coverage/xml || exit 0;",
+ "tests-report-clover": "phpunit --coverage-clover build/phpunit/coverage/clover/index.xml || exit 0;"
},
"scripts-descriptions": {
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 0000000..fe09287
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,70 @@
+# Configuration file for the Sphinx documentation builder.
+#
+# This file only contains a selection of the most common options. For a full
+# list see the documentation:
+# http://www.sphinx-doc.org/en/master/config
+
+# -- Path setup --------------------------------------------------------------
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+# import os
+# import sys
+# sys.path.insert(0, os.path.abspath('.'))
+
+
+# -- Project information -----------------------------------------------------
+
+project = 'DotArray'
+copyright = '2019, Banciu N. Cristian Mihai'
+
+# The full version, including alpha/beta/rc tags
+release = '1.0'
+
+
+# -- General configuration ---------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+ 'sphinxcontrib.phpdomain'
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
+exclude_patterns = ['_build']
+
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+#
+html_theme = 'sphinx_rtd_theme'
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+
+# -- Custom -------------------------------------------------
+import sphinx_rtd_theme
+html_theme = "sphinx_rtd_theme"
+html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
+
+# Set up PHP syntax highlights
+from sphinx.highlighting import lexers
+from pygments.lexers.web import PhpLexer
+
+lexers["php"] = PhpLexer(startinline=True, linenos=1)
+lexers["php-annotations"] = PhpLexer(startinline=True, linenos=1)
+primary_domain = "php"
+
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 0000000..662b240
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,62 @@
+.. title:: PHP DotArray Library:: Sail through array using the DOT notation
+
+====================
+DotArray
+====================
+
+.. raw:: html
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+DotArray is a simple PHP Library that can access an array in a dotted manner.
+
+- Easy to use.
+- Support to access complex keys that are having dot in the name.
+- Fluent access.
+- Possibility to search & filter items.
+- Dot access can, also, works in the "old school" array way (Vanilla PHP).
+
+.. code-block:: php
+
+ DotArray::create(['config' => ['some.dotted.key' => 'value']])->get('config.{some.dotted.key}')
+
+User Guide
+==========
+
+.. toctree::
+ :maxdepth: 2
+
+ overview
+ quickstart
diff --git a/docs/overview.rst b/docs/overview.rst
new file mode 100644
index 0000000..3915814
--- /dev/null
+++ b/docs/overview.rst
@@ -0,0 +1,108 @@
+========
+Overview
+========
+
+Requirements
+============
+
+#. PHP 7.1
+
+.. _installation:
+
+
+Installation
+============
+
+The recommended way to install DotArray is with
+`Composer `_. Composer is a dependency management tool
+for PHP that allows you to declare the dependencies your project needs and
+installs them into your project.
+
+.. code-block:: bash
+
+ # Install Composer
+ curl -sS https://getcomposer.org/installer | php
+
+You can add DotArray as a dependency using the composer.phar CLI:
+
+.. code-block:: bash
+
+ php composer.phar require binary-cube/dot-array
+
+Alternatively, you can specify DotArray as a dependency in your project's
+existing composer.json file:
+
+.. code-block:: js
+
+ {
+ "require": {
+ "binary-cube/dot-array": "*"
+ }
+ }
+
+After installing, you need to require Composer's autoloader:
+
+.. code-block:: php
+
+ require 'vendor/autoload.php';
+
+You can find out more on how to install Composer, configure autoloading, and
+other best-practices for defining dependencies at `getcomposer.org `_.
+
+
+License
+=======
+
+Licensed using the `MIT license `_.
+
+ Copyright (c) 2018 Binary Cube
+
+ 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.
+
+
+Bugs and feature requests
+=========================
+
+Have a bug or a feature request?
+Please first read the issue guidelines and search for existing and closed issues.
+If your problem or idea is not addressed yet, `please open a new issue `_.
+
+
+Contributing
+============
+
+All contributions are more than welcomed.
+Contributions may close an issue, fix a bug (reported or not reported), add new design blocks,
+improve the existing code, add new feature, and so on.
+In the interest of fostering an open and welcoming environment,
+we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone,
+regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality,
+personal appearance, race, religion, or sexual identity and orientation.
+`Read the full Code of Conduct `_.
+
+
+Versioning
+===========
+
+Through the development of new versions, we're going use the `Semantic Versioning `_.
+
+Example: `1.0.0`.
+- Major release: increment the first digit and reset middle and last digits to zero. Introduces major changes that might break backward compatibility. E.g. 2.0.0
+- Minor release: increment the middle digit and reset last digit to zero. It would fix bugs and also add new features without breaking backward compatibility. E.g. 1.1.0
+- Patch release: increment the third digit. It would fix bugs and keep backward compatibility. E.g. 1.0.1
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/docs/quickstart.rst
@@ -0,0 +1 @@
+
diff --git a/docs/requirements.txt b/docs/requirements.txt
new file mode 100644
index 0000000..855d66c
--- /dev/null
+++ b/docs/requirements.txt
@@ -0,0 +1,3 @@
+Sphinx>=2.1.0
+sphinx_rtd_theme>=0.4.0
+sphinxcontrib-phpdomain>=0.6.1
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
index f03c800..f73483f 100644
--- a/phpcs.xml.dist
+++ b/phpcs.xml.dist
@@ -1,7 +1,7 @@
-
+
- Base Coding Standards
+ Base Coding Standard
@@ -12,12 +12,12 @@
*/vendor/*
-
+
-
+
@@ -156,12 +156,13 @@
-
-
-
+
+
+
+
@@ -212,8 +213,10 @@
-
-
+
+
+ error
+
@@ -227,9 +230,6 @@
-
-
-
@@ -420,7 +420,7 @@
-->
-
+
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
new file mode 100644
index 0000000..5f962ab
--- /dev/null
+++ b/phpstan.neon.dist
@@ -0,0 +1,2 @@
+includes:
+ - vendor/phpstan/phpstan/conf/config.levelmax.neon
\ No newline at end of file
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 8b08e86..321e6af 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -14,8 +14,8 @@
processIsolation="false">
-
- ./tests
+
+ ./tests/Unit
diff --git a/src/DotArray.php b/src/DotArray.php
index bef16f1..2ee36fb 100644
--- a/src/DotArray.php
+++ b/src/DotArray.php
@@ -19,14 +19,12 @@ class DotArray implements
{
/* Traits. */
- use DotPathTrait;
+ use DotFilteringTrait;
- /**
- * Unique object identifier.
- *
- * @var string
- */
- protected $uniqueIdentifier;
+ private const TEMPLATE_PATTERN = '#(?|(?|[%s](.*?)[%s])|(.*?))(?:$|\.+)#i';
+ private const WRAP_KEY = '{%s}';
+ private const TOKEN_START = ['\'', '\"', '\[', '\(', '\{'];
+ private const TOKEN_END = ['\'', '\"', '\]', '\)', '\}'];
/**
* Stores the original data.
@@ -35,7 +33,6 @@ class DotArray implements
*/
protected $items;
-
/**
* Creates an DotArray object.
*
@@ -48,7 +45,6 @@ public static function create($items)
return (new static($items));
}
-
/**
* @param string $json
*
@@ -59,6 +55,130 @@ public static function createFromJson($json)
return static::create(\json_decode($json, true));
}
+ /**
+ * Getting the path pattern.
+ *
+ * Allowed tokens for more complex paths: '', "", [], (), {}
+ * Examples:
+ *
+ * - foo.bar
+ * - foo.'bar'
+ * - foo."bar"
+ * - foo.[bar]
+ * - foo.(bar)
+ * - foo.{bar}
+ *
+ * Or more complex:
+ * - foo.{bar}.[component].{version.1.0}
+ *
+ * @return string
+ */
+ protected static function pathPattern()
+ {
+ return (
+ vsprintf(
+ self::TEMPLATE_PATTERN,
+ [
+ \implode('', self::TOKEN_START),
+ \implode('', self::TOKEN_END),
+ ]
+ )
+ );
+ }
+
+ /**
+ * Converts dot string path to segments.
+ *
+ * @param string $path
+ *
+ * @return array
+ */
+ final protected static function pathToSegments($path)
+ {
+ $path = \trim($path, " \t\n\r\0\x0B\.");
+ $segments = [];
+ $matches = [];
+
+ if (\mb_strlen($path, 'UTF-8') === 0) {
+ return [];
+ }
+
+ \preg_match_all(self::pathPattern(), $path, $matches);
+
+ if (!empty($matches[1])) {
+ $matches = $matches[1];
+
+ $segments = \array_filter(
+ $matches,
+ function ($match) {
+ return (\mb_strlen($match, 'UTF-8') > 0);
+ }
+ );
+ }
+
+ unset($path, $matches);
+
+ return (empty($segments) ? [] : $segments);
+ }
+
+ /**
+ * Wrap a given string into special characters.
+ *
+ * @param string $key
+ *
+ * @return string
+ */
+ final protected static function wrapSegmentKey($key)
+ {
+ return \vsprintf(self::WRAP_KEY, [$key]);
+ }
+
+ /**
+ * @param array $segments
+ *
+ * @return string
+ */
+ final protected static function segmentsToKey(array $segments)
+ {
+ return (
+ \implode(
+ '.',
+ \array_map(
+ function ($segment) {
+ return self::wrapSegmentKey($segment);
+ },
+ $segments
+ )
+ )
+ );
+ }
+
+ /**
+ * Flatten the internal array using the dot delimiter,
+ * also the keys are wrapped inside {key} (1 x curly braces).
+ *
+ * @param array $items
+ * @param array $prepend
+ *
+ * @return array
+ */
+ final protected static function flatten(array $items, $prepend = [])
+ {
+ $flatten = [];
+
+ foreach ($items as $key => $value) {
+ if (\is_array($value) && !empty($value)) {
+ $flatten = \array_merge($flatten, self::flatten($value, \array_merge($prepend, [$key])));
+ continue;
+ }
+
+ $segmentsToKey = self::segmentsToKey(\array_merge($prepend, [$key]));
+
+ $flatten[$segmentsToKey] = $value;
+ }
+
+ return $flatten;
+ }
/**
* Return the given items as an array
@@ -67,7 +187,7 @@ public static function createFromJson($json)
*
* @return array
*/
- protected static function normalize($items)
+ final protected static function normalize($items)
{
if ($items instanceof self) {
$items = $items->toArray();
@@ -76,25 +196,24 @@ protected static function normalize($items)
if (\is_array($items)) {
foreach ($items as $k => $v) {
if (\is_array($v) || $v instanceof self) {
- $v = static::normalize($v);
+ $v = self::normalize($v);
}
$items[$k] = $v;
}
}
- return (array) (empty($items) ? [] : $items);
+ return (array) $items;
}
-
/**
* @param array|DotArray|mixed $array1
* @param null|array|DotArray|mixed $array2
*
* @return array
*/
- protected static function mergeRecursive($array1, $array2 = null)
+ final protected static function mergeRecursive($array1, $array2 = null)
{
- $args = static::normalize(\func_get_args());
+ $args = self::normalize(\func_get_args());
$res = \array_shift($args);
while (!empty($args)) {
@@ -105,7 +224,7 @@ protected static function mergeRecursive($array1, $array2 = null)
}
if (\is_array($v) && isset($res[$k]) && \is_array($res[$k])) {
- $v = static::mergeRecursive($res[$k], $v);
+ $v = self::mergeRecursive($res[$k], $v);
}
$res[$k] = $v;
@@ -115,102 +234,6 @@ protected static function mergeRecursive($array1, $array2 = null)
return $res;
}
-
- /**
- * List with internal operators and the associated callbacks.
- *
- * @return array
- */
- protected static function operators()
- {
- return [
- [
- 'tokens' => ['=', '==', 'eq'],
- 'closure' => function ($item, $property, $value) {
- return $item[$property] == $value[0];
- },
- ],
-
- [
- 'tokens' => ['===', 'i'],
- 'closure' => function ($item, $property, $value) {
- return $item[$property] === $value[0];
- },
- ],
-
- [
- 'tokens' => ['!=', 'ne'],
- 'closure' => function ($item, $property, $value) {
- return $item[$property] != $value[0];
- },
- ],
-
- [
- 'tokens' => ['!==', 'ni'],
- 'closure' => function ($item, $property, $value) {
- return $item[$property] !== $value[0];
- },
- ],
-
- [
- 'tokens' => ['<', 'lt'],
- 'closure' => function ($item, $property, $value) {
- return $item[$property] < $value[0];
- },
- ],
-
- [
- 'tokens' => ['>', 'gt'],
- 'closure' => function ($item, $property, $value) {
- return $item[$property] > $value[0];
- },
- ],
-
- [
- 'tokens' => ['<=', 'lte'],
- 'closure' => function ($item, $property, $value) {
- return $item[$property] <= $value[0];
- },
- ],
-
- [
- 'tokens' => ['>=', 'gte'],
- 'closure' => function ($item, $property, $value) {
- return $item[$property] >= $value[0];
- },
- ],
-
- [
- 'tokens' => ['in', 'contains'],
- 'closure' => function ($item, $property, $value) {
- return \in_array($item[$property], (array) $value, true);
- },
- ],
-
- [
- 'tokens' => ['not-in', 'not-contains'],
- 'closure' => function ($item, $property, $value) {
- return !\in_array($item[$property], (array) $value, true);
- },
- ],
-
- [
- 'tokens' => ['between'],
- 'closure' => function ($item, $property, $value) {
- return ($item[$property] >= $value[0] && $item[$property] <= $value[1]);
- },
- ],
-
- [
- 'tokens' => ['not-between'],
- 'closure' => function ($item, $property, $value) {
- return ($item[$property] < $value[0] || $item[$property] > $value[1]);
- },
- ],
- ];
- }
-
-
/**
* DotArray Constructor.
*
@@ -218,21 +241,17 @@ protected static function operators()
*/
public function __construct($items = [])
{
- $this->items = static::normalize($items);
+ $this->items = self::normalize($items);
}
-
/**
* DotArray Destructor.
*/
public function __destruct()
{
- unset($this->uniqueIdentifier);
unset($this->items);
- unset($this->nestedPathPattern);
}
-
/**
* Call object as function.
*
@@ -245,27 +264,6 @@ public function __invoke($key = null)
return $this->get($key);
}
-
- /**
- * @return string
- */
- public function uniqueIdentifier()
- {
- if (empty($this->uniqueIdentifier)) {
- $this->uniqueIdentifier = \vsprintf(
- '{%s}.{%s}.{%s}',
- [
- static::class,
- \uniqid('', true),
- \microtime(true),
- ]
- );
- }
-
- return $this->uniqueIdentifier;
- }
-
-
/**
* Merges one or more arrays into master recursively.
* If each array has an element with the same string key value, the latter
@@ -286,32 +284,25 @@ public function merge($array)
[
$this, 'mergeRecursive',
],
- \array_merge(
- [$this->items],
- \func_get_args()
- )
+ \array_values(\array_merge([$this->items], \func_get_args()))
);
return $this;
}
-
/**
- * @param string $key
- * @param mixed $default
+ * @param string|null|mixed $key
+ * @param mixed $default
*
* @return array|mixed
*/
- protected function &read($key, $default)
+ protected function &read($key = null, $default = null)
{
- $segments = static::pathToSegments($key);
+ $segments = self::pathToSegments($key);
$items = &$this->items;
foreach ($segments as $segment) {
- if (
- !\is_array($items)
- || !\array_key_exists($segment, $items)
- ) {
+ if (!\is_array($items) || !\array_key_exists($segment, $items)) {
return $default;
}
@@ -323,7 +314,6 @@ protected function &read($key, $default)
return $items;
}
-
/**
* @param string $key
* @param mixed $value
@@ -332,7 +322,7 @@ protected function &read($key, $default)
*/
protected function write($key, $value)
{
- $segments = static::pathToSegments($key);
+ $segments = self::pathToSegments($key);
$count = \count($segments);
$items = &$this->items;
@@ -340,10 +330,7 @@ protected function write($key, $value)
$segment = $segments[$i];
if (
- (
- !isset($items[$segment])
- || !\is_array($items[$segment])
- )
+ (!isset($items[$segment]) || !\is_array($items[$segment]))
&& ($i < ($count - 1))
) {
$items[$segment] = [];
@@ -352,15 +339,16 @@ protected function write($key, $value)
$items = &$items[$segment];
}
- unset($segments, $count);
-
if (\is_array($value) || $value instanceof self) {
- $value = static::normalize($value);
+ $value = self::normalize($value);
}
$items = $value;
- }
+ if (!\is_array($this->items)) {
+ $this->items = self::normalize($this->items);
+ }
+ }
/**
* Delete the given key or keys.
@@ -371,19 +359,17 @@ protected function write($key, $value)
*/
protected function remove($key)
{
- $segments = static::pathToSegments($key);
+ $segments = self::pathToSegments($key);
$count = \count($segments);
$items = &$this->items;
for ($i = 0; $i < $count; $i++) {
$segment = $segments[$i];
- // Nothing to unset.
if (!\array_key_exists($segment, $items)) {
break;
}
- // Last item, time to unset.
if ($i === ($count - 1)) {
unset($items[$segment]);
break;
@@ -391,11 +377,8 @@ protected function remove($key)
$items = &$items[$segment];
}
-
- unset($segments, $count);
}
-
/**
* @param string $key
*
@@ -403,12 +386,11 @@ protected function remove($key)
*/
public function has($key)
{
- $identifier = $this->uniqueIdentifier();
+ $identifier = \uniqid(static::class, true);
return ($identifier !== $this->read($key, $identifier));
}
-
/**
* Check if a given key contains empty values (null, [], 0, false)
*
@@ -418,12 +400,9 @@ public function has($key)
*/
public function isEmpty($key = null)
{
- $items = $this->read($key, null);
-
- return empty($items);
+ return empty($this->read($key, null));
}
-
/**
* @param null|string $key
* @param null|mixed $default
@@ -441,16 +420,15 @@ public function get($key = null, $default = null)
return $items;
}
-
/**
* Set the given value to the provided key or keys.
*
- * @param string|array $keys
- * @param mixed $value
+ * @param null|string|array $keys
+ * @param mixed|mixed $value
*
* @return static
*/
- public function set($keys, $value)
+ public function set($keys = null, $value = [])
{
$keys = (array) (!isset($keys) ? [$keys] : $keys);
@@ -461,7 +439,6 @@ public function set($keys, $value)
return $this;
}
-
/**
* Delete the given key or keys.
*
@@ -480,12 +457,11 @@ public function delete($keys)
return $this;
}
-
/**
* Set the contents of a given key or keys to the given value (default is empty array).
*
* @param null|string|array $keys
- * @param array $value
+ * @param array|mixed $value
*
* @return static
*/
@@ -500,182 +476,17 @@ public function clear($keys = null, $value = [])
return $this;
}
-
- /**
- * Find the first item in an array that passes the truth test, otherwise return false
- * The signature of the callable must be: `function ($value, $key)`
- *
- * @param \Closure $closure
- *
- * @return false|mixed
- */
- public function find(\Closure $closure)
- {
- foreach ($this->items as $key => $value) {
- if ($closure($value, $key)) {
- if (\is_array($value)) {
- $value = static::create($value);
- }
-
- return $value;
- }
- }
-
- return false;
- }
-
-
- /**
- * Use a callable function to filter through items.
- * The signature of the callable must be: `function ($value, $key)`
- *
- * @param \Closure|null $closure
- * @param int $flag Flag determining what arguments are sent to callback.
- * ARRAY_FILTER_USE_KEY :: pass key as the only argument
- * to callback. ARRAY_FILTER_USE_BOTH :: pass both value
- * and key as arguments to callback.
- *
- * @return static
- */
- public function filter(\Closure $closure = null, $flag = ARRAY_FILTER_USE_BOTH)
- {
- $items = $this->items;
-
- if (!isset($closure)) {
- return static::create($items);
- }
-
- return (
- static::create(
- \array_values(
- \array_filter(
- $items,
- $closure,
- $flag
- )
- )
- )
- );
- }
-
-
- /**
- * Allow to filter an array using one of the following comparison operators:
- * - [ =, ==, eq (equal) ]
- * - [ ===, i (identical) ]
- * - [ !=, ne (not equal) ]
- * - [ !==, ni (not identical) ]
- * - [ <, lt (less than) ]
- * - [ >, gr (greater than) ]
- * - [ <=, lte (less than or equal to) ]
- * - [ =>, gte (greater than or equal to) ]
- * - [ in, contains ]
- * - [ not-in, not-contains ]
- * - [ between ]
- * - [ not-between ]
- *
- * @param string $property
- * @param string $comparisonOperator
- * @param mixed $value
- *
- * @return static
- */
- public function filterBy($property, $comparisonOperator, $value)
- {
- $args = \func_get_args();
- $value = (array) \array_slice($args, 2, \count($args));
-
- $closure = null;
- $operators = static::operators();
-
- if (isset($value[0]) && \is_array($value[0])) {
- $value = $value[0];
- }
-
- foreach ($operators as $entry) {
- if (\in_array($comparisonOperator, $entry['tokens'])) {
- $closure = function ($item) use ($entry, $property, $value) {
- $item = (array) $item;
-
- if (!\array_key_exists($property, $item)) {
- return false;
- }
-
- return $entry['closure']($item, $property, $value);
- };
-
- break;
- }
- }
-
- return $this->filter($closure);
- }
-
-
- /**
- * Filtering through array.
- * The signature of the call can be:
- * - where([property, comparisonOperator, ...value])
- * - where(\Closure) :: The signature of the callable must be: `function ($value, $key)`
- * - where([\Closure]) :: The signature of the callable must be: `function ($value, $key)`
- *
- * Allowed comparison operators:
- * - [ =, ==, eq (equal) ]
- * - [ ===, i (identical) ]
- * - [ !=, ne (not equal) ]
- * - [ !==, ni (not identical) ]
- * - [ <, lt (less than) ]
- * - [ >, gr (greater than) ]
- * - [ <=, lte (less than or equal to) ]
- * - [ =>, gte (greater than or equal to) ]
- * - [ in, contains ]
- * - [ not-in, not-contains ]
- * - [ between ]
- * - [ not-between ]
- *
- * @param array|callable $criteria
- *
- * @return static
- */
- public function where($criteria)
- {
- $criteria = (array) $criteria;
-
- if (empty($criteria)) {
- return $this->filter();
- }
-
- $closure = \array_shift($criteria);
-
- if ($closure instanceof \Closure) {
- return $this->filter($closure);
- }
-
- $property = $closure;
- $comparisonOperator = \array_shift($criteria);
- $value = $criteria;
-
- if (isset($value[0]) && \is_array($value[0])) {
- $value = $value[0];
- }
-
- return $this->filterBy($property, $comparisonOperator, $value);
- }
-
-
/**
* Returning the first value from the current array.
+ * False otherwise, in case the list is empty.
*
* @return mixed
*/
public function first()
{
- $items = $this->items;
-
- return \array_shift($items);
+ return \reset($this->items);
}
-
/**
* Whether a offset exists
*
@@ -693,7 +504,6 @@ public function offsetExists($offset)
return $this->has($offset);
}
-
/**
* Offset to retrieve
*
@@ -710,7 +520,6 @@ public function &offsetGet($offset)
return $this->read($offset, null);
}
-
/**
* Offset to set
*
@@ -728,7 +537,6 @@ public function offsetSet($offset, $value)
$this->write($offset, $value);
}
-
/**
* Offset to unset
*
@@ -750,7 +558,6 @@ public function offsetUnset($offset)
$this->remove($offset);
}
-
/**
* Count elements of an object
*
@@ -767,39 +574,6 @@ public function count($mode = COUNT_NORMAL)
return \count($this->items, $mode);
}
-
- /**
- * @return array
- */
- public function toArray()
- {
- return $this->items;
- }
-
-
- /**
- * @param int $options
- *
- * @return string
- */
- public function toJson($options = 0)
- {
- return \json_encode($this->items, $options);
- }
-
-
- /**
- * Flatten the internal array using the dot delimiter,
- * also the keys are wrapped inside {{key}} (2 x curly braces).
- *
- * @return array
- */
- public function toFlat()
- {
- return static::flatten($this->items);
- }
-
-
/**
* Specify data which should be serialized to JSON
*
@@ -815,7 +589,6 @@ public function jsonSerialize()
return $this->items;
}
-
/**
* String representation of object
*
@@ -830,7 +603,6 @@ public function serialize()
return \serialize($this->items);
}
-
/**
* Constructs the object
*
@@ -847,7 +619,6 @@ public function unserialize($serialized)
$this->items = \unserialize($serialized);
}
-
/**
* Retrieve an external iterator.
*
@@ -862,5 +633,37 @@ public function getIterator()
return new \ArrayIterator($this->items);
}
+ /**
+ * Getting the internal raw array.
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ return $this->items;
+ }
+
+ /**
+ * Getting the internal raw array as JSON.
+ *
+ * @param int $options
+ *
+ * @return string
+ */
+ public function toJson($options = 0)
+ {
+ return (string) \json_encode($this->items, $options);
+ }
+
+ /**
+ * Flatten the internal array using the dot delimiter,
+ * also the keys are wrapped inside {key} (1 x curly braces).
+ *
+ * @return array
+ */
+ public function toFlat()
+ {
+ return self::flatten($this->items);
+ }
}
diff --git a/src/DotFilteringTrait.php b/src/DotFilteringTrait.php
new file mode 100644
index 0000000..6e55514
--- /dev/null
+++ b/src/DotFilteringTrait.php
@@ -0,0 +1,266 @@
+
+ * @license https://github.com/binary-cube/dot-array/blob/master/LICENSE
+ * @link https://github.com/binary-cube/dot-array
+ */
+trait DotFilteringTrait
+{
+
+ /**
+ * List with internal operators and the associated callbacks.
+ *
+ * @return array
+ */
+ protected static function operators()
+ {
+ return [
+ [
+ 'tokens' => ['=', '==', 'eq'],
+ 'closure' => function ($item, $property, $value) {
+ return $item[$property] == $value[0];
+ },
+ ],
+
+ [
+ 'tokens' => ['===', 'i'],
+ 'closure' => function ($item, $property, $value) {
+ return $item[$property] === $value[0];
+ },
+ ],
+
+ [
+ 'tokens' => ['!=', 'ne'],
+ 'closure' => function ($item, $property, $value) {
+ return $item[$property] != $value[0];
+ },
+ ],
+
+ [
+ 'tokens' => ['!==', 'ni'],
+ 'closure' => function ($item, $property, $value) {
+ return $item[$property] !== $value[0];
+ },
+ ],
+
+ [
+ 'tokens' => ['<', 'lt'],
+ 'closure' => function ($item, $property, $value) {
+ return $item[$property] < $value[0];
+ },
+ ],
+
+ [
+ 'tokens' => ['>', 'gt'],
+ 'closure' => function ($item, $property, $value) {
+ return $item[$property] > $value[0];
+ },
+ ],
+
+ [
+ 'tokens' => ['<=', 'lte'],
+ 'closure' => function ($item, $property, $value) {
+ return $item[$property] <= $value[0];
+ },
+ ],
+
+ [
+ 'tokens' => ['>=', 'gte'],
+ 'closure' => function ($item, $property, $value) {
+ return $item[$property] >= $value[0];
+ },
+ ],
+
+ [
+ 'tokens' => ['in', 'contains'],
+ 'closure' => function ($item, $property, $value) {
+ return \in_array($item[$property], (array) $value, true);
+ },
+ ],
+
+ [
+ 'tokens' => ['not-in', 'not-contains'],
+ 'closure' => function ($item, $property, $value) {
+ return !\in_array($item[$property], (array) $value, true);
+ },
+ ],
+
+ [
+ 'tokens' => ['between'],
+ 'closure' => function ($item, $property, $value) {
+ return ($item[$property] >= $value[0] && $item[$property] <= $value[1]);
+ },
+ ],
+
+ [
+ 'tokens' => ['not-between'],
+ 'closure' => function ($item, $property, $value) {
+ return ($item[$property] < $value[0] || $item[$property] > $value[1]);
+ },
+ ],
+ ];
+ }
+
+ /**
+ * Find the first item in an array that passes the truth test, otherwise return false.
+ * The signature of the callable must be: `function ($value, $key)`
+ *
+ * @param \Closure $closure
+ *
+ * @return false|mixed
+ */
+ public function find(\Closure $closure)
+ {
+ foreach ($this->items as $key => $value) {
+ if ($closure($value, $key)) {
+ if (\is_array($value)) {
+ $value = static::create($value);
+ }
+
+ return $value;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Use a callable function to filter through items.
+ * The signature of the callable must be: `function ($value, $key)`
+ *
+ * @param \Closure|null $closure
+ * @param int $flag Flag determining what arguments are sent to callback.
+ * ARRAY_FILTER_USE_KEY :: pass key as the only argument
+ * to callback. ARRAY_FILTER_USE_BOTH :: pass both value
+ * and key as arguments to callback.
+ *
+ * @return static
+ */
+ public function filter(\Closure $closure = null, $flag = ARRAY_FILTER_USE_BOTH)
+ {
+ $items = $this->items;
+
+ if (!isset($closure)) {
+ return static::create($items);
+ }
+
+ return static::create(
+ \array_values(
+ \array_filter(
+ $items,
+ $closure,
+ $flag
+ )
+ )
+ );
+ }
+
+ /**
+ * Allow to filter an array using one of the following comparison operators:
+ * - [ =, ==, eq (equal) ]
+ * - [ ===, i (identical) ]
+ * - [ !=, ne (not equal) ]
+ * - [ !==, ni (not identical) ]
+ * - [ <, lt (less than) ]
+ * - [ >, gr (greater than) ]
+ * - [ <=, lte (less than or equal to) ]
+ * - [ =>, gte (greater than or equal to) ]
+ * - [ in, contains ]
+ * - [ not-in, not-contains ]
+ * - [ between ]
+ * - [ not-between ]
+ *
+ * @param string $property
+ * @param string $comparisonOperator
+ * @param mixed $value
+ *
+ * @return static
+ */
+ public function filterBy($property, $comparisonOperator, $value)
+ {
+ $args = \func_get_args();
+ $value = \array_slice($args, 2, \count($args));
+
+ $closure = null;
+ $operators = static::operators();
+
+ if (isset($value[0]) && \is_array($value[0])) {
+ $value = $value[0];
+ }
+
+ foreach ($operators as $entry) {
+ if (\in_array($comparisonOperator, $entry['tokens'])) {
+ $closure = function ($item) use ($entry, $property, $value) {
+ $item = (array) $item;
+
+ if (!\array_key_exists($property, $item)) {
+ return false;
+ }
+
+ return $entry['closure']($item, $property, $value);
+ };
+
+ break;
+ }
+ }
+
+ return $this->filter($closure);
+ }
+
+ /**
+ * Filtering through array.
+ * The signature of the call can be:
+ * - where([property, comparisonOperator, ...value])
+ * - where(\Closure) :: The signature of the callable must be: `function ($value, $key)`
+ * - where([\Closure]) :: The signature of the callable must be: `function ($value, $key)`
+ *
+ * Allowed comparison operators:
+ * - [ =, ==, eq (equal) ]
+ * - [ ===, i (identical) ]
+ * - [ !=, ne (not equal) ]
+ * - [ !==, ni (not identical) ]
+ * - [ <, lt (less than) ]
+ * - [ >, gr (greater than) ]
+ * - [ <=, lte (less than or equal to) ]
+ * - [ =>, gte (greater than or equal to) ]
+ * - [ in, contains ]
+ * - [ not-in, not-contains ]
+ * - [ between ]
+ * - [ not-between ]
+ *
+ * @param array|callable $criteria
+ *
+ * @return static
+ */
+ public function where($criteria)
+ {
+ $criteria = (array) $criteria;
+
+ if (empty($criteria)) {
+ return $this->filter();
+ }
+
+ $closure = \array_shift($criteria);
+
+ if ($closure instanceof \Closure) {
+ return $this->filter($closure);
+ }
+
+ $property = $closure;
+ $comparisonOperator = \array_shift($criteria);
+ $value = $criteria;
+
+ if (isset($value[0]) && \is_array($value[0])) {
+ $value = $value[0];
+ }
+
+ return $this->filterBy($property, $comparisonOperator, $value);
+ }
+
+}
diff --git a/src/DotPathTrait.php b/src/DotPathTrait.php
deleted file mode 100644
index dd003b5..0000000
--- a/src/DotPathTrait.php
+++ /dev/null
@@ -1,175 +0,0 @@
-
- * @license https://github.com/binary-cube/dot-array/blob/master/LICENSE
- * @link https://github.com/binary-cube/dot-array
- */
-trait DotPathTrait
-{
-
- /**
- * Internal Dot Path Config.
- *
- * @var array
- */
- protected static $dotPathConfig = [
- 'template' => '#(?|(?|[](.*?)[])|(.*?))(?:$|\.+)#i',
- 'wrapKey' => '{{%s}}',
- 'wildcards' => [
- '' => ['\'', '\"', '\[', '\(', '\{'],
- '' => ['\'', '\"', '\]', '\)', '\}'],
- ],
- ];
-
- /**
- * The cached pattern that allow to match the JSON paths that use the dot notation.
- *
- * Allowed tokens for more complex paths: '', "", [], (), {}
- * Examples:
- *
- * - foo.bar
- * - foo.'bar'
- * - foo."bar"
- * - foo.[bar]
- * - foo.(bar)
- * - foo.{bar}
- *
- * Or more complex:
- * - foo.{bar}.[component].{version.1.0}
- *
- * @var string
- */
- protected static $dotPathPattern;
-
-
- /**
- * Getting the dot path pattern.
- *
- * @return string
- */
- protected static function dotPathPattern()
- {
- if (empty(self::$dotPathPattern)) {
- $path = self::$dotPathConfig['template'];
-
- foreach (self::$dotPathConfig['wildcards'] as $wildcard => $tokens) {
- $path = \str_replace($wildcard, \implode('', $tokens), $path);
- }
-
- self::$dotPathPattern = $path;
- }
-
- return self::$dotPathPattern;
- }
-
-
- /**
- * Converts dot string path to segments.
- *
- * @param string $path
- *
- * @return array
- */
- protected static function pathToSegments($path)
- {
- $path = \trim($path, " \t\n\r\0\x0B\.");
- $segments = [];
- $matches = [];
-
- if (\mb_strlen($path, 'UTF-8') === 0) {
- return [];
- }
-
- \preg_match_all(static::dotPathPattern(), $path, $matches);
-
- if (!empty($matches[1])) {
- $matches = $matches[1];
-
- $segments = \array_filter(
- $matches,
- function ($match) {
- return (\mb_strlen($match, 'UTF-8') > 0);
- }
- );
- }
-
- unset($path, $matches);
-
- return $segments;
- }
-
-
- /**
- * Wrap a given string into special characters.
- *
- * @param string $key
- *
- * @return string
- */
- protected static function wrapSegmentKey($key)
- {
- return vsprintf(static::$dotPathConfig['wrapKey'], [$key]);
- }
-
-
- /**
- * @param array $segments
- *
- * @return string
- */
- protected static function segmentsToKey(array $segments)
- {
- return (
- \implode(
- '',
- \array_map(
- [static::class, 'wrapSegmentKey'],
- $segments
- )
- )
- );
- }
-
-
- /**
- * Flatten the internal array using the dot delimiter,
- * also the keys are wrapped inside {{key}} (2 x curly braces).
- *
- * @param array $items
- * @param string $prepend
- *
- * @return array
- */
- protected static function flatten(array $items, $prepend = '')
- {
- $flatten = [];
-
- foreach ($items as $key => $value) {
- $wrapKey = static::wrapSegmentKey($key);
-
- if (\is_array($value) && !empty($value)) {
- $flatten = array_merge(
- $flatten,
- static::flatten(
- $value,
- ($prepend . $wrapKey . '.')
- )
- );
-
- continue;
- }
-
- $flatten[$prepend . $wrapKey] = $value;
- }
-
- return $flatten;
- }
-
-
-}
diff --git a/tests/Integration/ArrayDataProvider.php b/tests/Unit/ArrayDataProvider.php
similarity index 98%
rename from tests/Integration/ArrayDataProvider.php
rename to tests/Unit/ArrayDataProvider.php
index 77dbaea..129a3ba 100644
--- a/tests/Integration/ArrayDataProvider.php
+++ b/tests/Unit/ArrayDataProvider.php
@@ -1,6 +1,6 @@
[
+ 'b' => [
+ 'c' => true,
+ ],
+ ],
+ ];
+
+ return [
+ [
+ 'path' => 'a.b.c',
+ 'array' => $array,
+ ],
+ [
+ 'path' => "'a'.'b'.'c'",
+ 'array' => $array,
+ ],
+ [
+ 'path' => '"a"."b"."c"',
+ 'array' => $array,
+ ],
+ [
+ 'path' => '[a].[b].[c]',
+ 'array' => $array,
+ ],
+ [
+ 'path' => '(a).(b).(c)',
+ 'array' => $array,
+ ],
+ [
+ 'path' => '{a}.{b}.{c}',
+ 'array' => $array,
+ ],
+ [
+ 'path' => '[a].(b).{c}',
+ 'array' => $array,
+ ],
+ ];
+ }
+
+ /**
+ * Testing different patterns.
+ *
+ * @param string $path The query pattern
+ * @param array $array The array
+ *
+ * @covers \BinaryCube\DotArray\DotArray::get
+ * @covers \BinaryCube\DotArray\DotArray::toArray
+ * @covers \BinaryCube\DotArray\DotArray::
+ * @covers \BinaryCube\DotArray\DotArray::
+ *
+ * @dataProvider tokens
+ *
+ * @return void
+ */
+ public function testTokens($path, $array)
+ {
+ $dot = DotArray::create($array);
+
+ self::assertInstanceOf(DotArray::class, $dot);
+ self::assertTrue($dot->get($path));
+ }
/**
* Testing the Get Method.
*
- * @covers \BinaryCube\DotArray\DotPathTrait
* @covers \BinaryCube\DotArray\DotArray::get
* @covers \BinaryCube\DotArray\DotArray::toArray
* @covers \BinaryCube\DotArray\DotArray::
@@ -104,11 +168,9 @@ public function testGet()
self::assertIsString($this->dot['mixed_array.hello-world.{Nǐ hǎo}']);
}
-
/**
* Testing the Set Method.
*
- * @covers \BinaryCube\DotArray\DotPathTrait
* @covers \BinaryCube\DotArray\DotArray::set
* @covers \BinaryCube\DotArray\DotArray::toArray
* @covers \BinaryCube\DotArray\DotArray::
@@ -158,13 +220,13 @@ public function testSet()
self::assertIsInt($this->dot->get('new1.new2'));
self::assertIsInt($this->dot['new1']['new2']);
self::assertIsInt($this->dot['new1.new2']);
- }
+ self::assertSame([], $this->dot->set(null, null)->toArray());
+ }
/**
* Testing the Has Method.
*
- * @covers \BinaryCube\DotArray\DotPathTrait
* @covers \BinaryCube\DotArray\DotArray::has
* @covers \BinaryCube\DotArray\DotArray::
* @covers \BinaryCube\DotArray\DotArray::
@@ -191,11 +253,9 @@ public function testHas()
self::assertNotTrue($this->dot->has('a.b.c.d'));
}
-
/**
* Testing the isEmpty Method.
*
- * @covers \BinaryCube\DotArray\DotPathTrait
* @covers \BinaryCube\DotArray\DotArray::isEmpty
* @covers \BinaryCube\DotArray\DotArray::
* @covers \BinaryCube\DotArray\DotArray::
@@ -222,11 +282,9 @@ public function testIsEmpty()
self::assertIsBool($this->dot->get('dotObject')->isEmpty());
}
-
/**
* Testing the Delete Method.
*
- * @covers \BinaryCube\DotArray\DotPathTrait
* @covers \BinaryCube\DotArray\DotArray::delete
* @covers \BinaryCube\DotArray\DotArray::
* @covers \BinaryCube\DotArray\DotArray::
@@ -258,11 +316,9 @@ public function testDelete()
self::assertTrue(array_key_exists('one', $this->dot['assoc_array']));
}
-
/**
* Testing the Clear Method.
*
- * @covers \BinaryCube\DotArray\DotPathTrait
* @covers \BinaryCube\DotArray\DotArray::clear
* @covers \BinaryCube\DotArray\DotArray::
* @covers \BinaryCube\DotArray\DotArray::
@@ -287,11 +343,9 @@ public function testClear()
self::assertEmpty($users->toArray());
}
-
/**
* Testing the Merge Method.
*
- * @covers \BinaryCube\DotArray\DotPathTrait
* @covers \BinaryCube\DotArray\DotArray::merge
* @covers \BinaryCube\DotArray\DotArray::
* @covers \BinaryCube\DotArray\DotArray::
@@ -380,11 +434,9 @@ public function testMerge()
self::assertCount(3, $this->dot->get('mixed_array.{👋.🤘.some-key}.config.memcached.servers'));
}
-
/**
* Testing the Count Method.
*
- * @covers \BinaryCube\DotArray\DotPathTrait
* @covers \BinaryCube\DotArray\DotArray::count
* @covers \BinaryCube\DotArray\DotArray::
* @covers \BinaryCube\DotArray\DotArray::
@@ -402,11 +454,10 @@ public function testCount()
self::assertEquals(1, count($this->dot['assoc_array']['three']));
}
-
/**
* Testing the Find Method.
*
- * @covers \BinaryCube\DotArray\DotPathTrait
+ * @covers \BinaryCube\DotArray\DotFilteringTrait
* @covers \BinaryCube\DotArray\DotArray::find
* @covers \BinaryCube\DotArray\DotArray::
* @covers \BinaryCube\DotArray\DotArray::
@@ -442,11 +493,10 @@ function () {
);
}
-
/**
* Testing the Filter Method.
*
- * @covers \BinaryCube\DotArray\DotPathTrait
+ * @covers \BinaryCube\DotArray\DotFilteringTrait
* @covers \BinaryCube\DotArray\DotArray::filter
* @covers \BinaryCube\DotArray\DotArray::
* @covers \BinaryCube\DotArray\DotArray::
@@ -467,11 +517,10 @@ function ($value) {
self::assertSame([1, 2, 3, 4], $under->toArray());
}
-
/**
* Testing the FilterBy Method.
*
- * @covers \BinaryCube\DotArray\DotPathTrait
+ * @covers \BinaryCube\DotArray\DotFilteringTrait
* @covers \BinaryCube\DotArray\DotArray::filterBy
* @covers \BinaryCube\DotArray\DotArray::
* @covers \BinaryCube\DotArray\DotArray::
@@ -578,11 +627,10 @@ function ($value) {
);
}
-
/**
* Testing the Where Method.
*
- * @covers \BinaryCube\DotArray\DotPathTrait
+ * @covers \BinaryCube\DotArray\DotFilteringTrait
* @covers \BinaryCube\DotArray\DotArray::where
* @covers \BinaryCube\DotArray\DotArray::
* @covers \BinaryCube\DotArray\DotArray::
@@ -652,11 +700,9 @@ function ($value) {
self::assertSame($user1, $users->toArray());
}
-
/**
* Testing the First Method.
*
- * @covers \BinaryCube\DotArray\DotPathTrait
* @covers \BinaryCube\DotArray\DotArray::first
* @covers \BinaryCube\DotArray\DotArray::
* @covers \BinaryCube\DotArray\DotArray::
@@ -671,7 +717,6 @@ public function testFirst()
);
}
-
/**
* Testing the toArray Method.
*
@@ -687,7 +732,6 @@ public function testToArray()
self::assertSame($this->data, $this->dot->toArray());
}
-
/**
* Testing the toJson Method.
*
@@ -707,21 +751,19 @@ public function testToJson()
self::assertSame($this->jsonArray, $decode);
}
-
/**
- * Testing the toFlatten Method.
+ * Testing the toFlat Method.
*
- * @covers \BinaryCube\DotArray\DotPathTrait
- * @covers \BinaryCube\DotArray\DotPathTrait::flatten
- * @covers \BinaryCube\DotArray\DotPathTrait::dotPathPattern
- * @covers \BinaryCube\DotArray\DotPathTrait::wrapSegmentKey
+ * @covers \BinaryCube\DotArray\DotArray::flatten
+ * @covers \BinaryCube\DotArray\DotArray::pathPattern
+ * @covers \BinaryCube\DotArray\DotArray::wrapSegmentKey
* @covers \BinaryCube\DotArray\DotArray::toFlat
* @covers \BinaryCube\DotArray\DotArray::
* @covers \BinaryCube\DotArray\DotArray::
*
* @return void
*/
- public function testToFlatten()
+ public function testToFlat()
{
$dot = DotArray::create(
[
@@ -733,22 +775,29 @@ public function testToFlatten()
1,
2,
3,
+ 'array' => [
+ 1,
+ 2,
+ 3,
+ ]
],
]
);
self::assertSame(
[
- '{{a}}.{{b}}' => 'value',
- '{{b}}.{{0}}' => 1,
- '{{b}}.{{1}}' => 2,
- '{{b}}.{{2}}' => 3,
+ '{a}.{b}' => 'value',
+ '{b}.{0}' => 1,
+ '{b}.{1}' => 2,
+ '{b}.{2}' => 3,
+ '{b}.{array}.{0}' => 1,
+ '{b}.{array}.{1}' => 2,
+ '{b}.{array}.{2}' => 3,
],
$dot->toFlat()
);
}
-
/**
* Testing the serialize & unserialize Methods.
*
@@ -775,7 +824,6 @@ public function testSerializable()
self::assertInstanceOf(DotArray::class, $unserialize);
}
-
/**
* Testing the jsonSerialize Methods.
*
@@ -788,7 +836,6 @@ public function testJsonSerialize()
self::assertSame($this->data, $this->dot->jsonSerialize());
}
-
/**
* Testing the getIterator Methods.
*
@@ -803,5 +850,4 @@ public function testIterator()
self::assertSame($this->data, $this->dot->getIterator()->getArrayCopy());
}
-
}
diff --git a/tests/Integration/CreateTest.php b/tests/Unit/CreateTest.php
similarity index 89%
rename from tests/Integration/CreateTest.php
rename to tests/Unit/CreateTest.php
index 4c33e78..7b477e9 100644
--- a/tests/Integration/CreateTest.php
+++ b/tests/Unit/CreateTest.php
@@ -1,9 +1,8 @@
toArray());
+ self::assertIsArray(DotArray::create(1)->toArray());
+ self::assertIsArray(DotArray::create([])->toArray());
+ self::assertIsArray(DotArray::create(DotArray::create([]))->toArray());
+
$dot = new DotArray($this->data);
self::assertIsArray($dot->toArray());
+
self::assertArrayHasKey('empty_array', $dot->toArray());
self::assertArrayHasKey('indexed_array', $dot->toArray());
self::assertArrayHasKey('assoc_array', $dot->toArray());
@@ -96,5 +99,4 @@ public function testCreate()
self::assertArrayHasKey('a', $dot->toArray());
}
-
}
diff --git a/tests/TestCase.php b/tests/Unit/TestCase.php
similarity index 88%
rename from tests/TestCase.php
rename to tests/Unit/TestCase.php
index a2262e8..e709d2f 100644
--- a/tests/TestCase.php
+++ b/tests/Unit/TestCase.php
@@ -1,6 +1,6 @@