├── .gitbook.yaml
├── .github
└── FUNDING.yml
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── _config.yml
├── composer.json
├── docs
├── README.md
├── SUMMARY.md
├── common-methods
│ ├── clone.md
│ ├── get-getnestedelement.md
│ ├── getkeysarray.md
│ ├── has.md
│ ├── remove.md
│ └── set-setnestedelement.md
├── manipulating-array
│ ├── computation.md
│ ├── filtering.md
│ ├── flattening.md
│ ├── grouping.md
│ ├── mapping.md
│ └── sorting.md
├── object-oriented-methods
│ └── general-information.md
├── traversing-array
│ ├── finding.md
│ └── iterating.md
├── utility-methods
│ ├── forcearray.md
│ ├── getdepth.md
│ ├── getfirstkey.md
│ ├── getfirstvalue.md
│ ├── getlastkey.md
│ ├── getlastvalue.md
│ ├── nth.md
│ ├── pack.md
│ ├── random.md
│ ├── shuffle.md
│ ├── unpack.md
│ └── untitled.md
└── validating-array
│ ├── check.md
│ ├── haskeys.md
│ ├── isarrayofarrays.md
│ ├── isassoc.md
│ ├── isempty.md
│ ├── isnested.md
│ ├── isnumeric.md
│ └── isunique.md
├── phpunit.xml
├── src
├── Arr.php
└── ArrObj.php
└── tests
├── Arr
└── ArrTest.php
├── ArrObj
└── ArrObjTest.php
└── ArrTestCase.php
/.gitbook.yaml:
--------------------------------------------------------------------------------
1 | root: ./docs/
2 |
3 | structure:
4 | readme: README.md
5 | summary: SUMMARY.md
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: minwork # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .settings/
3 | .buildpath
4 | .project
5 | composer.lock
6 | vendor/
7 | build/
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 7.1
5 | - 7.2
6 | - 7.3
7 | branches:
8 | only:
9 | - master
10 | before_install:
11 | - travis_retry composer self-update
12 | - composer install
13 | script:
14 | - mkdir -p build/logs
15 | - vendor/bin/phpunit --testsuite Complex
16 | after_script:
17 | - travis_retry vendor/bin/php-coveralls
18 | # or enable logging
19 | - travis_retry vendor/bin/php-coveralls -v
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Minwork Array
2 |
3 | [](https://travis-ci.org/minwork/array) [](https://coveralls.io/github/minwork/array?branch=master) [](https://packagist.org/packages/minwork/array) [](https://github.com/minwork/array)
4 |
5 | ## Pack of array convenience methods for handling:
6 | * **Nested** arrays
7 | * Arrays of **objects**
8 | * **Associative** arrays
9 | * **Chaining** array transformations
10 | ### Easily **create**, **access**, **validate**, **manipulate** and **transform** arrays
11 | Advanced implementation of well known operations:
12 | * [Get](https://minwork.gitbook.io/array/common-methods/get-getnestedelement)
13 | * [Set](https://minwork.gitbook.io/array/common-methods/set-setnestedelement)
14 | * [Has](https://minwork.gitbook.io/array/common-methods/has)
15 | * [Map](https://minwork.gitbook.io/array/manipulating-array/mapping)
16 | * [Each](https://minwork.gitbook.io/array/traversing-array/iterating)
17 | * [Filter](https://minwork.gitbook.io/array/manipulating-array/filtering)
18 | * [Find](https://minwork.gitbook.io/array/traversing-array/finding)
19 | * [Group](https://minwork.gitbook.io/array/manipulating-array/grouping)
20 | * [Sort](https://minwork.gitbook.io/array/manipulating-array/sorting)
21 | * [Check](https://minwork.gitbook.io/array/validating-array/check)
22 | * [And many more...](https://minwork.gitbook.io/array/)
23 |
24 | ## Installation
25 |
26 | `composer require minwork/array`
27 |
28 | ## Advantages
29 |
30 | * Thoroughly **tested**
31 | * Well **documented**
32 | * Leverages PHP 7 syntax and **speed**
33 | * No external dependencies
34 | * Large variety of usages
35 |
36 | ## Example of usage
37 | ```php
38 | // Set nested array value
39 | $array = Arr::set([], 'key1.key2.key3', 'my_value');
40 | // Which is equivalent to
41 | [
42 | 'key1' => [
43 | 'key2' => [
44 | 'key3' => 'my_value'
45 | ]
46 | ]
47 | ]
48 |
49 | // Get nested array value
50 | Arr::get($array, 'key1.key2') -> ['key3' => 'my_value']
51 |
52 | // Check if array has nested element
53 | Arr::has($array, 'key1.key2.key3') -> true
54 |
55 | // Map array while accessing it's key
56 | Arr::map($array, function ($key, $value) {
57 | // Your code here
58 | });
59 |
60 | // Find array element
61 | Arr::find($array, function ($element) {
62 | return Arr::get($element, 'key2.key3') === 'my_value';
63 | }) -> [ 'key2' => [ 'key3' => 'my_value'] ]
64 |
65 | // Chain few methods
66 | Arr::obj(['test' => 1, 'foo' => 'bar'])
67 | ->set('abc', 123)
68 | ->set('[]', 'auto_index')
69 | ->remove('foo')
70 | ->getArray()
71 | ->
72 | [
73 | 'test' => 1,
74 | 'abc' => 123,
75 | 'auto_index'
76 | ]
77 |
78 | // Group objects by the result of calling method 'getSize' on each object
79 | Arr::groupObjects([$cat, $dog, $fish, ...], 'getSize') ->
80 | [
81 | 'medium' => [$cat, $dog, ...],
82 | 'small' => [$fish, ...],
83 | ...
84 | ]
85 | ```
86 |
87 | ## Documentation
88 |
89 | [https://minwork.gitbook.io/array/](https://minwork.gitbook.io/array/)
90 |
91 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-midnight
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name" : "minwork/array",
3 | "description" : "Pack of advanced array functions specifically tailored for: associative (assoc) array, multidimensional array, array of objects and handling nested array elements",
4 | "type" : "library",
5 | "keywords" : [
6 | "array",
7 | "arrays",
8 | "assoc",
9 | "multidimensional",
10 | "objects",
11 | "helper",
12 | "associative",
13 | "php 7"
14 | ],
15 | "license" : "MIT",
16 | "authors" : [{
17 | "name" : "Christopher Kalkhoff",
18 | "email" : "krzysztof.kalkhoff@gmail.com",
19 | "role" : "Founder"
20 | }
21 | ],
22 | "require" : {
23 | "php" : ">=7.1"
24 | },
25 | "require-dev" : {
26 | "phpunit/phpunit" : "^7.0",
27 | "php-coveralls/php-coveralls": "^2.1"
28 | },
29 | "autoload" : {
30 | "psr-4" : {
31 | "Minwork\\Helper\\" : "src/"
32 | }
33 | },
34 | "autoload-dev" : {
35 | "psr-4" : {
36 | "Minwork\\Helper\\Tests\\" : "tests/"
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # Minwork Array
2 |
3 | ## Welcome to the documentation page
4 |
5 | Minwork Array library is focused on conviniently handling nested arrays as well as arrays of objects.
6 |
7 | {% hint style="info" %}
8 | Detailed documentation is always available through PHPDoc in your editor, so you can access it anytime.
9 | {% endhint %}
10 |
11 | I believe that the best way to learn some code is to see it in action. That's why within this documentation, aside from detailed specification of each method you will find various examples of using it which will help in faster understanding it's purpose and applications.
12 |
13 | To quickly get started check out the links below or look at the full list of available methods in the left panel.
14 |
15 | {% page-ref page="common-methods/has.md" %}
16 |
17 | {% page-ref page="common-methods/get-getnestedelement.md" %}
18 |
19 | {% page-ref page="common-methods/set-setnestedelement.md" %}
20 |
21 | {% page-ref page="common-methods/remove.md" %}
22 |
23 | {% page-ref page="utility-methods/getfirstkey.md" %}
24 |
25 | {% page-ref page="utility-methods/getfirstvalue.md" %}
26 |
27 |
--------------------------------------------------------------------------------
/docs/SUMMARY.md:
--------------------------------------------------------------------------------
1 | # Table of contents
2 |
3 | * [Minwork Array](README.md)
4 |
5 | ## Common methods
6 |
7 | * [has](common-methods/has.md)
8 | * [get → getNestedElement](common-methods/get-getnestedelement.md)
9 | * [set → setNestedElement](common-methods/set-setnestedelement.md)
10 | * [remove](common-methods/remove.md)
11 | * [clone](common-methods/clone.md)
12 | * [getKeysArray](common-methods/getkeysarray.md)
13 |
14 | ## Object oriented methods
15 |
16 | * [General information](object-oriented-methods/general-information.md)
17 |
18 | ## Traversing array
19 |
20 | * [Finding](traversing-array/finding.md)
21 | * [Iterating](traversing-array/iterating.md)
22 |
23 | ## Manipulating array
24 |
25 | * [Mapping](manipulating-array/mapping.md)
26 | * [Filtering](manipulating-array/filtering.md)
27 | * [Grouping](manipulating-array/grouping.md)
28 | * [Sorting](manipulating-array/sorting.md)
29 | * [Computations](manipulating-array/computation.md)
30 | * [Flattening](manipulating-array/flattening.md)
31 |
32 | ## Validating array
33 |
34 | * [check](validating-array/check.md)
35 | * [isEmpty](validating-array/isempty.md)
36 | * [isNested](validating-array/isnested.md)
37 | * [isArrayOfArrays](validating-array/isarrayofarrays.md)
38 | * [isAssoc](validating-array/isassoc.md)
39 | * [isUnique](validating-array/isunique.md)
40 | * [isNumeric](validating-array/isnumeric.md)
41 | * [hasKeys](validating-array/haskeys.md)
42 |
43 | ## Utility methods
44 |
45 | * [pack](utility-methods/pack.md)
46 | * [unpack](utility-methods/unpack.md)
47 | * [createMulti](utility-methods/untitled.md)
48 | * [forceArray](utility-methods/forcearray.md)
49 | * [getDepth](utility-methods/getdepth.md)
50 | * [random](utility-methods/random.md)
51 | * [shuffle](utility-methods/shuffle.md)
52 | * [nth](utility-methods/nth.md)
53 | * [getFirstKey](utility-methods/getfirstkey.md)
54 | * [getLastKey](utility-methods/getlastkey.md)
55 | * [getFirstValue](utility-methods/getfirstvalue.md)
56 | * [getLastValue](utility-methods/getlastvalue.md)
57 |
58 |
--------------------------------------------------------------------------------
/docs/common-methods/clone.md:
--------------------------------------------------------------------------------
1 | # clone
2 |
3 | #### Definition
4 |
5 | ```php
6 | Arr::clone(array $array): array
7 | ```
8 |
9 | #### Description
10 |
11 | Copy array and clone every object inside it
12 |
13 | #### Examples
14 |
15 | ```php
16 | $object = new class() {
17 | public $counter = 1;
18 |
19 | function __clone()
20 | {
21 | $this->counter = 2;
22 | }
23 | };
24 |
25 | $array = [
26 | 'foo',
27 | 'bar',
28 | $object,
29 | 'test',
30 | 'nested' => [
31 | 'object' => $object
32 | ]
33 | ];
34 |
35 | $cloned = Arr::clone($array);
36 |
37 | $cloned[0] -> 'foo'
38 | $cloned[2]->counter -> 2
39 | $cloned['nested']['object']->counter -> 2
40 | ```
41 |
42 |
--------------------------------------------------------------------------------
/docs/common-methods/get-getnestedelement.md:
--------------------------------------------------------------------------------
1 | # get → getNestedElement
2 |
3 | #### Definition
4 |
5 | ```php
6 | Arr::getNestedElement(array|ArrayAccess $array, mixed $keys, mixed $default = null): mixed
7 | ```
8 |
9 | #### Aliases
10 |
11 | ```php
12 | get($array, $keys, $default = null) -> getNestedElement($array, $keys, $default)
13 | ```
14 |
15 | #### Description
16 |
17 | Get nested array element using specified keys or return `$default` value if it does not exists.
18 |
19 | {% hint style="info" %}
20 | `$keys` argument is parsed using [getKeysArray ](getkeysarray.md)method
21 | {% endhint %}
22 |
23 | #### Examples
24 |
25 | ```php
26 | $array = ['key1' => ['key2' => ['key3' => ['test']]]];
27 |
28 | Arr::getNestedElement($array, 'key1.key2.key3') -> ['test']
29 |
30 | Arr::getNestedElement($array, 'key1.key2.key3.0') -> 'test'
31 |
32 | Arr::getNestedElement($array, ['nonexistent', 'key'], 'default') -> 'default'
33 |
34 | Arr::getNestedElement($array, 'nonexistent.key.without.default') -> null
35 | ```
36 |
37 |
--------------------------------------------------------------------------------
/docs/common-methods/getkeysarray.md:
--------------------------------------------------------------------------------
1 | # getKeysArray
2 |
3 | #### Definition
4 |
5 | ```php
6 | Arr::getKeysArray(mixed $keys): array
7 | ```
8 |
9 | #### Description
10 |
11 | Transform variable into standardised array of keys.
12 |
13 | This method filters out any values that cannot be used as array key leaving only not empty strings and integers as seen in the example below.
14 |
15 | {% hint style="info" %}
16 | All `$keys` arguments in other methods are normalized using this method
17 | {% endhint %}
18 |
19 | #### Examples
20 |
21 | ```php
22 | Arr::getKeysArray(0) -> [0]
23 |
24 | Arr::getKeysArray(null) -> []
25 |
26 | Arr::getKeysArray('key') -> ['key']
27 |
28 | Arr::getKeysArray('key1.0.key2.1') -> ['key1', '0', 'key2', '1']
29 |
30 | Arr::getKeysArray([null, 'key1', '', 'key2', 3.1415, 0]) -> ['key1', 'key2', 0]
31 | ```
32 |
33 |
--------------------------------------------------------------------------------
/docs/common-methods/has.md:
--------------------------------------------------------------------------------
1 | # has
2 |
3 | #### Definition
4 |
5 | ```php
6 | Arr::has(array $array, mixed $keys): bool
7 | ```
8 |
9 | #### Description
10 |
11 | Check if specified \(nested\) key\(s\) exists in array
12 |
13 | {% hint style="info" %}
14 | `$keys` argument is parsed using [getKeysArray ](getkeysarray.md)method
15 | {% endhint %}
16 |
17 | #### Examples
18 |
19 | ```php
20 | $array = [
21 | 'foo' => [
22 | 1,
23 | 'test' => [
24 | 'abc' => 2,
25 | 'def'
26 | ],
27 | [
28 | 'bar' => true
29 | ],
30 | ],
31 | ];
32 |
33 | Arr::has($array, 'foo') -> true
34 | Arr::has($array, 'foo.0') -> true
35 | Arr::has($array, 'foo.test') -> true
36 | Arr::has($array, 'foo.test.abc') -> true
37 | Arr::has($array, ['foo', 1, 'bar']) -> true
38 |
39 | Arr::has($array, 'test') -> false
40 | Arr::has($array, []) -> false
41 | Arr::has($array, 'not.existing.key') -> false
42 | ```
43 |
44 |
--------------------------------------------------------------------------------
/docs/common-methods/remove.md:
--------------------------------------------------------------------------------
1 | # remove
2 |
3 | #### Definition
4 |
5 | ```php
6 | Arr::remove(array $array, $keys): array
7 | ```
8 |
9 | #### Description
10 |
11 | Remove element inside array at path specified by keys.
12 |
13 | {% hint style="info" %}
14 | `$keys` argument is parsed using [getKeysArray ](getkeysarray.md)method
15 | {% endhint %}
16 |
17 | #### Examples
18 |
19 | ```php
20 | $array = [
21 | 'foo' => [
22 | 1,
23 | 'test' => [
24 | 'abc' => 2,
25 | 'def'
26 | ],
27 | [
28 | 'bar' => true
29 | ],
30 | ],
31 | ];
32 |
33 | Arr::remove($array, 'foo') -> []
34 | Arr::remove($array, '') -> $array
35 | Arr::remove($array, []) -> $array
36 |
37 | Arr::remove($array, 'foo.test.abc') ->
38 | [
39 | 'foo' => [
40 | 1,
41 | 'test' => [
42 | // Removed
43 | //'abc' => 2,
44 | 'def'
45 | ],
46 | [
47 | 'bar' => true
48 | ],
49 | ],
50 | ]
51 |
52 | Arr::remove($array, 'foo.test') ->
53 | [
54 | 'foo' => [
55 | 1,
56 | // Removed
57 | /*'test' => [
58 | 'abc' => 2,
59 | 'def'
60 | ],*/
61 | [
62 | 'bar' => true
63 | ],
64 | ],
65 | ]
66 |
67 | Arr::remove($array, ['foo', 1, 'bar']) ->
68 | [
69 | 'foo' => [
70 | 1,
71 | 'test' => [
72 | 'abc' => 2,
73 | 'def'
74 | ],
75 | [
76 | // Removed
77 | //'bar' => true
78 | ],
79 | ],
80 | ]
81 | ```
82 |
83 |
--------------------------------------------------------------------------------
/docs/common-methods/set-setnestedelement.md:
--------------------------------------------------------------------------------
1 | # set → setNestedElement
2 |
3 | #### Definition
4 |
5 | ```php
6 | Arr::setNestedElement(array|ArrayAccess $array, mixed $keys, mixed $value): array|ArrayAccess
7 | ```
8 |
9 | #### Aliases
10 |
11 | ```php
12 | set(array|ArrayAccess $array, $keys, $value) -> setNestedElement(array|ArrayAccess $array, $keys, $value)
13 | ```
14 |
15 | #### Description
16 |
17 | Set array element specified by keys to the desired value \(create missing keys if necessary\).
18 |
19 | {% hint style="info" %}
20 | `$keys` argument is parsed using [getKeysArray ](getkeysarray.md)method
21 | {% endhint %}
22 |
23 | #### Examples
24 |
25 | ```php
26 | $array = ['key1' => ['key2' => ['key3' => ['test']]]];
27 |
28 | Arr::setNestedElement([], 'key1.key2.key3', ['test']) -> $array
29 |
30 | $array = Arr::setNestedElement($array, 'key1.key2.key4', 'test2');
31 | $array['key1']['key2']['key4'] -> 'test2'
32 |
33 | // Create nested array element using automatic index
34 | Arr::setNestedElement($array, 'foo.[].foo', 'bar') ->
35 | [
36 | 'foo' => [
37 | [
38 | 'foo' => 'bar',
39 | ],
40 | ],
41 | ]
42 |
43 | Arr::setNestedElement([], '[].[].[]', 'test') -> [ [ [ 'test' ] ] ]
44 | ```
45 |
46 |
--------------------------------------------------------------------------------
/docs/manipulating-array/computation.md:
--------------------------------------------------------------------------------
1 | # Computations
2 |
3 | ## sum
4 |
5 | #### Definition
6 |
7 | ```php
8 | Arr::sum(array ...$arrays): array
9 | ```
10 |
11 | #### Description
12 |
13 | Sum associative arrays by their keys into one array
14 |
15 | #### Examples
16 |
17 | ```php
18 | $arrays = [
19 | [
20 | 'a' => 1,
21 | 'b' => -3.5,
22 | 'c' => 0,
23 | 3
24 | ],
25 | [
26 | 2,
27 | 'a' => 0,
28 | 'c' => -5,
29 | 'd' => PHP_INT_MAX,
30 | ],
31 | [
32 | -5,
33 | 'b' => 3.5,
34 | 'a' => -1,
35 | 'c' => 5,
36 | ],
37 | [
38 | 'd' => PHP_INT_MAX,
39 | ],
40 | [
41 | 'd' => 2 * -PHP_INT_MAX,
42 | ]
43 | ];
44 |
45 | Arr::sum(...$arrays) ->
46 | [
47 | 0,
48 | 'a' => 0,
49 | 'b' => 0,
50 | 'c' => 0,
51 | 'd' => 0,
52 | ]
53 |
54 | Arr::sum([null, '', false], ['1', true, 'test']) -> [1, 1, 0]
55 | ```
56 |
57 | ## diffObjects
58 |
59 | #### Declaration
60 |
61 | ```php
62 | Arr::diffObjects(array $array1, array $array2, array ...$arrays): array
63 | ```
64 |
65 | #### Description
66 |
67 | Compute difference between two or more arrays of objects
68 |
69 | #### Examples
70 |
71 | ```php
72 | $object1 = new \stdClass();
73 | $object2 = new \stdClass();
74 | $object3 = new \stdClass();
75 |
76 | Arr::diffObjects(
77 | [$object3, $object1, $object2],
78 | [$object3], [$object2]
79 | ) -> [1 => $object1]
80 |
81 | Arr::diffObjects(
82 | [$object3, $object1, $object2],
83 | [$object3],
84 | [$object1, $object2]
85 | ) -> []
86 |
87 | Arr::diffObjects(
88 | [$object1],
89 | [$object3],
90 | [$object2],
91 | []
92 | ) -> [$object1]
93 | ```
94 |
95 | ## intersectObjects
96 |
97 | #### Definition
98 |
99 | ```php
100 | Arr::intersectObjects(array $array1, array $array2, array ...$arrays): array
101 | ```
102 |
103 | #### Description
104 |
105 | Compute intersection between two or more arrays of objects
106 |
107 | #### Examples
108 |
109 | ```php
110 | $object1 = new \stdClass();
111 | $object2 = new \stdClass();
112 | $object3 = new \stdClass();
113 |
114 | Arr::intersectObjects(
115 | [$object3, $object1, $object2],
116 | [$object3, $object2],
117 | [$object2]
118 | ) -> [2 => $object2]
119 |
120 | Arr::intersectObjects(
121 | [$object3, $object1, $object2],
122 | [$object3],
123 | [$object1, $object2]
124 | ) -> []
125 |
126 | Arr::intersectObjects(
127 | [$object1, $object2, $object3, $object1],
128 | [$object1, $object2]
129 | ) -> [$object1, $object2, 3 => $object1]
130 | ```
131 |
132 |
--------------------------------------------------------------------------------
/docs/manipulating-array/filtering.md:
--------------------------------------------------------------------------------
1 | # Filtering
2 |
3 | ## filter
4 |
5 | #### Definition
6 |
7 | ```php
8 | Arr::filter(array $array, ?callable $callback = null, int $flag = 0): array
9 | ```
10 |
11 | #### Description
12 |
13 | Wrapper around PHP built-in [array\_filter](https://www.php.net/manual/en/function.array-filter.php) method to allow [chaining](https://minwork.gitbook.io/array/object-oriented-methods/general-information#chaining) in `ArrObj`
14 |
15 | #### Examples
16 |
17 | See [array\_filter examples](https://www.php.net/manual/en/function.array-filter.php#refsect1-function.array-filter-examples)
18 |
19 | ## filterByKeys
20 |
21 | #### Definition
22 |
23 | ```php
24 | Arr::filterByKeys(array $array, mixed $keys, bool $exclude = false): array
25 | ```
26 |
27 | #### Description
28 |
29 | Filter array values by preserving only those which keys are present in array obtained from $keys variable
30 |
31 | #### Examples
32 |
33 | ```php
34 | $array = [
35 | 'a' => 1,
36 | 'b' => 2,
37 | 3 => 'c',
38 | 4 => 5
39 | ];
40 |
41 | Arr::filterByKeys($array, 'a.b.3') -> ['a' => 1, 'b' => 2, 3 => 'c']
42 | Arr::filterByKeys($array, 'a.b.3', true) -> [4 => 5]
43 |
44 | Arr::filterByKeys($array, [null, 0, '']) -> []
45 | Arr::filterByKeys($array, [null, 0, ''], true) -> $array
46 | ```
47 |
48 | ## filterObjects
49 |
50 | #### Definition
51 |
52 | ```php
53 | Arr::filterObjects(array $objects, string $method, ...$args): array
54 | ```
55 |
56 | #### Description
57 |
58 | Filter objects array using return value of specified method
59 |
60 | This method also filter values other than objects by standard boolean comparison
61 |
62 | #### Examples
63 |
64 | ```php
65 | $object = new class() {
66 | function test($preserve = true) {
67 | return $preserve;
68 | }
69 | };
70 |
71 | $array = [$object, 'foo', $object, false];
72 |
73 | Arr::filterObjects($array, 'test') -> [$object, 'foo', $object]
74 | Arr::filterObjects($array, 'test', false) -> ['foo']
75 | ```
76 |
77 |
--------------------------------------------------------------------------------
/docs/manipulating-array/flattening.md:
--------------------------------------------------------------------------------
1 | # Flattening
2 |
3 | ## flatten
4 |
5 | #### Definition
6 |
7 | ```php
8 | Arr::flatten(array $array, ?int $depth = null, bool $assoc = false): array
9 | ```
10 |
11 | #### Description
12 |
13 | Flatten array of arrays to a n-depth array
14 |
15 | #### Examples
16 |
17 | ```php
18 | $array = [
19 | 'a' => [
20 | 'b' => [
21 | 'c' => 'test'
22 | ],
23 | 'd' => 1
24 | ],
25 | 'b' => [
26 | 'e' => 2
27 | ]
28 | ];
29 |
30 | Arr::flatten($array) ->
31 | [
32 | 'test',
33 | 1,
34 | 2
35 | ]
36 | Arr::flatten($array, 1) ->
37 | [
38 | ['c' => 'test'],
39 | 1,
40 | 2
41 | ]
42 | Arr::flatten($array, 0) ->
43 | [
44 | [
45 | 'b' => [
46 | 'c' => 'test'
47 | ],
48 | 'd' => 1
49 | ],
50 | [
51 | 'e' => 2
52 | ]
53 | ]
54 |
55 | Arr::flatten([[[[]]]]) -> []
56 |
57 | // When $assoc is set to true this method will try to preserve as much string keys as possible using automatically generated numeric indexes as fallback
58 | Arr::flatten($array, null, true) ->
59 | [
60 | 'c' => 'test',
61 | 'd' => 1,
62 | 'e' => 2
63 | ]
64 |
65 | $array = [
66 | 'a' => [
67 | 'b' => [
68 | 'c' => 1,
69 | ],
70 | ],
71 | [
72 | 'c' => 2
73 | ]
74 | ];
75 |
76 | // Here key 'c' is duplicated so it will fallback to numeric index
77 | Arr::flatten($array, null, true) ->
78 | [
79 | 'c' => 1,
80 | 2,
81 | ]
82 | ```
83 |
84 | ## flattenSingle
85 |
86 | #### Definition
87 |
88 | ```php
89 | Arr::flattenSingle(array $array): array
90 | ```
91 |
92 | #### Description
93 |
94 | Flatten single element arrays \(also nested single element arrays\)
95 |
96 | #### Examples
97 |
98 | ```php
99 | $array = [
100 | 'a' => ['test'],
101 | 'b' => [
102 | 'test2',
103 | 'c' => ['test3']
104 | ]
105 | ];
106 |
107 | Arr::flattenSingle($array) ->
108 | [
109 | 'a' => 'test',
110 | 'b' => [
111 | 'test2',
112 | 'c' => 'test3'
113 | ],
114 | ]
115 |
116 | $array = [
117 | 'a' => [
118 | 'b' => 1
119 | ],
120 | 'b' => 2,
121 | ];
122 |
123 | Arr::flattenSingle($array) -> ['a' => 1, 'b' => 2]
124 |
125 | Arr::flattenSingle([['a']]) -> ['a']
126 |
127 | Arr::flattenSingle([]) -> []
128 | ```
129 |
130 |
--------------------------------------------------------------------------------
/docs/manipulating-array/grouping.md:
--------------------------------------------------------------------------------
1 | # Grouping
2 |
3 | ## group
4 |
5 | #### Definition
6 |
7 | ```php
8 | Arr::group(array $array, string|int $key): array
9 | ```
10 |
11 | #### Description
12 |
13 | Group array of arrays by value of element with specified key
14 |
15 | #### Examples
16 |
17 | ```php
18 | $array = [
19 | 'a' => ['key1' => 'test1', 'key2' => 1, 'key3' => 'a'],
20 | 'b' => ['key1' => 'test1', 'key2' => 2],
21 | 2 => ['key1' => 'test2', 'key2' => 3, 'key3' => 'b']
22 | ];
23 |
24 | Arr::group($array, 'key1') ->
25 | [
26 | 'test1' => [
27 | 'a' => ['key1' => 'test1', 'key2' => 1, 'key3' => 'a'],
28 | 'b' => ['key1' => 'test1', 'key2' => 2]
29 | ],
30 | 'test2' => [
31 | 2 => ['key1' => 'test2', 'key2' => 3, 'key3' => 'b']
32 | ],
33 | ]
34 |
35 | Arr::group($array, 'key2') ->
36 | [
37 | 1 => [
38 | 'a' => ['key1' => 'test1', 'key2' => 1, 'key3' => 'a'],
39 | ],
40 | 2 => [
41 | 'b' => ['key1' => 'test1', 'key2' => 2]
42 | ],
43 | 3 => [
44 | 2 => ['key1' => 'test2', 'key2' => 3, 'key3' => 'b']
45 | ],
46 | ]
47 |
48 | Arr::group($array, 'key3') ->
49 | [
50 | 'a' => [
51 | 'a' => ['key1' => 'test1', 'key2' => 1, 'key3' => 'a']
52 | ],
53 | 'b' => [
54 | 2 => ['key1' => 'test2', 'key2' => 3, 'key3' => 'b']
55 | ],
56 | ]
57 |
58 | Arr::group($array, 'key4') -> []
59 | ```
60 |
61 | ## groupObjects
62 |
63 | #### Definition
64 |
65 | ```php
66 | Arr::groupObjects(array $objects, string $method, ...$args): array
67 | ```
68 |
69 | #### Description
70 |
71 | Group array of objects by value returned from specified method
72 |
73 | #### Examples
74 |
75 | ```php
76 | $object1 = new class() {
77 | function test() {
78 | return 'test1';
79 | }
80 | };
81 | $object2 = new class() {
82 | function test() {
83 | return 'test2';
84 | }
85 | };
86 |
87 | Arr::groupObjects([$object1, $object2, $object1], 'test') ->
88 | [
89 | 'test1' => [$object1, $object1],
90 | 'test2' => [$object2],
91 | ]
92 |
93 | // This method is also very useful in conjunction with Arr::flattenSingle to assign unique key for each object
94 | Arr::flattenSingle(Arr::groupObjects([$object1, $object2], 'test')) ->
95 | [
96 | 'test1' => $object1,
97 | 'test2' => $object2,
98 | ]
99 | ```
100 |
101 |
--------------------------------------------------------------------------------
/docs/manipulating-array/mapping.md:
--------------------------------------------------------------------------------
1 | # Mapping
2 |
3 | ## map
4 |
5 | #### Definition
6 |
7 | ```php
8 | Arr::map(array $array, callable $callback, int $mode = Arr::MAP_ARRAY_KEY_VALUE): array
9 | ```
10 |
11 | #### Description
12 |
13 | Applies a callback to the elements of given array. Arguments supplied to callback differs depending on selected `$mode`.
14 |
15 | {% hint style="warning" %}
16 | For backward compatibility using `map(callable, array)` is still possible but is deprecated and will issue appropriate warning
17 | {% endhint %}
18 |
19 | #### Modes
20 |
21 | | Constant name | Description |
22 | | :--- | :--- |
23 | | MAP\_ARRAY\_KEY\_VALUE | Map array using callback in form of `function($key, $value)` |
24 | | MAP\_ARRAY\_VALUE\_KEYS\_LIST | Map array using callback in form of `function($value, $key1, $key2, ...)` |
25 | | MAP\_ARRAY\_KEYS\_ARRAY\_VALUE | Map array using callback in form of `function(array $keys, $value)` |
26 | | MAP\_ARRAY\_VALUE\_KEY | Map array using callback in form of `function($value, $key)` |
27 |
28 | #### Examples
29 |
30 | ```php
31 | $array1 = ['a', 'b', 'c'];
32 | $array2 = [
33 | 1 => [
34 | 2 => 'a',
35 | 3 => 'b',
36 | 4 => [
37 | 5 => 'c',
38 | ],
39 | ],
40 | 'test' => 'd',
41 | ];
42 |
43 | $mapKeyValue = function ($key, $value) {
44 | return "{$key} -> {$value}";
45 | };
46 | // Mind that $value is a first argument here
47 | $mapValueKey = function ($value, $key) {
48 | return "{$key} -> {$value}";
49 | };
50 | $mapKeysValue = function ($keys, $value) {
51 | return implode('.', $keys) . " -> {$value}";
52 | };
53 | $mapValueKeysList = function ($value, $key1, $key2) {
54 | return "$key1.$key2 -> {$value}";
55 | };
56 |
57 | // Equivalent to using MAP_ARRAY_KEY_VALUE as mode (3rd) argument
58 | Arr::map($array1, $mapKeyValue) -> ['0 -> a', '1 -> b', '2 -> c']
59 |
60 | // Resemble array_map function but with array supplied as first argument
61 | Arr::map($array1, $mapValueKey) -> ['0 -> a', '1 -> b', '2 -> c']
62 |
63 | // Map multidimensional array using keys array
64 | Arr::map($array2, $mapKeysValue, Arr::MAP_ARRAY_KEYS_ARRAY_VALUE) ->
65 | [
66 | 1 => [
67 | 2 => '1.2 -> a',
68 | 3 => '1.3 -> b',
69 | 4 => [
70 | 5 => '1.4.5 -> c',
71 | ],
72 | ],
73 | 'test' => 'test -> d',
74 | ]
75 |
76 | // Map multidimensional array using keys list (mind that all keys above 2nd are ignored due to callback function syntax)
77 | Arr::map($array2, $mapValueKeysList, Arr::MAP_ARRAY_VALUE_KEYS_LIST) ->
78 | [
79 | 1 => [
80 | 2 => '1.2 -> a',
81 | 3 => '1.3 -> b',
82 | 4 => [
83 | 5 => '1.4 -> c',
84 | ],
85 | ],
86 | 'test' => 'test -> d',
87 | ]
88 | ```
89 |
90 | ## mapObjects
91 |
92 | #### Definition
93 |
94 | ```php
95 | Arr::mapObjects(array $objects, string $method, ...$args): array
96 | ```
97 |
98 | #### Description
99 |
100 | Map array of object to values returned from objects method
101 |
102 | #### Examples
103 |
104 | ```php
105 | $object = new class() {
106 | function test($arg = 0) {
107 | return 1 + $arg;
108 | }
109 | };
110 | $array = [$object, $object, $object];
111 |
112 | Arr::mapObjects($array, 'test') -> [1, 1, 1]
113 | Arr::mapObjects($array, 'test', 2) -> [3, 3, 3]
114 | ```
115 |
116 |
--------------------------------------------------------------------------------
/docs/manipulating-array/sorting.md:
--------------------------------------------------------------------------------
1 | # Sorting
2 |
3 | ## orderByKeys
4 |
5 | #### Definition
6 |
7 | ```php
8 | Arr::orderByKeys(array $array, mixed $keys, bool $appendUnmatched = true): array
9 | ```
10 |
11 | #### Description
12 |
13 | Order associative array according to supplied keys order
14 |
15 | #### Examples
16 |
17 | ```php
18 | $array = [
19 | 'foo',
20 | 'a' => 'bar',
21 | 'b' => 'test',
22 | 1,
23 | 'c' => ['test' => 2]
24 | ];
25 |
26 | Arr::orderByKeys($array, 'a.0.c.1.b') ->
27 | [
28 | 'a' => 'bar',
29 | 0 => 'foo',
30 | 'c' => ['test' => 2],
31 | 1 => 1,
32 | 'b' => 'test',
33 | ]
34 |
35 | Arr::orderByKeys($array, 'a.0.c') ->
36 | [
37 | 'a' => 'bar',
38 | 0 => 'foo',
39 | 'c' => ['test' => 2],
40 |
41 | 'b' => 'test',
42 | 1 => 1,
43 | ]
44 |
45 | Arr::orderByKeys($array, 'a.0.c', false) ->
46 | [
47 | 'a' => 'bar',
48 | 0 => 'foo',
49 | 'c' => ['test' => 2],
50 | ]
51 | ```
52 |
53 | ## sortByKeys
54 |
55 | #### Definition
56 |
57 | ```php
58 | Arr::sortByKeys(array $array, mixed $keys = null, bool $assoc = true): array
59 | ```
60 |
61 | #### Description
62 |
63 | Sort array of arrays using value specified by key\(s\)
64 |
65 | #### Examples
66 |
67 | ```php
68 | $array = [
69 | 'a' => ['b' => ['c' => 3]],
70 | 'b' => ['b' => ['c' => -1]],
71 | 'c' => ['b' => ['c' => 0]]
72 | ];
73 |
74 | Arr::sortByKeys($array, 'b.c') ->
75 | [
76 | 'c' => ['b' => ['c' => -1]],
77 | 'd' => ['b' => ['c' => 0]],
78 | 'a' => ['b' => ['c' => 3]],
79 | ]
80 |
81 | Arr::sortByKeys($array, 'b.c', false) ->
82 | [
83 | ['b' => ['c' => -1]],
84 | ['b' => ['c' => 0]],
85 | ['b' => ['c' => 3]],
86 | ]
87 |
88 | Arr::sortByKeys(['a' => 3, 'b' => 1, 'c' => 6]) -> ['b' => 1, 'a' => 3, 'c' => 6]
89 | Arr::sortByKeys(['a' => 3, 'b' => 1, 'c' => 6], null, false) -> [1, 3, 6]
90 | ```
91 |
92 | ## sortObjects
93 |
94 | #### Definition
95 |
96 | ```php
97 | Arr::sortObjects(array $objects, string $method, ...$args): array
98 | ```
99 |
100 | #### Description
101 |
102 | Sort array of objects by comparing result of supplied method name
103 |
104 | `$object1->$method(...$args) <=> $object2->$method(...$args)`
105 |
106 | #### Examples
107 |
108 | ```php
109 | $object1 = new class() {
110 | function getValue() {
111 | return 1;
112 | }
113 | };
114 | $object2 = new class() {
115 | function getValue(bool $reverse = false) {
116 | return $reverse ? 1/2 : 2;
117 | }
118 | };
119 | $object3 = new class() {
120 | function getValue(bool $reverse = false) {
121 | return $reverse ? 1/3 : 3;
122 | }
123 | };
124 |
125 | $array = [$object2, $object3, $object1];
126 |
127 | Arr::sortObjects($array, 'getValue') -> [$object1, $object2, $object3]
128 | Arr::sortObjects($array, 'getValue', true) -> [$object3, $object2, $object1]
129 | ```
130 |
131 |
--------------------------------------------------------------------------------
/docs/object-oriented-methods/general-information.md:
--------------------------------------------------------------------------------
1 | # General information
2 |
3 | ### Object creating
4 |
5 | You can create `ArrObj` by calling
6 |
7 | ```php
8 | new ArrObj(array|ArrayAccess $array = [])
9 |
10 | // Or for easier chaining
11 | Arr::obj(array|ArrayAccess $array = [])
12 | ```
13 |
14 | ### Chaining
15 |
16 | For chaining just call standard `Arr` methods without first parameter \(array or ArrayAccess object\).
17 |
18 | As a convenience `ArrObj` contains PHPDoc definitions for every available method, so you don't need to guess their parameters.
19 |
20 | Also you can quickly jump to the corresponding `Arr` method by using `@see` tag.
21 |
22 | To obtain array from object just call `getArray()` as the final method in chain.
23 |
24 | ### Examples
25 |
26 | ```php
27 | // Chain setting nested array values
28 | Arr::obj()->set('foo', 'bar')->set('test.[]', 'test')->getArray() ->
29 | [
30 | 'foo' => 'bar',
31 | 'test' => ['test']
32 | ]
33 |
34 | // Quickly flatten array of objects grouped by id
35 | Arr::obj([...])->groupObjects('getId')->flattenSingle()->getArray()
36 | ```
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/docs/traversing-array/finding.md:
--------------------------------------------------------------------------------
1 | # Finding
2 |
3 | ## find
4 |
5 | #### Definition
6 |
7 | ```php
8 | Arr::find(array|Iterator|IteratorAggregate $array, callable $condition, string $return = self::FIND_RETURN_VALUE): mixed|mixed[]
9 | ```
10 |
11 | #### Description
12 |
13 | Find array \(or iterable object\) element\(s\) that match specified condition.
14 |
15 | #### Modes \(`$return` method argument\)
16 |
17 | | Constant name | Description |
18 | | :--- | :--- |
19 | | FIND\_RETURN\_VALUE | Return first value of an array that match find condition |
20 | | FIND\_RETURN\_KEY | Return first key of an array that match find condition |
21 | | FIND\_RETURN\_ALL | Return array of all values \(preserving original keys\) that match find condition |
22 |
23 | #### Examples
24 |
25 | ```php
26 | $array = [
27 | 'a' => 0,
28 | 'b' => 1,
29 | 3 => 'c',
30 | 4 => 5
31 | ];
32 |
33 |
34 | Arr::find($array, 'boolval') -> 1
35 | Arr::find($array, function ($element) {
36 | return is_string($element);
37 | }) -> 'c'
38 |
39 |
40 | Arr::find($array, 'boolval', Arr::FIND_RETURN_KEY) -> 'b'
41 | Arr::find($array, function ($element) {
42 | return is_string($element);
43 | }, Arr::FIND_RETURN_KEY) -> 3
44 |
45 |
46 | Arr::find($array, 'boolval', Arr::FIND_RETURN_ALL) ->
47 | [
48 | 'b' => 1,
49 | 3 => 'c',
50 | 4 => 5
51 | ]
52 |
53 | Arr::find($array, function ($element) {
54 | return is_string($element);
55 | }, Arr::FIND_RETURN_ALL) ->
56 | [
57 | 3 => 'c',
58 | ]
59 |
60 | Arr::find($array, function ($element) {
61 | return is_number($element);
62 | }, Arr::FIND_RETURN_ALL) ->
63 | [
64 | 'a' => 0,
65 | 'b' => 1,
66 | 4 => 5
67 | ]
68 | ```
69 |
70 |
--------------------------------------------------------------------------------
/docs/traversing-array/iterating.md:
--------------------------------------------------------------------------------
1 | # Iterating
2 |
3 | ## each
4 |
5 | #### Definition
6 |
7 | ```php
8 | Arr::each(array|Iterator|IteratorAggregate $iterable, callable $callback, int $mode = self::EACH_VALUE): array|Iterator|IteratorAggregate
9 | ```
10 |
11 | #### Description
12 |
13 | Traverse through array or iterable object and call callback for each element \(ignoring the result\).
14 |
15 | #### Modes
16 |
17 |
18 |
19 |
20 | Constant name |
21 | Description |
22 |
23 |
24 |
25 |
26 | EACH_VALUE |
27 | Iterate using callback in form of function($value)
28 | |
29 |
30 |
31 | EACH_KEY_VALUE |
32 | Iterate using callback in form of function($key, $value)
33 | |
34 |
35 |
36 | EACH_VALUE_KEY |
37 | Iterate using callback in form of function($value, $key)
38 | |
39 |
40 |
41 | EACH_VALUE_KEYS_LIST |
42 |
43 | Iterate using callback in form of function($value, $key1, $key2, ...)
44 |
46 |
47 | Only for array $iterable
48 |
49 | |
50 |
51 |
52 | EACH_KEYS_ARRAY_VALUE |
53 |
54 | Iterate using callback in form of function(array $keys, $value)
55 |
56 | Only for array $iterable
57 |
58 | |
59 |
60 |
61 |
#### Examples
62 |
63 | ```php
64 | $array = [
65 | 1 => [
66 | 2 => 'a',
67 | 3 => 'b',
68 | 4 => [
69 | 5 => 'c',
70 | ],
71 | ],
72 | 'test' => 'd',
73 | ];
74 |
75 | // Value only - using default EACH_VALUE mode
76 | Arr::each($array, function ($value) {
77 | print_r($value);
78 | // [ 2 => 'a', ...]
79 | // 'd'
80 | });
81 |
82 | // Key, Value
83 | Arr::each($array, function ($key, $value) {
84 | echo "{$key}: \t\t";
85 | print_r($value);
86 | // 1: [2 => 'a', ...]
87 | // test: 'd'
88 | }, Arr::EACH_KEY_VALUE);
89 |
90 | // Value, Key
91 | Arr::each($array, function ($value, $key) {
92 | echo "{$key}: \t\t";
93 | print_r($value);
94 | // 1: [2 => 'a', ...]
95 | // test: 'd'
96 | }, Arr::EACH_VALUE_KEY);
97 |
98 | // Value, Keys list
99 | Arr::each($array, function ($value, ...$keys) {
100 | echo implode('.', $keys) . ': \t\t';
101 | print_r($value);
102 | // 1.2: 'a'
103 | // 1.3: 'b'
104 | // 1.4.5: 'c'
105 | // test: 'd'
106 | }, Arr::EACH_VALUE_KEYS_LIST);
107 |
108 |
109 | // Keys array, value
110 | Arr::each($array, function (array $keys, $value) {
111 | echo implode('.', $keys) . ': \t\t';
112 | print_r($value);
113 | // 1.2: 'a'
114 | // 1.3: 'b'
115 | // 1.4.5: 'c'
116 | // test: 'd'
117 | }, Arr::EACH_KEYS_ARRAY_VALUE);
118 | ```
119 |
120 |
--------------------------------------------------------------------------------
/docs/utility-methods/forcearray.md:
--------------------------------------------------------------------------------
1 | # forceArray
2 |
3 | #### Definition
4 |
5 | ```php
6 | Arr::forceArray(mixed $var, int $flag = self::FORCE_ARRAY_ALL): mixed
7 | ```
8 |
9 | #### Description
10 |
11 | Make variable an array \(according to flag settings\)
12 |
13 | #### Examples
14 |
15 | ```php
16 | Arr::forceArray(0) -> [0]
17 | Arr::forceArray('test') -> ['test']
18 |
19 | Arr::forceArray(null) -> [null]
20 | Arr::forceArray(null, Arr::FORCE_ARRAY_PRESERVE_NULL) -> null
21 |
22 |
23 | $object = new stdClass();
24 |
25 | Arr::forceArray($object) -> [$object]
26 | // With this flag all objects remain intact
27 | Arr::forceArray($object, Arr::FORCE_ARRAY_PRESERVE_OBJECTS) -> $object
28 |
29 |
30 | $object = new ArrayObject();
31 |
32 | Arr::forceArray($object) -> [$object]
33 | // With this flag objects implementing ArrayAccess remain intact
34 | Arr::forceArray($object, Arr::FORCE_ARRAY_PRESERVE_ARRAY_OBJECTS) -> $object
35 | ```
36 |
37 |
--------------------------------------------------------------------------------
/docs/utility-methods/getdepth.md:
--------------------------------------------------------------------------------
1 | # getDepth
2 |
3 | #### Definition
4 |
5 | ```php
6 | Arr::getDepth(array $array): int
7 | ```
8 |
9 | #### Description
10 |
11 | Get nesting depth of an array
12 |
13 | #### Examples
14 |
15 | ```php
16 | Arr::getDepth([]) -> 1
17 |
18 | Arr::getDepth([1, 2, 3]) -> 1
19 |
20 | Arr::getDepth([1, 2 => [], 3]) -> 2
21 |
22 | Arr::getDepth([
23 | 1,
24 | 2 => [
25 | 3 => [
26 | 4 => []
27 | ]
28 | ],
29 | 5 => []
30 | ]) -> 4
31 | ```
32 |
33 |
--------------------------------------------------------------------------------
/docs/utility-methods/getfirstkey.md:
--------------------------------------------------------------------------------
1 | # getFirstKey
2 |
3 | #### Definition
4 |
5 | ```php
6 | Arr::getFirstKey(array $array): int|string|null
7 | ```
8 |
9 | #### Description
10 |
11 | Get the first key of the given array without affecting the internal array pointer.
12 |
13 | Returns `null` if array is empty.
14 |
15 | #### Examples
16 |
17 | ```php
18 | Arr::getFirstKey(['a' => 1, 'b' => 2, 'c' => 3]) -> 'a'
19 |
20 | Arr::getFirstKey([1, 2, 3]) -> 0
21 |
22 | Arr::getFirstKey([]) -> null
23 | ```
24 |
25 |
--------------------------------------------------------------------------------
/docs/utility-methods/getfirstvalue.md:
--------------------------------------------------------------------------------
1 | # getFirstValue
2 |
3 | #### Definition
4 |
5 | ```php
6 | Arr::getFirstValue(array $array): mixed|null
7 | ```
8 |
9 | #### Description
10 |
11 | Get the first value of the given array without affecting the internal array pointer.
12 |
13 | Returns `null` if array is empty.
14 |
15 | #### Examples
16 |
17 | ```php
18 | Arr::getFirstValue(['a' => 1, 'b' => 2, 'c' => 3]) -> 1
19 |
20 | Arr::getFirstValue([1, 2, 3, 4, 5]) -> 1
21 |
22 | Arr::getFirstValue([]) -> null
23 | ```
24 |
25 |
--------------------------------------------------------------------------------
/docs/utility-methods/getlastkey.md:
--------------------------------------------------------------------------------
1 | # getLastKey
2 |
3 | #### Definition
4 |
5 | ```php
6 | Arr::getLastKey(array $array): int|string|null
7 | ```
8 |
9 | #### Description
10 |
11 | Get the last key of the given array without affecting the internal array pointer.
12 |
13 | Returns `null` if array is empty.
14 |
15 | #### Examples
16 |
17 | ```php
18 | Arr::getLastKey(['a' => 1, 'b' => 2, 'c' => 3]) -> 'c'
19 |
20 | Arr::getLastKey([1, 2, 3]) -> 2
21 |
22 | Arr::getLastKey([]) -> null
23 | ```
24 |
25 |
--------------------------------------------------------------------------------
/docs/utility-methods/getlastvalue.md:
--------------------------------------------------------------------------------
1 | # getLastValue
2 |
3 | #### Definition
4 |
5 | ```php
6 | Arr::getLastValue(array $array): mixed|null
7 | ```
8 |
9 | #### Description
10 |
11 | Get the last value of the given array without affecting the internal array pointer.
12 |
13 | Returns `null` if array is empty.
14 |
15 | #### Examples
16 |
17 | ```php
18 | Arr::getLastValue(['a' => 1, 'b' => 2, 'c' => 3]) -> 3
19 |
20 | Arr::getLastValue([1, 2, 3, 4, 5]) -> 5
21 |
22 | Arr::getLastValue([]) -> null
23 | ```
24 |
25 |
--------------------------------------------------------------------------------
/docs/utility-methods/nth.md:
--------------------------------------------------------------------------------
1 | # nth
2 |
3 | #### Definition
4 |
5 | ```php
6 | Arr::nth(array $array, int $A = 1, int $B = 0): array
7 | ```
8 |
9 | #### Aliases
10 |
11 | ```php
12 | even(array $array) -> nth($array, 2)
13 | odd(array $array) -> nth($array, 2, 1)
14 | ```
15 |
16 | #### Description
17 |
18 | Gets array elements with index matching condition $An + $B \(preserving original keys\)
19 |
20 | #### Examples
21 |
22 | ```php
23 | $array = [
24 | 'a' => 0,
25 | 'b' => 1,
26 | 'c' => 2,
27 | 'd' => 3,
28 | 'e' => 4,
29 | ];
30 |
31 | Arr::nth($array, 2, 3) ->
32 | [
33 | 'c' => 2,
34 | 'e' => 4,
35 | ]
36 |
37 | Arr::nth($array, 2) === Arr::even($array) ->
38 | [
39 | 'a' => 0,
40 | 'c' => 2,
41 | 'e' => 4,
42 | ]
43 |
44 | Arr::nth($array, 2, 1) === Arr::odd($array) ->
45 | [
46 | 'b' => 1,
47 | 'd' => 3,
48 | ]
49 | ```
50 |
51 |
--------------------------------------------------------------------------------
/docs/utility-methods/pack.md:
--------------------------------------------------------------------------------
1 | # pack
2 |
3 | #### Definition
4 |
5 | ```php
6 | Arr::pack(array $array): array
7 | ```
8 |
9 | #### Description
10 |
11 | Converts map of keys concatenated by dot and corresponding values to multidimensional array.
12 |
13 | Inverse of [`unpack`]().
14 |
15 | #### Examples
16 |
17 | Let's use result array from [example below]().
18 |
19 | ```php
20 | $array = [
21 | 'key1.key2.key3.foo' => 'test',
22 | 'key1.key2.key3.bar' => 'test2',
23 | 'key1.abc.0' => 'test3',
24 | 'xyz' => 'test4',
25 | '0' => 'test5',
26 | ];
27 |
28 | Arr::pack($array) ->
29 | [
30 | 'key1' => [
31 | 'key2' => [
32 | 'key3' => [
33 | 'foo' => 'test',
34 | 'bar' => 'test2',
35 | ]
36 | ]
37 | 'abc' => ['test3'],
38 | ],
39 | 'xyz' => 'test4',
40 | 'test5'
41 | ]
42 |
43 | // Unpack is inverse operation to pack
44 | $array2 = [
45 | 'test',
46 | [
47 | 'foo' => ['bar'],
48 | 'a' => [
49 | 'b' => 1
50 | ]
51 | ]
52 | ];
53 |
54 | Arr::unpack(Arr::pack($array2)) === $array2 -> true
55 | ```
56 |
57 |
--------------------------------------------------------------------------------
/docs/utility-methods/random.md:
--------------------------------------------------------------------------------
1 | # random
2 |
3 | #### Definition
4 |
5 | ```php
6 | Arr::random(array $array, int $count = 1): mixed
7 | ```
8 |
9 | #### Description
10 |
11 | Get random array value\(s\)
12 |
13 | #### Examples
14 |
15 | ```php
16 | $array = [
17 | 'a' => 1,
18 | 'b' => 2,
19 | 'c' => 3,
20 | 'd' => 4,
21 | 'e' => 5
22 | ];
23 |
24 | Arr::random($array) -> 5
25 | Arr::random($array) -> 1
26 | Arr::random($array) -> 3
27 | Arr::random($array) -> 2
28 |
29 | Arr::random($array, 2) -> ['d' => 4, 'a' => 1]
30 | Arr::random($array, 2) -> ['b' => 2, 'e' => 5]
31 | Arr::random($array, 2) -> ['c' => 3, 'b' => 2]
32 | ```
33 |
34 |
--------------------------------------------------------------------------------
/docs/utility-methods/shuffle.md:
--------------------------------------------------------------------------------
1 | # shuffle
2 |
3 | #### Definition
4 |
5 | ```php
6 | Arr::shuffle(array $array): array
7 | ```
8 |
9 | #### Description
10 |
11 | Shuffle array preserving keys and returning new shuffled array
12 |
13 | #### Examples
14 |
15 | ```php
16 | $array = [
17 | 'a' => 1,
18 | 'b' => 2,
19 | 'c' => 3,
20 | 'd' => 4,
21 | 'e' => 5,
22 | ];
23 |
24 | Arr::shuffle($array) ->
25 | [
26 | 'e' => 5,
27 | 'a' => 1,
28 | 'c' => 3,
29 | 'b' => 2,
30 | 'd' => 4,
31 | ]
32 |
33 | Arr::shuffle($array) ->
34 | [
35 | 'a' => 1,
36 | 'e' => 5,
37 | 'b' => 2,
38 | 'd' => 4,
39 | 'c' => 3,
40 | ]
41 | ```
42 |
43 |
--------------------------------------------------------------------------------
/docs/utility-methods/unpack.md:
--------------------------------------------------------------------------------
1 | # unpack
2 |
3 | #### Definition
4 |
5 | ```php
6 | Arr::unpack(array $array, int $mode = Arr::UNPACK_ALL): array
7 | ```
8 |
9 | #### Description
10 |
11 | Converts multidimensional array to map of keys concatenated by dot and corresponding values.
12 |
13 | Inverse of [`pack`]().
14 |
15 | #### Modes
16 |
17 | | Mode | Description |
18 | | :--- | :--- |
19 | | `Arr::UNPACK_ALL` | Every array will be unpacked |
20 | | `Arr::UNPACK_PRESERVE_LIST_ARRAY` | Preserve arrays with highest nesting level \(if they are not associative\) as element values instead of unpacking them |
21 | | `Arr::UNPACK_PRESERVE_ASSOC_ARRAY` | Preserve arrays with highest nesting level \(if they are associative\) as element values instead of unpacking them |
22 | | `Arr::UNPACK_PRESERVE_ARRAY` | Preserve all arrays with highest nesting level as element values instead of unpacking them |
23 |
24 | #### Examples
25 |
26 | ```php
27 | $array = [
28 | 'key1' => [
29 | 'key2' => [
30 | 'key3' => [
31 | 'foo' => 'test',
32 | 'bar' => 'test2',
33 | ]
34 | ]
35 | 'abc' => ['test3'],
36 | ],
37 | 'xyz' => 'test4',
38 | 'test5'
39 | ];
40 |
41 | // Equal to Arr::unpack($array, Arr::UNPACK_ALL)
42 | Arr::unpack($array) ->
43 | [
44 | 'key1.key2.key3.foo' => 'test',
45 | 'key1.key2.key3.bar' => 'test2',
46 | 'key1.abc.0' => 'test3',
47 | 'xyz' => 'test4',
48 | '0' => 'test5',
49 | ]
50 |
51 | Arr::unpack($array, Arr::UNPACK_PRESERVE_LIST_ARRAY) ->
52 | [
53 | 'key1.key2.key3.foo' => 'test',
54 | 'key1.key2.key3.bar' => 'test2',
55 | // Preserve list array as value
56 | 'key1.abc' => ['test3'],
57 | 'xyz' => 'test4',
58 | '0' => 'test5',
59 | ]
60 |
61 | Arr::unpack($array, Arr::UNPACK_PRESERVE_ASSOC_ARRAY) ->
62 | [
63 | // Preserve assoc array as value
64 | 'key1.key2.key3' => [
65 | 'foo' => 'test',
66 | 'bar' => 'test2',
67 | ],
68 | 'key1.abc.0' => 'test3',
69 | 'xyz' => 'test4',
70 | '0' => 'test5',
71 | ]
72 |
73 | Arr::unpack($array, Arr::UNPACK_PRESERVE_ARRAY) ->
74 | [
75 | // Preserve assoc array as value
76 | 'key1.key2.key3' => [
77 | 'foo' => 'test',
78 | 'bar' => 'test2',
79 | ],
80 | // Preserve list array as value
81 | 'key1.abc' => ['test3'],
82 | 'xyz' => 'test4',
83 | '0' => 'test5',
84 | ]
85 | ```
86 |
87 |
--------------------------------------------------------------------------------
/docs/utility-methods/untitled.md:
--------------------------------------------------------------------------------
1 | # createMulti
2 |
3 | #### Definition
4 |
5 | ```php
6 | Arr::createMulti(array $keys, ?array $values = null): array
7 | ```
8 |
9 | #### Description
10 |
11 | Create multidimensional array using either first param as config of keys and values or separate keys and values arrays
12 |
13 | #### Examples
14 |
15 | ```php
16 | Arr::createMulti([
17 | 'test.[]' => '123',
18 | 'test.test2.test3' => 'abc',
19 | 'test.test2.[]' => 567,
20 | 'test.[].1' => 'def',
21 | ]) ->
22 | [
23 | 'test' => [
24 | '123',
25 | 'test2' => [
26 | 'test3' => 'abc',
27 | 567
28 | ],
29 | [
30 | 1 => 'def'
31 | ],
32 | ]
33 | ]
34 |
35 | Arr::createMulti([
36 | ['test', '[]'],
37 | ['test', 'test2', 'test3'],
38 | ['test', 'test2', '[]'],
39 | ['test', '[]', 1],
40 | ], [
41 | '123',
42 | 'abc',
43 | 567,
44 | 'def',
45 | ]) ->
46 | [
47 | 'test' => [
48 | '123',
49 | 'test2' => [
50 | 'test3' => 'abc',
51 | 567
52 | ],
53 | [
54 | 1 => 'def'
55 | ],
56 | ]
57 | ]
58 |
59 | // In case of empty keys argument simply return new empty array
60 | Arr::createMulti([]) -> []
61 | ```
62 |
63 |
--------------------------------------------------------------------------------
/docs/validating-array/check.md:
--------------------------------------------------------------------------------
1 | # check
2 |
3 | #### Definition
4 |
5 | {% code title="" %}
6 | ```php
7 | Arr::check(array $array, mixed|callable $condition, int $flag = 0): bool
8 | ```
9 | {% endcode %}
10 |
11 | #### Description
12 |
13 | Check if some or every array element meets specified condition.
14 |
15 | If `CHECK_SOME` flag is NOT present then every array element must meet specified condition in order to pass check.
16 |
17 | #### Condition
18 |
19 |
20 |
21 |
22 | $condition type |
23 | Description |
24 |
25 |
26 |
27 |
28 | callable
29 | |
30 |
31 | Callable should return truthy or falsy value (while using CHECK_STRICT flag,
32 | return values other than true are treated as false ).
33 |
34 | Callable is supplied with either only element value (function($value) )
35 | or pair of element value and key (function($value, $key) )
36 | as arguments.
37 | Arguments amount depend on callable definition and is dynamically
38 | resolved using reflection (defaults
39 | to 2 - value and key)
40 | |
41 |
42 |
43 | mixed
44 | |
45 |
46 | If condition type is different than callable then every
47 | array element is compared against it value.
48 |
49 | $value == $condition by default
50 | $value === $condition if CHECK_STRICT flag is enabled
51 | |
52 |
53 |
54 |
#### Flags
55 |
56 | Can be used as stand alone \(i.e.`Arr::CHECK_STRICT`\) as well as in conjunction \(i.e. `Arr::CHECK_STRICT | Arr::CHECK_SOME`\)
57 |
58 | {% hint style="info" %}
59 | `$flag` argument used to be a boolean parameter called `$strict`
60 |
61 | But do not worry, it it is fully backward compatible due to in-flight type conversion from `bool` to `int`
62 | {% endhint %}
63 |
64 |
65 |
66 |
67 | Constant name |
68 | Description |
69 |
70 |
71 |
72 |
73 | CHECK_STRICT
74 | |
75 |
76 | In case condition is callable check if it result is
77 | exactly true
78 |
79 |
80 | If condition is not callable , then check if array element
81 | is equal to it both by value and type
82 |
83 | See Condition section for more info
84 | |
85 |
86 |
87 | CHECK_SOME
88 | |
89 |
90 | Check will return true on first array element that match specified condition or
91 | false if none of them matches it.
92 |
93 | By default check method will return true only if
94 | ALL of array elements meet specified condition
95 |
96 | |
97 |
98 |
99 |
#### Examples
100 |
101 | ```php
102 | $array = [1, '1', true];
103 |
104 | // Every array element is EQUAL to 1 ($value == '1')
105 | Arr::check($array, '1') -> true
106 |
107 | // Only one array element is the SAME as 1 ($value === '1') which is not sufficient
108 | Arr::check($array, '1', Arr::CHECK_STRICT) -> false
109 |
110 | // When CHECK_SOME flag is present, one element is sufficient to pass check
111 | Arr::check($array, '1', Arr::CHECK_STRICT | Arr::CHECK_SOME) -> true
112 |
113 | // You can also use built-in functions to validate whole array
114 | Arr::check($array, 'is_int') -> false
115 | Arr::check($array, 'is_string') -> false
116 |
117 | // When CHECK_SOME flag is present only one element need to meet specified condition
118 | Arr::check($array, 'is_int', Arr::CHECK_SOME) -> true
119 | Arr::check($array, 'is_string', Arr::CHECK_SOME) -> true
120 |
121 | // Every value of array is truthy
122 | Arr::check($array, function ($value) { return $value; }) -> true
123 | // Above check can be simplified to
124 | Arr::check($array, true) -> true
125 | // Or even shorter
126 | Arr::check($array, 1) -> true
127 |
128 | // When CHECK_STRICT flag is present, check will fail for callback return values other true
129 | Arr::check($array, function ($value) { return $value; }, Arr::CHECK_STRICT) -> false
130 | // But when callback is written as follows check will pass
131 | Arr::check($array, function ($value) { return boolval($value); }, Arr::CHECK_STRICT) -> true
132 | // Above check will pass if we add CHECK_SOME flag cause of of the elements is exactly true
133 | Arr::check($array, function ($value) { return $value; }, Arr::CHECK_STRICT | Arr::CHECK_SOME) -> true
134 |
135 |
136 | // Callback function arguments count is automatically detected
137 | Arr::check($array, function ($value, $key) { return $value > $key; }) -> false
138 | // First array element 1 is greater than its key 0
139 | Arr::check($array, function ($value, $key) { return $value > $key; }, Arr::CHECK_SOME) -> true
140 | ```
141 |
142 |
--------------------------------------------------------------------------------
/docs/validating-array/haskeys.md:
--------------------------------------------------------------------------------
1 | # hasKeys
2 |
3 | #### Definition
4 |
5 | ```php
6 | Arr::hasKeys(array $array, mixed $keys, bool $strict = false): bool
7 | ```
8 |
9 | #### Description
10 |
11 | Check if array has specified keys \( all required, when `$strict` is `true`\).
12 |
13 | #### Examples
14 |
15 | ```php
16 | $array = ['key1' => 1, 'key2' => 2, 'key3' => 3];
17 |
18 | Arr::hasKeys($array, ['key2', 'key3']) -> true
19 |
20 | Arr::hasKeys($array, 'key1.key2') -> true
21 |
22 | Arr::hasKeys($array, ['test', 'key1']) -> true
23 |
24 | Arr::hasKeys($array, ['test', 'key1'], true) -> false
25 |
26 | Arr::hasKeys($array, 'test') -> false
27 | ```
28 |
29 |
--------------------------------------------------------------------------------
/docs/validating-array/isarrayofarrays.md:
--------------------------------------------------------------------------------
1 | # isArrayOfArrays
2 |
3 | #### Definition
4 |
5 | ```php
6 | Arr::isArrayOfArrays(array $array): bool
7 | ```
8 |
9 | #### Description
10 |
11 | Check if every array element is array
12 |
13 | #### Examples
14 |
15 | ```php
16 | Arr::isArrayOfArrays([]) -> false
17 |
18 | Arr::isArrayOfArrays([[], []]) -> true
19 |
20 | Arr::isArrayOfArrays([1, 2 => []]) -> false
21 | ```
22 |
23 |
--------------------------------------------------------------------------------
/docs/validating-array/isassoc.md:
--------------------------------------------------------------------------------
1 | # isAssoc
2 |
3 | #### Definition
4 |
5 | ```php
6 | Arr::isAssoc(array $array, bool $strict = false): bool
7 | ```
8 |
9 | #### Description
10 |
11 | Check if array is associative
12 |
13 | #### Examples
14 |
15 | ```php
16 | $array = ['a' => 1, 'b' => 3, 1 => 'd', 'c'];
17 |
18 | Arr::isAssoc($array) -> true
19 | Arr::isAssoc($array, true) -> true
20 |
21 |
22 | $array = [1 => 1, 2 => 2, 3 => 3];
23 |
24 | // There are no string keys
25 | Arr::isAssoc($array) -> false
26 |
27 | // However indexes are not automatically generated (starting from 0 up)
28 | Arr::isAssoc($array, true) -> true
29 |
30 | // In this case keys are automatically generated
31 | Arr::isAssoc([1, 2, 3], true) -> false
32 |
33 | // Which is equal to this
34 | Arr::isAssoc([0 => 1, 1 => 2, 2 => 3], true) -> false
35 | ```
36 |
37 |
--------------------------------------------------------------------------------
/docs/validating-array/isempty.md:
--------------------------------------------------------------------------------
1 | # isEmpty
2 |
3 | #### Definition
4 |
5 | ```php
6 | Arr::isEmpty(mixed $array): bool
7 | ```
8 |
9 | #### Description
10 |
11 | Recursively check if all of array values match empty condition.
12 |
13 | #### Examples
14 |
15 | ```php
16 | Arr::isEmpty(null) -> true
17 |
18 | Arr::isEmpty([]) -> true
19 |
20 | Arr::isEmpty([0 => [0], [], null, [false]) -> true
21 | Arr::isEmpty([0 => [0 => 'a'], [], null, [false]]) -> false
22 | ```
23 |
24 |
--------------------------------------------------------------------------------
/docs/validating-array/isnested.md:
--------------------------------------------------------------------------------
1 | # isNested
2 |
3 | #### Definition
4 |
5 | ```php
6 | Arr::isNested(array $array): bool
7 | ```
8 |
9 | #### Description
10 |
11 | Check if any element of an array is also an array
12 |
13 | #### Examples
14 |
15 | ```php
16 | Arr::isNested([]) -> false
17 |
18 | Arr::isNested([1, 2, 3]) -> false
19 |
20 | Arr::isNested([1, 2 => [], 3]) -> true
21 |
22 | Arr::isNested([1, 2 => [[[]]], 3 => []]) -> true
23 | ```
24 |
25 |
--------------------------------------------------------------------------------
/docs/validating-array/isnumeric.md:
--------------------------------------------------------------------------------
1 | # isNumeric
2 |
3 | #### Definition
4 |
5 | ```php
6 | Arr::isNumeric(array $array): bool
7 | ```
8 |
9 | #### Description
10 |
11 | Check if array contain only numeric values
12 |
13 | #### Examples
14 |
15 | ```php
16 | Arr::isNumeric([1, '2', '3e10', 5.0002]) -> true
17 |
18 | Arr::isNumeric([1, '2', '3e10', 5.0002, 'a']) -> false
19 | ```
20 |
21 |
--------------------------------------------------------------------------------
/docs/validating-array/isunique.md:
--------------------------------------------------------------------------------
1 | # isUnique
2 |
3 | #### Definition
4 |
5 | ```php
6 | Arr::isUnique(array $array, bool $strict = false): bool
7 | ```
8 |
9 | #### Description
10 |
11 | Check if array values are unique
12 |
13 | #### Examples
14 |
15 | ```php
16 | // Without strict flag 1 is equal to '1'
17 | Arr::isUnique([1, '1', true]) -> false
18 |
19 | Arr::isUnique([1, '1', true], true) -> true
20 | ```
21 |
22 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ./tests
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | ./src
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/Arr.php:
--------------------------------------------------------------------------------
1 | In case condition is callable check if it result is exactly true
100 | * If condition is not callable, then check if array element is equal to it both by value and type
101 | */
102 | const CHECK_STRICT = 1;
103 |
104 | /**
105 | * Check will return true on first array element that match specified condition
106 | */
107 | const CHECK_SOME = 2;
108 |
109 | private const AUTO_INDEX_KEY = '[]';
110 | private const KEY_SEPARATOR = '.';
111 |
112 | /*--------------------------------------------------------------------------------------*\
113 | | Common |
114 | | ****************************************************************************** |
115 | | Basic operations used by other methods |
116 | \*--------------------------------------------------------------------------------------*/
117 |
118 |
119 | /**
120 | * Convert variable into normalized array of keys
121 | *
122 | * Transforms 'key1.key2.key3' strings into ['key1','key2','key3']
123 | *
124 | * When array is supplied, this function preserve only not empty strings and integers
125 | *
126 | * ['', 'test', 5.5, null, 0] -> ['test', 0]
127 | *
128 | *
129 | * @param mixed $keys
130 | * @return array
131 | */
132 | public static function getKeysArray($keys): array
133 | {
134 | if (is_string($keys)) {
135 | return empty($keys) ? [] : explode(self::KEY_SEPARATOR, $keys);
136 | }
137 | return is_null($keys) ? [] : array_filter(array_values(self::forceArray($keys)), function ($value) {
138 | return $value !== null && $value !== '' && (is_string($value) || is_int($value));
139 | });
140 | }
141 |
142 | /**
143 | * Check if specified (nested) key(s) exists in array
144 | *
145 | * @param array $array
146 | * @param mixed $keys Keys needed to access desired array element (for possible formats see getKeysArray method)
147 | * @return bool
148 | * @see Arr::getKeysArray()
149 | */
150 | public static function has(array $array, $keys): bool
151 | {
152 | $keysArray = self::getKeysArray($keys);
153 |
154 | if (empty($keysArray)) {
155 | return false;
156 | }
157 |
158 | $tmp = $array;
159 |
160 | foreach ($keysArray as $key) {
161 | if (!is_array($tmp) || !array_key_exists($key, $tmp)) {
162 | return false;
163 | }
164 | $tmp = $tmp[$key];
165 | }
166 |
167 | return true;
168 | }
169 |
170 | /**
171 | * Check if array has list of specified keys
172 | *
173 | * @param array $array
174 | * @param mixed $keys Keys needed to access desired array element (for possible formats see getKeysArray method)
175 | * @param bool $strict If array must have all of specified keys
176 | * @return bool
177 | * @see Arr::getKeysArray()
178 | */
179 | public static function hasKeys(array $array, $keys, bool $strict = false): bool
180 | {
181 | foreach (self::getKeysArray($keys) as $key) {
182 | if (array_key_exists($key, $array) && !$strict) {
183 | return true;
184 | } elseif (!array_key_exists($key, $array) && $strict) {
185 | return false;
186 | }
187 | }
188 | return $strict ? true : false;
189 | }
190 |
191 | /**
192 | * Alias of Arr::getNestedElement
193 | *
194 | * @param array|ArrayAccess $array Array or object implementing array access to get element from
195 | * @param mixed $keys Keys needed to access desired array element (for possible formats see getKeysArray method)
196 | * @param mixed $default Default value if element was not found
197 | * @return null|mixed
198 | * @see Arr::getNestedElement()
199 | */
200 | public static function get($array, $keys, $default = null)
201 | {
202 | return self::getNestedElement($array, $keys, $default);
203 | }
204 |
205 | /**
206 | * Get nested element of an array or object implementing array access
207 | *
208 | * @param array|ArrayAccess $array Array or object implementing array access to get element from
209 | * @param mixed $keys Keys needed to access desired array element (for possible formats see getKeysArray method)
210 | * @param mixed $default Default value if element was not found
211 | * @return null|mixed
212 | * @see Arr::getKeysArray()
213 | */
214 | public static function getNestedElement($array, $keys, $default = null)
215 | {
216 | $keys = self::getKeysArray($keys);
217 | foreach ($keys as $key) {
218 | if (!is_array($array) && !$array instanceof ArrayAccess) {
219 | return $default;
220 | }
221 | if (($array instanceof ArrayAccess && $array->offsetExists($key)) || array_key_exists($key, $array)) {
222 | $array = $array[$key];
223 | } else {
224 | return $default;
225 | }
226 | }
227 | return $array;
228 | }
229 |
230 | /**
231 | * Alias of Arr::setNestedElement
232 | *
233 | * @param array|ArrayAccess $array Array or object implementing array access to set element on
234 | * @param mixed $keys Keys needed to access desired array element (for possible formats see getKeysArray method)
235 | * @param mixed $value Value to set
236 | * @return array|ArrayAccess Copy of an array with element set
237 | * @see Arr::setNestedElement()
238 | */
239 | public static function set($array, $keys, $value)
240 | {
241 | return self::setNestedElement($array, $keys, $value);
242 | }
243 |
244 | /**
245 | * Set array element specified by keys to the desired value (create missing keys if necessary)
246 | *
247 | * @param array|ArrayAccess $array Array or object implementing array access to set element on
248 | * @param mixed $keys Keys needed to access desired array element (for possible formats see getKeysArray method)
249 | * @param mixed $value Value to set
250 | * @return array|ArrayAccess Copy of an array with element set
251 | * @see Arr::getKeysArray()
252 | */
253 | public static function setNestedElement($array, $keys, $value)
254 | {
255 | $result = $array;
256 | $keysArray = self::getKeysArray($keys);
257 |
258 | // If no keys specified then preserve array
259 | if (empty($keysArray)) {
260 | return $result;
261 | }
262 |
263 | $tmp = &$result;
264 |
265 | while (count($keysArray) > 0) {
266 | $key = array_shift($keysArray);
267 | if (!is_array($tmp)) {
268 | $tmp = [];
269 | }
270 | if ($key === self::AUTO_INDEX_KEY) {
271 | $tmp[] = null;
272 | end($tmp);
273 | $key = key($tmp);
274 | }
275 | $tmp = &$tmp[$key];
276 | }
277 | $tmp = $value;
278 |
279 | return $result;
280 | }
281 |
282 | /**
283 | * Remove element inside array at path specified by keys
284 | *
285 | * @param array $array
286 | * @param mixed $keys Keys needed to access desired array element (for possible formats see getKeysArray method)
287 | * @return array
288 | * @see Arr::getKeysArray()
289 | */
290 | public static function remove(array $array, $keys): array
291 | {
292 | $result = $array;
293 | $keysArray = self::getKeysArray($keys);
294 |
295 | $tmp = &$result;
296 |
297 | while (count($keysArray) > 1) {
298 | $key = array_shift($keysArray);
299 | if (!is_array($tmp) || !array_key_exists($key, $tmp)) {
300 | return $result;
301 | }
302 |
303 | $tmp = &$tmp[$key];
304 | }
305 | $key = array_shift($keysArray);
306 | unset($tmp[$key]);
307 |
308 | return $result;
309 | }
310 |
311 | /**
312 | * Converts map of keys concatenated by dot and corresponding values to multidimensional array
313 | *
314 | * @param array $array
315 | * @return array
316 | */
317 | public static function pack(array $array): array
318 | {
319 | $result = [];
320 |
321 | foreach ($array as $key => $value) {
322 | $result = self::setNestedElement($result, $key, $value);
323 | }
324 |
325 | return $result;
326 | }
327 |
328 | /**
329 | * Converts multidimensional array to map of keys concatenated by dot and corresponding values
330 | *
331 | * @param array $array
332 | * @param int $mode Modify behaviour of unpack (see description of Arr::UNPACK_ constants)
333 | * @return array
334 | */
335 | public static function unpack(array $array, int $mode = self::UNPACK_ALL): array
336 | {
337 | return self::_unpack($array, $mode);
338 | }
339 |
340 | /**
341 | * Check if every element of an array meets specified condition
342 | *
343 | * @param array $array
344 | * @param mixed $condition Either callable performing check or a value which every array element will be compared to.
345 | * Callable is supplied with either only element value or pair of element value and key. Arguments amount depends on callable definition and is dynamically resolved using reflection (defaults to 2 - value and key)
346 | * @param int $flag [optional]
347 | * Determines how condition is processed (see Arr::CHECK_* constants for more info)
348 | *
349 | * - CHECK_STRICT - compare using condition by both value and type
350 | * - CHECK_SOME - return true if any of array elements meet specified condition
351 | *
352 | * @return bool
353 | * @see Arr::CHECK_STRICT
354 | * @see Arr::CHECK_SOME
355 | * @see ReflectionMethod::getNumberOfParameters()
356 | * @see ReflectionFunction::getNumberOfParameters()
357 | */
358 | public static function check(array $array, $condition, int $flag = 0): bool
359 | {
360 | if (is_callable($condition)) {
361 | try {
362 | $reflection = is_array($condition) ?
363 | new ReflectionMethod($condition[0], $condition[1]) :
364 | new ReflectionMethod($condition);
365 |
366 | $paramsCount = $reflection->getNumberOfParameters();
367 | } catch (Throwable $e) {
368 | try {
369 | $reflection = new ReflectionFunction($condition);
370 | $paramsCount = $reflection->getNumberOfParameters();
371 | } catch (Throwable $exception) { // @codeCoverageIgnore
372 | $paramsCount = 2; // @codeCoverageIgnore
373 | }
374 | }
375 | }
376 |
377 | $checkStrict = $flag & self::CHECK_STRICT;
378 | $checkSome = $flag & self::CHECK_SOME;
379 |
380 | foreach ($array as $key => $value) {
381 | if (is_callable($condition)) {
382 | $result = $paramsCount == 1 ? call_user_func($condition, $value) : call_user_func($condition, $value, $key);
383 |
384 | if ($checkStrict ? $result === true : $result) {
385 | if ($checkSome) {
386 | return true;
387 | }
388 | } else {
389 | if (!$checkSome) {
390 | return false;
391 | }
392 | }
393 | } else {
394 | if ($checkStrict ? $value === $condition : $value == $condition) {
395 | if ($checkSome) {
396 | return true;
397 | }
398 | } else {
399 | if (!$checkSome) {
400 | return false;
401 | }
402 | }
403 | }
404 | }
405 |
406 | return $checkSome ? false : true;
407 | }
408 |
409 | private static function _unpack(array $array, int $mode = self::UNPACK_ALL, array $keys = []): array
410 | {
411 | $result = [];
412 |
413 | foreach ($array as $key => $value) {
414 |
415 | if (is_array($value) && !(
416 | // Check if value IS NOT a subject for preserve mode
417 | !self::isNested($value) && // Preserve mode only work for highest depth elements
418 | (
419 | ($mode === self::UNPACK_PRESERVE_LIST_ARRAY && !self::isAssoc($value, true)) ||
420 | ($mode === self::UNPACK_PRESERVE_ASSOC_ARRAY && self::isAssoc($value, true)) ||
421 | $mode === self::UNPACK_PRESERVE_ARRAY
422 | )
423 | )) {
424 | $keys[] = $key;
425 | $result += self::_unpack($value, $mode, $keys);
426 | array_pop($keys);
427 | } else {
428 | $result[implode(self::KEY_SEPARATOR, array_merge($keys, [$key]))] = $value;
429 | }
430 | }
431 |
432 | return $result;
433 | }
434 |
435 | /*--------------------------------------------------------------------------------------*\
436 | | Validation |
437 | | ****************************************************************************** |
438 | | Flexible check method and various specific checks |
439 | \*--------------------------------------------------------------------------------------*/
440 |
441 | /**
442 | * Recursively check if all of array values match empty condition
443 | *
444 | * @param array|ArrayAccess $array
445 | * @return boolean
446 | */
447 | public static function isEmpty($array): bool
448 | {
449 | if (is_array($array)) {
450 | foreach ($array as $v) {
451 | if (!self::isEmpty($v)) {
452 | return false;
453 | }
454 | }
455 | } elseif (!empty($array)) {
456 | return false;
457 | }
458 |
459 | return true;
460 | }
461 |
462 | /**
463 | * Check if array is associative
464 | *
465 | * @param array $array
466 | * @param bool $strict
467 | * If false then this function will match any array that doesn't contain integer keys.
468 | * If true then this function match only arrays with sequence of integers starting from zero (range from 0 to elements_number - 1) as keys.
469 | *
470 | * @return boolean
471 | */
472 | public static function isAssoc(array $array, bool $strict = false): bool
473 | {
474 | if (empty($array)) {
475 | return false;
476 | }
477 |
478 | if ($strict) {
479 | return array_keys($array) !== range(0, count($array) - 1);
480 | } else {
481 | foreach (array_keys($array) as $key) {
482 | if (!is_int($key)) {
483 | return true;
484 | }
485 | }
486 | return false;
487 | }
488 | }
489 |
490 | /**
491 | * Check if array contain only numeric values
492 | *
493 | * @param array $array
494 | * @return bool
495 | */
496 | public static function isNumeric(array $array): bool
497 | {
498 | return self::check($array, 'is_numeric');
499 | }
500 |
501 | /**
502 | * Check if array values are unique
503 | *
504 | * @param array $array
505 | * @param bool $strict If it should also compare type
506 | * @return bool
507 | */
508 | public static function isUnique(array $array, bool $strict = false): bool
509 | {
510 | if ($strict) {
511 | foreach ($array as $key => $value) {
512 | $keys = array_keys($array, $value, true);
513 | if (count($keys) > 1 || $keys[0] !== $key) {
514 | return false;
515 | }
516 | }
517 | return true;
518 | }
519 | return array_unique(array_values($array), SORT_REGULAR) === array_values($array);
520 | }
521 |
522 | /**
523 | * Check if any element of an array is also an array
524 | *
525 | * @param array $array
526 | * @return bool
527 | */
528 | public static function isNested(array $array): bool
529 | {
530 | foreach ($array as $element) {
531 | if (is_array($element)) {
532 | return true;
533 | }
534 | }
535 |
536 | return false;
537 | }
538 |
539 | /**
540 | * Check if every element of an array is array
541 | *
542 | * @param array $array
543 | * @return bool
544 | */
545 | public static function isArrayOfArrays(array $array): bool
546 | {
547 | // If empty array
548 | if (count($array) === 0) {
549 | return false;
550 | }
551 | foreach ($array as $element) {
552 | if (!is_array($element)) {
553 | return false;
554 | }
555 | }
556 | return true;
557 | }
558 |
559 | /*--------------------------------------------------------------------------------------*\
560 | | Manipulation |
561 | | ****************************************************************************** |
562 | | Well known methods (like map, filter, group etc.) in 2 variants: regular and objects |
563 | \*--------------------------------------------------------------------------------------*/
564 |
565 | /**
566 | * Applies a callback to the elements of given array
567 | *
568 | * @param array|callable $array
569 | * @param callable|array $callback Callback to run for each element of array
570 | * @param int $mode Determines callback arguments order and format
571 | *
572 | * MAP_ARRAY_KEY_VALUE -> callback($key, $value)
573 | * MAP_ARRAY_VALUE_KEYS_LIST -> callback($value, $key1, $key2, ...)
574 | * MAP_ARRAY_KEYS_ARRAY_VALUE -> callback(array $keys, $value)
575 | * MAP_ARRAY_VALUE_KEY -> callback($value, $key)
576 | * @return array
577 | */
578 | public static function map($array, $callback, int $mode = self::MAP_ARRAY_KEY_VALUE): array
579 | {
580 | // If has old arguments order then swap and issue warning
581 | if (is_callable($array) && is_array($callback)) {
582 | $tmp = $array;
583 | $array = $callback;
584 | $callback = $tmp;
585 | trigger_error('Supplying callback as first argument to Arr::map method is deprecated and will trigger error in next major release. Please use new syntax -> Arr::map(array $array, callback $callback, int $mode)', E_USER_DEPRECATED);
586 | }
587 | $result = [];
588 |
589 | switch ($mode) {
590 | case self::MAP_ARRAY_KEY_VALUE:
591 | foreach ($array as $key => $value) {
592 | $result[$key] = $callback($key, $value);
593 | }
594 | break;
595 | case self::MAP_ARRAY_VALUE_KEY:
596 | foreach ($array as $key => $value) {
597 | $result[$key] = $callback($value, $key);
598 | }
599 | break;
600 | case self::MAP_ARRAY_VALUE_KEYS_LIST:
601 | foreach (self::unpack($array) as $dotKeys => $value) {
602 | $keys = self::getKeysArray($dotKeys);
603 | $result = self::setNestedElement($result, $keys, $callback($value, ...$keys));
604 | }
605 | break;
606 | case self::MAP_ARRAY_KEYS_ARRAY_VALUE:
607 | foreach (self::unpack($array) as $dotKeys => $value) {
608 | $keys = self::getKeysArray($dotKeys);
609 | $result = self::setNestedElement($result, $keys, $callback($keys, $value));
610 | }
611 | break;
612 | }
613 |
614 | return $result;
615 | }
616 |
617 | /**
618 | * Map array of object to values returned from objects method
619 | *
620 | * This method leaves values other than objects intact
621 | *
622 | * @param array $objects Array of objects
623 | * @param string $method Object method name
624 | * @param mixed ...$args Method arguments
625 | * @return array
626 | */
627 | public static function mapObjects(array $objects, string $method, ...$args): array
628 | {
629 | $return = [];
630 |
631 | foreach ($objects as $key => $value) {
632 | if (is_object($value)) {
633 | $return[$key] = $value->$method(...$args);
634 | } else {
635 | $return[$key] = $value;
636 | }
637 | }
638 |
639 | return $return;
640 | }
641 |
642 | /**
643 | * Traverse through array or iterable object and call callback for each element (ignoring the result).
644 | *
645 | * Warning: For EACH_VALUE_KEYS_LIST and EACH_KEYS_ARRAY_VALUE modes $iterable MUST be an array.
646 | *
647 | * @param array|Iterator|IteratorAggregate $iterable Usually array, but can be an iterable object.
648 | * @param callable $callback Callback function for each element of an iterable
649 | * @param int $mode What parameters and in which order should $callback receive
650 | * @return array|Iterator|IteratorAggregate Return unchanged input for chaining
651 | */
652 | public static function each($iterable, callable $callback, int $mode = self::EACH_VALUE)
653 | {
654 | switch ($mode) {
655 | case self::EACH_KEY_VALUE:
656 | foreach ($iterable as $key => $value) {
657 | $callback($key, $value);
658 | }
659 | break;
660 | case self::EACH_VALUE_KEY:
661 | foreach ($iterable as $key => $value) {
662 | $callback($value, $key);
663 | }
664 | break;
665 | case self::EACH_VALUE_KEYS_LIST:
666 | foreach (self::unpack($iterable) as $dotKeys => $value) {
667 | $keys = self::getKeysArray($dotKeys);
668 | $callback($value, ...$keys);
669 | }
670 | break;
671 | case self::EACH_KEYS_ARRAY_VALUE:
672 | foreach (self::unpack($iterable) as $dotKeys => $value) {
673 | $keys = self::getKeysArray($dotKeys);
674 | $callback($keys, $value);
675 | }
676 | break;
677 | case self::EACH_VALUE:
678 | default:
679 | foreach ($iterable as $value) {
680 | $callback($value);
681 | }
682 | break;
683 | }
684 |
685 | return $iterable;
686 | }
687 |
688 | /**
689 | * Filter array values by preserving only those which keys are present in array obtained from $keys variable
690 | *
691 | * @param array $array
692 | * @param mixed $keys Keys needed to access desired array element (for possible formats see getKeysArray method)
693 | * @param bool $exclude If values matching $keys should be excluded from returned array
694 | * @return array
695 | * @see Arr::getKeysArray()
696 | */
697 | public static function filterByKeys(array $array, $keys, bool $exclude = false): array
698 | {
699 | if (is_null($keys)) {
700 | return $array;
701 | }
702 | $keysArray = self::getKeysArray($keys);
703 | if (empty($keysArray)) {
704 | return $exclude ? $array : [];
705 | }
706 | return $exclude ? array_diff_key($array, array_flip($keysArray)) : array_intersect_key($array, array_flip($keysArray));
707 | }
708 |
709 | /**
710 | * Wrapper around PHP built-in array_filter method.
711 | *
712 | * Iterates over each value in the array passing them to the callback function. If the callback function returns true, the current value from array is returned into the result array. Array keys are preserved.
713 | *
714 | * @param array $array The array to iterate over
715 | * @param callable|null $callback [optional]
716 | * The callback function to use
717 | * If no callback is supplied, all entries of input equal to false (see converting to boolean) will be removed.
718 | * @param int $flag [optional]
719 | * Flag determining what arguments are sent to callback:
720 | *
721 | * - ARRAY_FILTER_USE_KEY - pass key as the only argument to callback instead of the value
722 | * - ARRAY_FILTER_USE_BOTH - pass both value and key as arguments to callback instead of the value
723 | * @return array
724 | * @see array_filter()
725 | */
726 | public static function filter(array $array, ?callable $callback = null, int $flag = 0): array
727 | {
728 | return is_null($callback) ? array_filter($array) : array_filter($array, $callback, $flag);
729 | }
730 |
731 | /**
732 | * Filter objects array using return value of specified method.
733 | *
734 | * This method also filter values other than objects by standard boolean comparison
735 | *
736 | * @param array $objects Array of objects
737 | * @param string $method Object method name
738 | * @param mixed ...$args Method arguments
739 | * @return array
740 | */
741 | public static function filterObjects(array $objects, string $method, ...$args): array
742 | {
743 | $return = [];
744 |
745 | foreach ($objects as $key => $value) {
746 | if (is_object($value)) {
747 | if ($value->$method(...$args)) {
748 | $return[$key] = $value;
749 | }
750 | } elseif ($value) {
751 | $return[$key] = $value;
752 | }
753 | }
754 |
755 | return $return;
756 | }
757 |
758 | /**
759 | * Group array of arrays by value of element with specified key
760 | *
761 | * Example
762 | *
763 | * Arr::group([
764 | * 'a' => [ 'key1' => 'test1', 'key2' => 1 ],
765 | * 'b' => [ 'key1' => 'test1', 'key2' => 2 ],
766 | * 2 => [ 'key1' => 'test2', 'key2' => 3 ]
767 | * ], 'key1')
768 | *
769 | * will produce
770 | *
771 | * [
772 | * 'test1' => [
773 | * 'a' => [ 'key1' => 'test1', 'key2' => 1 ],
774 | * 'b' => [ 'key1' => 'test1', 'key2' => 2 ]
775 | * ],
776 | * 'test2' => [
777 | * 2 => [ 'key1' => 'test2', 'key2' => 3 ]
778 | * ],
779 | * ]
780 | *
781 | *
782 | * If key does not exists in one of the arrays, this array will be excluded from result
783 | * @param array $array Array of arrays
784 | * @param string|int $key Key on which to group arrays
785 | * @return array
786 | */
787 | public static function group(array $array, $key): array
788 | {
789 | $return = [];
790 |
791 | // If not array of arrays return untouched
792 | if (!self::isArrayOfArrays($array)) {
793 | return $array;
794 | }
795 |
796 | foreach ($array as $k => $v) {
797 | if (array_key_exists($key, $v)) {
798 | $return[$v[$key]][$k] = $v;
799 | }
800 |
801 | }
802 |
803 | return $return;
804 | }
805 |
806 | /**
807 | * Group array of objects by value returned from specified method
808 | *
809 | * Example
810 | * Let's say we have a list of Foo objects [Foo1, Foo2, Foo3] and all of them have method bar which return string.
811 | * If method bar return duplicate strings then all keys will contain list of corresponding objects like this:
812 | *
813 | * ['string1' => [Foo1], 'string2' => [Foo2, Foo3]]
814 | *
815 | *
816 | * @param array $objects Array of objects
817 | * @param string $method Object method name
818 | * @param mixed ...$args Method arguments
819 | * @return array
820 | */
821 | public static function groupObjects(array $objects, string $method, ...$args): array
822 | {
823 | $return = [];
824 |
825 | foreach ($objects as $key => $object) {
826 | if (is_object($object)) {
827 | $return[$object->$method(...$args)][$key] = $object;
828 | }
829 | }
830 |
831 | return $return;
832 | }
833 |
834 | /**
835 | * Find array (or iterable object) element(s) that match specified condition
836 | *
837 | * @param array|Iterator|IteratorAggregate $array
838 | * @param callable $condition Callable accepting one argument (current array element value) and returning truthy or falsy value
839 | * @param string $return What type of result should be returned after finding desired element(s)
840 | * @return mixed|mixed[] Either key, value or assoc array containing keys and values for elements matching specified condition. Returns null if element was not found, or empty array if FIND_RETURN_ALL mode was used.
841 | */
842 | public static function find($array, callable $condition, string $return = self::FIND_RETURN_VALUE)
843 | {
844 | $result = [];
845 | foreach ($array as $key => $value) {
846 | if (call_user_func($condition, $value)) {
847 | switch ($return) {
848 | case self::FIND_RETURN_KEY:
849 | return $key;
850 | case self::FIND_RETURN_VALUE:
851 | return $value;
852 | case self::FIND_RETURN_ALL:
853 | $result[$key] = $value;
854 | break;
855 | }
856 | }
857 | }
858 |
859 | return $return === self::FIND_RETURN_ALL ? $result : null;
860 | }
861 |
862 | /**
863 | * Order associative array according to supplied keys order
864 | * Keys that are not present in $keys param will be appended to the end of an array preserving supplied order.
865 | * @param array $array
866 | * @param mixed $keys Keys needed to access desired array element (for possible formats see getKeysArray method)
867 | * @param bool $appendUnmatched If values not matched by supplied keys should be appended to the end of an array
868 | * @return array
869 | * @see Arr::getKeysArray()
870 | */
871 | public static function orderByKeys(array $array, $keys, bool $appendUnmatched = true): array
872 | {
873 | $return = [];
874 |
875 | foreach (self::getKeysArray($keys) as $key) {
876 | if (array_key_exists($key, $array)) {
877 | $return[$key] = $array[$key];
878 | }
879 | }
880 |
881 | return $appendUnmatched ? $return + self::filterByKeys($array, $keys, true) : $return;
882 | }
883 |
884 | /**
885 | * Sort array of arrays using value specified by key(s)
886 | *
887 | * @param array $array Array of arrays
888 | * @param mixed $keys Keys in format specified by getKeysArray method or null to perform sort using 0-depth keys
889 | * @param bool $assoc If sorting should preserve main array keys (default: true)
890 | * @return array New sorted array
891 | * @see Arr::getKeysArray()
892 | */
893 | public static function sortByKeys(array $array, $keys = null, bool $assoc = true): array
894 | {
895 | $return = $array;
896 | $method = $assoc ? 'uasort' : 'usort';
897 |
898 | $method($return, function ($a, $b) use ($keys) {
899 | return self::getNestedElement($a, $keys) <=> self::getNestedElement($b, $keys);
900 | });
901 |
902 | return $return;
903 | }
904 |
905 | /**
906 | * Sort array of objects using result of calling supplied method name on object as value to compare
907 | *
908 | * @param array $objects Array of objects
909 | * @param string $method Name of a method called for every array element (object) in order to obtain value to compare
910 | * @param mixed ...$args Arguments for method
911 | * @return array New sorted array
912 | */
913 | public static function sortObjects(array $objects, string $method, ...$args): array
914 | {
915 | $result = $objects;
916 |
917 | uasort($result, function ($a, $b) use ($method, $args) {
918 | return $a->$method(...$args) <=> $b->$method(...$args);
919 | });
920 |
921 | return $result;
922 | }
923 |
924 | /**
925 | * Sum associative arrays by their keys into one array
926 | *
927 | * @param array $first Require first argument to ensure method is not called without arguments
928 | * @param array[] ...$arrays
929 | * @return array
930 | */
931 | public static function sum(array $first, array ...$arrays): array
932 | {
933 | $return = [];
934 |
935 | array_unshift($arrays, $first);
936 |
937 | foreach ($arrays as $array) {
938 | foreach ($array as $key => $value) {
939 | $return[$key] = ($return[$key] ?? 0) + floatval($value);
940 | }
941 | }
942 |
943 | return $return;
944 | }
945 |
946 | /**
947 | * Compute difference between two or more arrays of objects
948 | *
949 | * @param array $array1
950 | * @param array $array2
951 | * @param array[] $arrays
952 | * @return array
953 | */
954 | public static function diffObjects(array $array1, array $array2, array ...$arrays): array
955 | {
956 | $arguments = $arrays;
957 | array_unshift($arguments, $array1, $array2);
958 | array_push($arguments, function ($obj1, $obj2) {
959 | return strcmp(spl_object_hash($obj1), spl_object_hash($obj2));
960 | });
961 |
962 | return array_udiff(...$arguments);
963 | }
964 |
965 | /**
966 | * Compute intersection between two or more arrays of objects
967 | *
968 | * @param array $array1
969 | * @param array $array2
970 | * @param array[] $arrays
971 | * @return array
972 | */
973 | public static function intersectObjects(array $array1, array $array2, array ...$arrays): array
974 | {
975 | $arguments = $arrays;
976 | array_unshift($arguments, $array1, $array2);
977 | array_push($arguments, function ($obj1, $obj2) {
978 | return strcmp(spl_object_hash($obj1), spl_object_hash($obj2));
979 | });
980 |
981 | return array_uintersect(...$arguments);
982 | }
983 |
984 | /**
985 | * Flatten array of arrays to a n-depth array
986 | *
987 | * @param array $array
988 | * @param int|null $depth How many levels of nesting will be flatten. By default every nested array will be flatten.
989 | * @param bool $assoc If this param is set to true, this method will try to preserve as much string keys as possible.
990 | * In case of conflicting key name, value will be added with automatic numeric key.
991 | *
992 | * Warning: This method may produce unexpected results when array has numeric keys and $assoc param is set to true
993 | * @return array
994 | */
995 | public static function flatten(array $array, ?int $depth = null, bool $assoc = false): array
996 | {
997 | $return = [];
998 |
999 | $addElement = function ($key, $value) use (&$return, $assoc) {
1000 | if (!$assoc || array_key_exists($key, $return)) {
1001 | $return[] = $value;
1002 | } else {
1003 | $return[$key] = $value;
1004 | }
1005 | };
1006 |
1007 | foreach ($array as $key => $value) {
1008 | if (is_array($value) && (is_null($depth) || $depth >= 1)) {
1009 | foreach (self::flatten($value, is_null($depth) ? $depth : $depth - 1, $assoc) as $k => $v) {
1010 | $addElement($k, $v);
1011 | }
1012 | } else {
1013 | $addElement($key, $value);
1014 | }
1015 | }
1016 |
1017 | return $return;
1018 | }
1019 |
1020 | /**
1021 | * Flatten single element arrays (also nested single element arrays)
1022 | * Let's say we have an array like this:
1023 | *
1024 | * ['foo' => ['bar'], 'foo2' => ['bar2', 'bar3' => ['foo4']]
1025 | *
1026 | * then we have result:
1027 | *
1028 | * ['foo' => 'bar', 'foo2' => ['bar2', 'bar3' => 'foo4']]
1029 | *
1030 | *
1031 | * @param array $array
1032 | * @return array
1033 | */
1034 | public static function flattenSingle(array $array): array
1035 | {
1036 | $return = [];
1037 |
1038 | foreach ($array as $key => $value) {
1039 | if (is_array($value)) {
1040 | if (count($value) === 1) {
1041 | $return[$key] = reset($value);
1042 | } else {
1043 | $return[$key] = self::flattenSingle($value);
1044 | }
1045 | } else {
1046 | $return[$key] = $value;
1047 | }
1048 | }
1049 |
1050 | return $return;
1051 | }
1052 |
1053 | /*--------------------------------------------------------------------------------------*\
1054 | | Utility |
1055 | | ****************************************************************************** |
1056 | | Other useful methods |
1057 | \*--------------------------------------------------------------------------------------*/
1058 |
1059 | /**
1060 | * Create multidimensional array using either first param as config of keys and values
1061 | * or separate keys and values arrays
1062 | *
1063 | * @param array $keys If values are not specified, array will be created from this param keys (optionally dot formatted) and values. Otherwise it is used as array of keys (both dot and array notation possible)
1064 | * @param array|null $values [optional] Values for new array
1065 | * @return array
1066 | */
1067 | public static function createMulti(array $keys, ?array $values = null): array
1068 | {
1069 | if (is_null($values)) {
1070 | $values = array_values($keys);
1071 | $keys = array_keys($keys);
1072 | }
1073 |
1074 | if (count($keys) !== count($values)) {
1075 | throw new InvalidArgumentException('Keys and values arrays must have same amount of elements');
1076 | }
1077 |
1078 | // Reset array indexes
1079 | $keys = array_values($keys);
1080 | $values = array_values($values);
1081 |
1082 | $array = [];
1083 |
1084 | foreach ($keys as $index => $key) {
1085 | $array = self::setNestedElement($array, $key, $values[$index]);
1086 | }
1087 |
1088 | return $array;
1089 | }
1090 |
1091 | /**
1092 | * Make variable an array (according to flag settings)
1093 | *
1094 | * @param mixed $var
1095 | * @param int $flag Set flag(s) to preserve specific values from being converted to array (see Arr::FORCE_ARRAY_ constants)
1096 | * @return array|ArrayAccess
1097 | */
1098 | public static function forceArray($var, int $flag = self::FORCE_ARRAY_ALL)
1099 | {
1100 | if (!is_array($var)) {
1101 | if ($flag & self::FORCE_ARRAY_ALL) {
1102 | return [$var];
1103 | }
1104 | if (is_object($var)) {
1105 | if ($flag & self::FORCE_ARRAY_PRESERVE_OBJECTS) {
1106 | return $var;
1107 | }
1108 | if (($flag & self::FORCE_ARRAY_PRESERVE_ARRAY_OBJECTS) && $var instanceof ArrayAccess) {
1109 | return $var;
1110 | }
1111 | }
1112 | if (is_null($var) && ($flag & self::FORCE_ARRAY_PRESERVE_NULL)) {
1113 | return $var;
1114 | }
1115 |
1116 | return [$var];
1117 | }
1118 | return $var;
1119 | }
1120 |
1121 |
1122 | /**
1123 | * Get nesting depth of an array.
1124 | *
1125 | * Depth is calculated by counting amount of nested arrays - each nested array increase depth by one.
1126 | * Nominal depth of an array is 1.
1127 | *
1128 | * @param array $array
1129 | * @return int
1130 | */
1131 | public static function getDepth(array $array): int
1132 | {
1133 | $depth = 0;
1134 | $queue = [$array];
1135 |
1136 | do {
1137 | ++$depth;
1138 | $current = $queue;
1139 | $queue = [];
1140 | foreach ($current as $element) {
1141 | foreach ($element as $value) {
1142 | if (is_array($value)) {
1143 | $queue[] = $value;
1144 | }
1145 | }
1146 | }
1147 | } while (!empty($queue));
1148 |
1149 | return $depth;
1150 | }
1151 |
1152 | /**
1153 | * Copy array and clone every object inside it
1154 | *
1155 | * @param array $array
1156 | * @return array
1157 | */
1158 | public static function clone(array $array): array
1159 | {
1160 | $cloned = [];
1161 | foreach ($array as $key => $value) {
1162 | if (is_array($value)) {
1163 | $cloned[$key] = self::clone($value);
1164 | } elseif (is_object($value)) {
1165 | $cloned[$key] = clone $value;
1166 | } else {
1167 | $cloned[$key] = $value;
1168 | }
1169 | }
1170 | return $cloned;
1171 | }
1172 |
1173 | /**
1174 | * Get random array value(s)
1175 | *
1176 | * @param array $array
1177 | * @param int $count If equal to 1 than directly returns value or array of values otherwise
1178 | * @return mixed
1179 | * @throws InvalidArgumentException
1180 | */
1181 | public static function random(array $array, int $count = 1)
1182 | {
1183 | if (empty($array)) {
1184 | return null;
1185 | }
1186 |
1187 | if ($count > ($arrayCount = count($array)) || $count < 1) {
1188 | throw new InvalidArgumentException("Count must be a number between 1 and $arrayCount");
1189 | }
1190 |
1191 | return $count == 1 ? $array[array_rand($array)] : array_intersect_key($array, array_flip(array_rand($array, $count) ?? []));
1192 | }
1193 |
1194 | /**
1195 | * Shuffle array preserving keys and returning new shuffled array
1196 | *
1197 | * @param array $array
1198 | * @return array
1199 | */
1200 | public static function shuffle(array $array): array
1201 | {
1202 | $return = [];
1203 | $keys = array_keys($array);
1204 |
1205 | shuffle($keys);
1206 |
1207 | foreach ($keys as $key) {
1208 | $return[$key] = $array[$key];
1209 | }
1210 |
1211 | return $return;
1212 | }
1213 |
1214 | /**
1215 | * Gets array elements with index matching condition $An + $B (preserving original keys)
1216 | *
1217 | * @param array $array
1218 | * @param int $A
1219 | * @param int $B
1220 | * @return array
1221 | * @see Arr::even()
1222 | * @see Arr::odd()
1223 | */
1224 | public static function nth(array $array, int $A = 1, int $B = 0): array
1225 | {
1226 | $keys = [];
1227 |
1228 | for ($i = $B; $i < count($array); $i += $A) {
1229 | $keys[] = $i;
1230 | }
1231 | return self::filterByKeys($array, self::filterByKeys(array_keys($array), $keys));
1232 | }
1233 |
1234 | /**
1235 | * Get even array values - alias for nth method with $A = 2
1236 | *
1237 | * @param array $array
1238 | * @return array
1239 | */
1240 | public static function even(array $array): array
1241 | {
1242 | return self::nth($array, 2);
1243 | }
1244 |
1245 | /**
1246 | * Get odd array values - alias for nth method with $A = 2 and $B = 1
1247 | *
1248 | * @param array $array
1249 | * @return array
1250 | */
1251 | public static function odd(array $array): array
1252 | {
1253 | return self::nth($array, 2, 1);
1254 | }
1255 |
1256 | /**
1257 | * Get the first key of the given array without affecting the internal array pointer.
1258 | *
1259 | * @param array $array
1260 | * @return string|int|null Null if array is empty
1261 | */
1262 | public static function getFirstKey(array $array)
1263 | {
1264 | return empty($array) ? null : array_keys($array)[0];
1265 | }
1266 |
1267 | /**
1268 | * Get the last key of the given array without affecting the internal array pointer.
1269 | *
1270 | * @param array $array
1271 | * @return string|int|null Null if array is empty
1272 | */
1273 | public static function getLastKey(array $array)
1274 | {
1275 | if (empty($array)) {
1276 | return null;
1277 | } else {
1278 | $keys = array_keys($array);
1279 | return $keys[count($keys) - 1];
1280 | }
1281 | }
1282 |
1283 | /**
1284 | * Get the first value of the given array without affecting the internal array pointer.
1285 | *
1286 | * @param array $array
1287 | * @return mixed|null Null if array is empty
1288 | */
1289 | public static function getFirstValue(array $array)
1290 | {
1291 | if (empty($array)) {
1292 | return null;
1293 | }
1294 |
1295 | return array_values($array)[0];
1296 | }
1297 |
1298 | /**
1299 | * Get the last value of the given array without affecting the internal array pointer.
1300 | *
1301 | * @param array $array
1302 | * @return mixed|null Null if array is empty
1303 | */
1304 | public static function getLastValue(array $array)
1305 | {
1306 | if (empty($array)) {
1307 | return null;
1308 | }
1309 |
1310 | $values = array_values($array);
1311 |
1312 | return $values[count($values) - 1];
1313 | }
1314 |
1315 | /**
1316 | * Convenience method for creating new ArrObj instance
1317 | *
1318 | * @param array|ArrayAccess $array
1319 | * @return ArrObj
1320 | */
1321 | public static function obj($array = []): ArrObj
1322 | {
1323 | return new ArrObj($array);
1324 | }
1325 | }
1326 |
--------------------------------------------------------------------------------
/src/ArrObj.php:
--------------------------------------------------------------------------------
1 | setArray($array);
242 | }
243 |
244 | public function __call($name, $arguments)
245 | {
246 | if (!in_array($name, self::METHODS)) {
247 | throw new BadMethodCallException("Method {$name} does not exists in Arr class or cannot be called on supplied array");
248 | }
249 |
250 | $result = Arr::$name($this->array, ...$arguments);
251 |
252 | if (in_array($name, self::CHAINABLE_METHODS)) {
253 | return $this->setArray($result);
254 | }
255 |
256 | return $result;
257 | }
258 |
259 | /**
260 | * @return array|ArrayAccess
261 | */
262 | public function getArray()
263 | {
264 | return $this->array;
265 | }
266 |
267 | /**
268 | * @param array|ArrayAccess $array
269 | * @return ArrObj
270 | */
271 | public function setArray($array): self
272 | {
273 | $this->array = $array;
274 | return $this;
275 | }
276 |
277 | public function offsetExists($offset)
278 | {
279 | return array_key_exists($offset, $this->array);
280 | }
281 |
282 | public function offsetGet($offset)
283 | {
284 | return $this->array[$offset] ?? null;
285 | }
286 |
287 | public function offsetSet($offset, $value)
288 | {
289 | return $this->array[$offset] = $value;
290 | }
291 |
292 | public function offsetUnset($offset)
293 | {
294 | if (isset($this->array[$offset])) {
295 | unset($this->array[$offset]);
296 | }
297 | }
298 |
299 | public function getIterator()
300 | {
301 | return new ArrayIterator($this->array);
302 | }
303 |
304 | public function count()
305 | {
306 | return count($this->array);
307 | }
308 | }
--------------------------------------------------------------------------------
/tests/Arr/ArrTest.php:
--------------------------------------------------------------------------------
1 | assertSame([], Arr::getKeysArray(null));
18 | $this->assertSame([3], Arr::getKeysArray(3));
19 | $this->assertSame(['key'], Arr::getKeysArray('key'));
20 | $this->assertSame([3], Arr::getKeysArray(3));
21 | $this->assertSame([1, 'test'], Arr::getKeysArray([1, 'test']));
22 | $this->assertSame([], Arr::getKeysArray([]));
23 | $this->assertSame([], Arr::getKeysArray(''));
24 | $this->assertSame([], Arr::getKeysArray(['', null]));
25 | $this->assertSame([], Arr::getKeysArray(null));
26 | }
27 |
28 | /**
29 | * @param $class string|ArrObj
30 | *
31 | * @dataProvider arrayClassProvider
32 | */
33 | public function testHas($class)
34 | {
35 | $array = [
36 | 'test' => [
37 | 'test1',
38 | 'test2' => [
39 | 'test3' => 'abc',
40 | 'test4'
41 | ],
42 | [
43 | 'test6' => 'def'
44 | ],
45 | 'test7' => [],
46 | 'test8' => null,
47 | ],
48 | ];
49 |
50 | $this->assertTrue($this->callMethod([$class, 'has'], $array, 'test'));
51 | $this->assertTrue($this->callMethod([$class, 'has'], $array, 'test.test2'));
52 | $this->assertTrue($this->callMethod([$class, 'has'], $array, 'test.test2.test3'));
53 | $this->assertTrue($this->callMethod([$class, 'has'], $array, 'test.0'));
54 | $this->assertTrue($this->callMethod([$class, 'has'], $array, 'test.1.test6'));
55 | $this->assertTrue($this->callMethod([$class, 'has'], $array, ['test', 1, 'test6']));
56 |
57 | $this->assertFalse($this->callMethod([$class, 'has'], $array, []));
58 | $this->assertFalse($this->callMethod([$class, 'has'], $array, new stdClass()));
59 | $this->assertFalse($this->callMethod([$class, 'has'], $array, 0));
60 | $this->assertFalse($this->callMethod([$class, 'has'], $array, 'test2'));
61 | $this->assertFalse($this->callMethod([$class, 'has'], $array, 'test.test1'));
62 | $this->assertFalse($this->callMethod([$class, 'has'], $array, 'test.test2.test4'));
63 |
64 | $this->assertFalse($this->callMethod([$class, 'has'], $array, 'test.test2.test3.foo'));
65 | $this->assertFalse($this->callMethod([$class, 'has'], $array, 'test.test7.foo'));
66 | $this->assertFalse($this->callMethod([$class, 'has'], $array, 'test.test8.foo'));
67 | $this->assertFalse($this->callMethod([$class, 'has'], $array, '1.2.3'));
68 | }
69 |
70 | /**
71 | * @param $class string|ArrObj
72 | *
73 | * @dataProvider arrayClassProvider
74 | */
75 | public function testHasKeys($class)
76 | {
77 | $array = ['key1' => 1, 'key2' => 2, 'key3' => 3];
78 |
79 | $this->assertSame(true, $this->callMethod([$class, 'hasKeys'], $array, 'key1'));
80 | $this->assertSame(true, $this->callMethod([$class, 'hasKeys'], $array, ['key2', 'key3']));
81 |
82 | $this->assertSame(false, $this->callMethod([$class, 'hasKeys'], $array, 'test'));
83 | $this->assertSame(false, $this->callMethod([$class, 'hasKeys'], $array, ''));
84 |
85 | $this->assertSame(true, $this->callMethod([$class, 'hasKeys'], $array, 'key1.key2'));
86 | $this->assertSame(true, $this->callMethod([$class, 'hasKeys'], $array, ['test', 'key1']));
87 |
88 | $this->assertSame(false, $this->callMethod([$class, 'hasKeys'], $array, ['test', 'key1'], true));
89 | $this->assertSame(true, $this->callMethod([$class, 'hasKeys'], $array, ['key2', 'key1'], true));
90 | }
91 |
92 | /**
93 | * @param $class string|ArrObj
94 | *
95 | * @dataProvider arrayClassProvider
96 | */
97 | public function testGetNestedElement($class)
98 | {
99 | $array = ['key1' => ['key2' => ['key3' => ['test']]]];
100 |
101 | $this->assertSame(['test'], $this->callMethod([$class, 'get'], $array, 'key1.key2.key3'));
102 | $this->assertSame('test', $this->callMethod([$class, 'get'], $array, 'key1.key2.key3.0'));
103 | $this->assertSame('default', $this->callMethod([$class, 'get'], $array, 'key1.key4.key2.key3', 'default'));
104 | $this->assertSame('default', $this->callMethod([$class, 'get'], new stdClass(), 'key1.key4.key2.key3', 'default'));
105 | $this->assertNull($this->callMethod([$class, 'get'], $array, 'key1.key4.key2.key3'));
106 |
107 | $object = new ArrayObject();
108 | $object['key'] = 'test';
109 | $object['nested'] = ['key' => 'test2'];
110 |
111 | $this->assertSame('test', $this->callMethod([$class, 'get'], $object, 'key'));
112 | $this->assertNull($this->callMethod([$class, 'get'], $object, 'key2'));
113 | $this->assertSame('test2', $this->callMethod([$class, 'get'], $object, 'nested.key'));
114 |
115 | // Test alias
116 | $this->assertSame(Arr::getNestedElement($array, 'key1.key2.key3'), Arr::get($array, 'key1.key2.key3'));
117 | $this->assertSame(Arr::getNestedElement($array, 'key1.key2.key3', 'default'), Arr::get($array, 'key1.key2.key3', 'default'));
118 | /** @noinspection PhpParamsInspection */
119 | $this->assertSame(Arr::getNestedElement(new stdClass(), 'key1.key4.key2.key3', 'default'), Arr::get(new stdClass(), 'key1.key4.key2.key3', 'default'));
120 | $this->assertSame(Arr::getNestedElement($object, 'nested.key'), Arr::get($object, 'nested.key'));
121 | }
122 |
123 | /**
124 | * @param $class string|ArrObj
125 | *
126 | * @dataProvider arrayClassProvider
127 | */
128 | public function testSetNestedElement($class)
129 | {
130 | $array = ['key1' => ['key2' => ['key3' => ['test']]]];
131 | // Array creation
132 | $this->assertSame($array, $this->callMethod([$class, 'set'], [], 'key1.key2.key3', ['test']));
133 |
134 | $array = $this->callMethod([$class, 'set'], $array, 'key1.key2.key3', 'test');
135 | $this->assertSame('test', $array['key1']['key2']['key3']);
136 |
137 | $array = $this->callMethod([$class, 'set'], $array, 'key1.key2', ['key3' => 'test']);
138 | $this->assertSame('test', $array['key1']['key2']['key3']);
139 |
140 | $array = $this->callMethod([$class, 'set'], $array, 'key1.key2.key4', 'test2');
141 | $this->assertSame('test2', $array['key1']['key2']['key4']);
142 |
143 | // Test auto index
144 | $array = $this->callMethod([$class, 'set'], [], 'test.[]', 'test1');
145 | $array = $this->callMethod([$class, 'set'], $array, 'test.test2.test3', 'abc');
146 | $array = $this->callMethod([$class, 'set'], $array, 'test.test2.[]', 'test4');
147 | $array = $this->callMethod([$class, 'set'], $array, 'test.[].test6', 'def');
148 | $this->assertSame([
149 | 'test' => [
150 | 'test1',
151 | 'test2' => [
152 | 'test3' => 'abc',
153 | 'test4'
154 | ],
155 | [
156 | 'test6' => 'def'
157 | ],
158 | ],
159 | ], $array);
160 |
161 | $this->assertSame([[['test']]], $this->callMethod([$class, 'set'], [], '[].[].[]', 'test'));
162 | $this->assertSame([[[[]]]], $this->callMethod([$class, 'set'], [], '[].[].[]', []));
163 | $this->assertSame([[[[]]]], $this->callMethod([$class, 'set'], [], ['[]', '[]', '[]'], []));
164 | $this->assertSame([], $this->callMethod([$class, 'set'], [], [], 'test'));
165 | $this->assertSame([], $this->callMethod([$class, 'set'], [], [null], 'test'));
166 |
167 | // Test for objects
168 | $obj1 = new stdClass();
169 | $obj2 = new stdClass();
170 | $array = [
171 | [
172 | $obj1,
173 | ],
174 | 'test' => [
175 | 'abc' => [
176 | 'foo' => $obj1,
177 | ]
178 | ],
179 | $obj2,
180 | ];
181 |
182 | $this->assertSame($array, $this->callMethod([$class, 'set'], $array, '', 'whatever'));
183 | $this->assertSame([
184 | [
185 | $obj1,
186 | ],
187 | 'test' => [
188 | 'abc' => [
189 | 'foo' => $obj1,
190 | ]
191 | ],
192 | $obj2,
193 | 'test2' => $obj2,
194 | ], $this->callMethod([$class, 'set'], $array, 'test2', $obj2));
195 |
196 | // Test alias
197 | $this->assertSame(Arr::setNestedElement([], '[].[].[]', 'test'), Arr::set([], '[].[].[]', 'test'));
198 | $this->assertSame(Arr::setNestedElement($array, 'test.test2.test3', 'abc'), Arr::set($array, 'test.test2.test3', 'abc'));
199 | $this->assertSame(Arr::setNestedElement($array, 'key1.key2', ['key3' => 'test']), Arr::set($array, 'key1.key2', ['key3' => 'test']));
200 |
201 | // Test ArrayAccess
202 |
203 | $obj3 = new ArrayObject([
204 | 'c' => [
205 | 'd' => 1,
206 | ],
207 | ]);
208 | $array = [
209 | 'test' => [
210 | 'a' => [
211 | 'b' => $obj3,
212 | 'test2'
213 | ],
214 | ],
215 | ];
216 |
217 | $array = $this->callMethod([$class, 'set'], $array, 'test.a.b.c.d', 2);
218 | $this->assertSame(2, $array['test']['a']['b']['c']['d']);
219 |
220 | $array = $this->callMethod([$class, 'set'], $array, 'test.a.b.foo', 'bar');
221 | $this->assertSame('bar', $array['test']['a']['b']['foo']);
222 |
223 | $array = $this->callMethod([$class, 'set'], $array, 'test.a.b.x.[]', 'xyz');
224 | $this->assertSame('xyz', $array['test']['a']['b']['x'][0]);
225 |
226 | // Test pure array object set
227 | $obj3 = $this->callMethod([$class, 'set'], $obj3, 'c.[].test', true);
228 | $this->assertSame(true, $obj3['c'][0]['test']);
229 | }
230 |
231 | /**
232 | * @param $class string|ArrObj
233 | *
234 | * @dataProvider arrayClassProvider
235 | */
236 | public function testRemove($class)
237 | {
238 | $array = [
239 | 'test' => [
240 | 'test1',
241 | 'test2' => [
242 | 'test3' => 'abc',
243 | 'test4'
244 | ],
245 | [
246 | 'test6' => 'def'
247 | ],
248 | ],
249 | ];
250 |
251 | $this->assertSame([], $this->callMethod([$class, 'remove'], $array, 'test'));
252 | $this->assertSame([
253 | 'test' => [
254 | 'test1',
255 | [
256 | 'test6' => 'def'
257 | ],
258 | ],
259 | ], $this->callMethod([$class, 'remove'], $array, 'test.test2'));
260 |
261 | $this->assertSame([
262 | 'test' => [
263 | 'test2' => [
264 | 'test3' => 'abc',
265 | 'test4'
266 | ],
267 | 1 => [
268 | 'test6' => 'def'
269 | ],
270 | ]
271 | ], $this->callMethod([$class, 'remove'], $array, 'test.0'));
272 |
273 | $this->assertSame([
274 | 'test' => [
275 | 'test1',
276 | 'test2' => [
277 | 'test4'
278 | ],
279 | [
280 | 'test6' => 'def'
281 | ],
282 | ],
283 | ], $this->callMethod([$class, 'remove'], $array, 'test.test2.test3'));
284 |
285 | $this->assertSame([
286 | 'test' => [
287 | 'test1',
288 | 'test2' => [
289 | 'test3' => 'abc',
290 | 'test4'
291 | ],
292 | [],
293 | ],
294 | ], $this->callMethod([$class, 'remove'], $array, 'test.1.test6'));
295 |
296 | $this->assertSame($array, $this->callMethod([$class, 'remove'], $array, '0'));
297 | $this->assertSame($array, $this->callMethod([$class, 'remove'], $array, 'test.test1'));
298 | $this->assertSame($array, $this->callMethod([$class, 'remove'], $array, 'test.test2.test4'));
299 | $this->assertSame($array, $this->callMethod([$class, 'remove'], $array, 'test.test2.test4.test5.test6.abc'));
300 | $this->assertSame($array, $this->callMethod([$class, 'remove'], $array, 'test.2'));
301 | }
302 |
303 | public function testPack()
304 | {
305 | $result = [
306 | 'key1' => [
307 | 'key2' => [
308 | 'key3' => ['test', 'test2'],
309 | 'key4' => 'test3'
310 | ],
311 | 1 => ['a' => 'b', 'c'],
312 | ],
313 | 2 => [3 => 4, 5 => 6],
314 | 4 => 56
315 | ];
316 | $input = [
317 | 'key1.key2.key3.0' => 'test',
318 | 'key1.key2.key3.1' => 'test2',
319 | 'key1.key2.key4' => 'test3',
320 | 'key1.1.a' => 'b',
321 | 'key1.1.0' => 'c',
322 | '2.3' => 4,
323 | '2.5' => 6,
324 | 4 => 56,
325 |
326 | ];
327 | $array2 = [1, 2, 3, 4, 5];
328 |
329 | $this->assertSame($result, Arr::pack($input));
330 | $this->assertSame($array2, Arr::pack($array2));
331 | $this->assertSame($result, Arr::pack(Arr::unpack($result)));
332 | }
333 |
334 | public function testUnpack()
335 | {
336 | $array = [
337 | 'key1' => [
338 | 'key2' => [
339 | 'key3' => ['test', 'test2'],
340 | 'key4' => 'test3'
341 | ],
342 | 1 => ['a' => 'b', 'c'],
343 | ],
344 | 2 => [3 => 4, 5 => 6],
345 | 4 => 56
346 | ];
347 | $array2 = [1, 2, 3, 4, 5];
348 |
349 |
350 | // Test default mode
351 | $this->assertSame([
352 | 'key1.key2.key3.0' => 'test',
353 | 'key1.key2.key3.1' => 'test2',
354 | 'key1.key2.key4' => 'test3',
355 | 'key1.1.a' => 'b',
356 | 'key1.1.0' => 'c',
357 | '2.3' => 4,
358 | '2.5' => 6,
359 | 4 => 56,
360 |
361 | ], Arr::unpack($array));
362 |
363 | $this->assertSame($array2, Arr::unpack($array2));
364 |
365 |
366 | // Test UNPACK_PRESERVE_LIST_ARRAY mode
367 | $this->assertSame([
368 | 'key1.key2.key3' => ['test', 'test2'],
369 | 'key1.key2.key4' => 'test3',
370 | 'key1.1.a' => 'b',
371 | 'key1.1.0' => 'c',
372 | '2.3' => 4,
373 | '2.5' => 6,
374 | 4 => 56,
375 |
376 | ], Arr::unpack($array, Arr::UNPACK_PRESERVE_LIST_ARRAY));
377 |
378 | $this->assertSame($array2, Arr::unpack($array2, Arr::UNPACK_PRESERVE_LIST_ARRAY));
379 |
380 |
381 | // Test UNPACK_PRESERVE_ASSOC_ARRAY mode
382 | $this->assertSame([
383 | 'key1.key2.key3.0' => 'test',
384 | 'key1.key2.key3.1' => 'test2',
385 | 'key1.key2.key4' => 'test3',
386 | 'key1.1' => ['a' => 'b', 0 => 'c'],
387 | 2 => [3 => 4, 5 => 6],
388 | 4 => 56,
389 |
390 | ], Arr::unpack($array, Arr::UNPACK_PRESERVE_ASSOC_ARRAY));
391 |
392 | $this->assertSame($array2, Arr::unpack($array2, Arr::UNPACK_PRESERVE_ASSOC_ARRAY));
393 |
394 |
395 | // Test UNPACK_PRESERVE_ARRAY mode
396 | $this->assertSame([
397 | 'key1.key2.key3' => ['test', 'test2'],
398 | 'key1.key2.key4' => 'test3',
399 | 'key1.1' => ['a' => 'b', 0 => 'c'],
400 | 2 => [3 => 4, 5 => 6],
401 | 4 => 56,
402 |
403 | ], Arr::unpack($array, Arr::UNPACK_PRESERVE_ARRAY));
404 |
405 | $this->assertSame($array2, Arr::unpack($array2, Arr::UNPACK_PRESERVE_ARRAY));
406 | }
407 |
408 | /********************************* Validation *********************************/
409 |
410 | /**
411 | * @param $class string|ArrObj
412 | *
413 | * @dataProvider arrayClassProvider
414 | */
415 | public function testCheck($class)
416 | {
417 | $array1 = ['test', 'test', 'test'];
418 | $array2 = [1, 1, 1];
419 | $array3 = [false, 0, true, 1, '2', 'three'];
420 |
421 | $testClass = new TestCheckMethod();
422 |
423 | $this->assertTrue($this->callMethod([$class, 'check'], $array1, function ($value, $key) {
424 | return is_int($key) && $value == 'test';
425 | }));
426 |
427 | $this->assertTrue($this->callMethod([$class, 'check'], $array1, function ($value) {
428 | return $value;
429 | }, false));
430 | $this->assertFalse($this->callMethod([$class, 'check'], $array1, function ($value) {
431 | return $value;
432 | }, true));
433 |
434 | $this->assertFalse($this->callMethod([$class, 'check'], $array1, [$testClass, 'testOneArg']));
435 | $this->assertFalse($this->callMethod([$class, 'check'], $array1, [$testClass, 'testOneArg'], true));
436 | $this->assertFalse($this->callMethod([$class, 'check'], $array1, [$testClass, 'testOneArg'], Arr::CHECK_STRICT));
437 |
438 | $this->assertTrue($this->callMethod([$class, 'check'], $array2, [$testClass, 'testOneArg']));
439 | $this->assertTrue($this->callMethod([$class, 'check'], $array2, [$testClass, 'testOneArg'], true));
440 | $this->assertTrue($this->callMethod([$class, 'check'], $array2, [$testClass, 'testOneArg'], Arr::CHECK_STRICT));
441 |
442 | $this->assertTrue($this->callMethod([$class, 'check'], $array1, [$testClass, 'testTwoArg']));
443 | $this->assertTrue($this->callMethod([$class, 'check'], $array1, [$testClass, 'testTwoArg'], true));
444 | $this->assertTrue($this->callMethod([$class, 'check'], $array1, [$testClass, 'testTwoArg'], Arr::CHECK_STRICT));
445 |
446 | $this->assertTrue($this->callMethod([$class, 'check'], $array2, [$testClass, 'testTwoArg']));
447 | $this->assertTrue($this->callMethod([$class, 'check'], $array2, [$testClass, 'testTwoArg'], true));
448 | $this->assertTrue($this->callMethod([$class, 'check'], $array2, [$testClass, 'testTwoArg'], Arr::CHECK_STRICT));
449 |
450 | $this->assertTrue($this->callMethod([$class, 'check'], $array2, [TestCheckMethod::class, 'testStaticOneArg']));
451 | $this->assertTrue($this->callMethod([$class, 'check'], $array2, [TestCheckMethod::class, 'testStaticTwoArg']));
452 |
453 |
454 |
455 | $this->assertTrue($this->callMethod([$class, 'check'], $array1, 'test', false));
456 | $this->assertTrue($this->callMethod([$class, 'check'], $array1, 'test', 0));
457 | $this->assertTrue($this->callMethod([$class, 'check'], $array1, 'test', Arr::CHECK_SOME));
458 |
459 | $this->assertTrue($this->callMethod([$class, 'check'], $array1, 'test', true));
460 | $this->assertTrue($this->callMethod([$class, 'check'], $array1, 'test', Arr::CHECK_STRICT));
461 | $this->assertTrue($this->callMethod([$class, 'check'], $array1, 'test', Arr::CHECK_STRICT | Arr::CHECK_SOME));
462 |
463 | $this->assertFalse($this->callMethod([$class, 'check'], $array1, 'test1', false));
464 | $this->assertFalse($this->callMethod([$class, 'check'], $array1, 'test1', 0));
465 | $this->assertFalse($this->callMethod([$class, 'check'], $array1, 'test1', Arr::CHECK_SOME));
466 |
467 | $this->assertFalse($this->callMethod([$class, 'check'], $array1, 'test1', true));
468 | $this->assertFalse($this->callMethod([$class, 'check'], $array1, 'test1', Arr::CHECK_STRICT));
469 | $this->assertFalse($this->callMethod([$class, 'check'], $array1, 'test1', Arr::CHECK_STRICT | Arr::CHECK_SOME));
470 |
471 |
472 |
473 | $this->assertTrue($this->callMethod([$class, 'check'], $array2, '1', false));
474 | $this->assertTrue($this->callMethod([$class, 'check'], $array2, '1', 0));
475 | $this->assertTrue($this->callMethod([$class, 'check'], $array2, '1', Arr::CHECK_SOME));
476 |
477 | $this->assertFalse($this->callMethod([$class, 'check'], $array2, '1', true));
478 | $this->assertFalse($this->callMethod([$class, 'check'], $array2, '1', Arr::CHECK_STRICT));
479 | $this->assertFalse($this->callMethod([$class, 'check'], $array2, '1', Arr::CHECK_STRICT | Arr::CHECK_SOME));
480 |
481 | $this->assertTrue($this->callMethod([$class, 'check'], $array2, 1, false));
482 | $this->assertTrue($this->callMethod([$class, 'check'], $array2, 1, 0));
483 | $this->assertTrue($this->callMethod([$class, 'check'], $array2, 1, Arr::CHECK_SOME));
484 |
485 | $this->assertTrue($this->callMethod([$class, 'check'], $array2, 1, true));
486 | $this->assertTrue($this->callMethod([$class, 'check'], $array2, 1, Arr::CHECK_STRICT));
487 | $this->assertTrue($this->callMethod([$class, 'check'], $array2, 1, Arr::CHECK_STRICT | Arr::CHECK_SOME));
488 |
489 |
490 | $this->assertFalse($this->callMethod([$class, 'check'], $array3, '1'));
491 | $this->assertTrue($this->callMethod([$class, 'check'], $array3, '1', Arr::CHECK_SOME));
492 | $this->assertFalse($this->callMethod([$class, 'check'], $array3, '1', Arr::CHECK_STRICT | Arr::CHECK_SOME));
493 |
494 | $this->assertFalse($this->callMethod([$class, 'check'], $array3, 1));
495 | $this->assertTrue($this->callMethod([$class, 'check'], $array3, 1, Arr::CHECK_SOME));
496 | $this->assertTrue($this->callMethod([$class, 'check'], $array3, 1, Arr::CHECK_STRICT | Arr::CHECK_SOME));
497 |
498 |
499 | $this->assertTrue($this->callMethod([$class, 'check'], $array2, 'is_int'));
500 | $this->assertTrue($this->callMethod([$class, 'check'], $array2, 'is_int', true));
501 | $this->assertTrue($this->callMethod([$class, 'check'], $array2, 'is_int', Arr::CHECK_STRICT));
502 | $this->assertTrue($this->callMethod([$class, 'check'], $array2, 'is_int', Arr::CHECK_STRICT | Arr::CHECK_SOME));
503 |
504 | $this->assertFalse($this->callMethod([$class, 'check'], $array2, 'is_string'));
505 | $this->assertFalse($this->callMethod([$class, 'check'], $array2, 'is_string', true));
506 | $this->assertFalse($this->callMethod([$class, 'check'], $array2, 'is_string', Arr::CHECK_STRICT));
507 | $this->assertFalse($this->callMethod([$class, 'check'], $array2, 'is_string', Arr::CHECK_STRICT | Arr::CHECK_SOME));
508 |
509 |
510 |
511 | $this->assertFalse($this->callMethod([$class, 'check'], $array1, ['test']));
512 | $this->assertFalse($this->callMethod([$class, 'check'], $array2, [1]));
513 | }
514 |
515 | /**
516 | * @param $class string|ArrObj
517 | *
518 | * @dataProvider arrayClassProvider
519 | */
520 | public function testIsEmpty($class)
521 | {
522 | $this->assertTrue($this->callMethod([$class, 'isEmpty'], null));
523 | $this->assertTrue($this->callMethod([$class, 'isEmpty'], []));
524 | $this->assertTrue($this->callMethod([$class, 'isEmpty'], [0]));
525 | $this->assertTrue($this->callMethod([$class, 'isEmpty'], [null]));
526 | $this->assertTrue($this->callMethod([$class, 'isEmpty'], ['']));
527 | $this->assertTrue($this->callMethod([$class, 'isEmpty'], [false]));
528 | $this->assertTrue($this->callMethod([$class, 'isEmpty'], [1 => '']));
529 | $this->assertTrue($this->callMethod([$class, 'isEmpty'], [2 => [null]]));
530 |
531 | $this->assertFalse($this->callMethod([$class, 'isEmpty'], ['a']));
532 | $this->assertTrue($this->callMethod([$class, 'isEmpty'], [0 => [0], [], null, [false]]));
533 | $this->assertFalse($this->callMethod([$class, 'isEmpty'], [0 => [0 => 'a'], [], null, [false]]));
534 | }
535 |
536 | /**
537 | * @param $class string|ArrObj
538 | *
539 | * @dataProvider arrayClassProvider
540 | */
541 | public function testIsAssoc($class)
542 | {
543 | $array = ['a' => 1, 'b' => 3, 1 => 'd', 'c'];
544 | $array2 = array_combine(range(1, 11), range(0, 10));
545 | $this->assertFalse($this->callMethod([$class, 'isAssoc'], []));
546 | $this->assertTrue($this->callMethod([$class, 'isAssoc'], $array));
547 | $this->assertTrue($this->callMethod([$class, 'isAssoc'], $array, true));
548 | $this->assertFalse($this->callMethod([$class, 'isAssoc'], $array2));
549 | $this->assertTrue($this->callMethod([$class, 'isAssoc'], $array2, true));
550 | $this->assertFalse($this->callMethod([$class, 'isAssoc'], range(0, 10), true));
551 | }
552 |
553 | /**
554 | * @param $class string|ArrObj
555 | *
556 | * @dataProvider arrayClassProvider
557 | */
558 | public function testIsNumeric($class)
559 | {
560 | $this->assertTrue($this->callMethod([$class, 'isNumeric'], [1, '2', '3e10', 5.0002]));
561 | $this->assertFalse($this->callMethod([$class, 'isNumeric'], [1, '2', '3e10', 5.0002, 'a']));
562 | }
563 |
564 | /**
565 | * @param $class string|ArrObj
566 | *
567 | * @dataProvider arrayClassProvider
568 | */
569 | public function testIsUnique($class)
570 | {
571 | $this->assertFalse($this->callMethod([$class, 'isUnique'], [1, '1', true]));
572 | $this->assertTrue($this->callMethod([$class, 'isUnique'], [1, '1', true], true));
573 | $this->assertFalse($this->callMethod([$class, 'isUnique'], [1, '1', true, false, null, 0, 1]));
574 | $this->assertFalse($this->callMethod([$class, 'isUnique'], [1, '1', true, false, null, 0, 1], true));
575 | $this->assertFalse($this->callMethod([$class, 'isUnique'], [1, 1, 1]));
576 | $this->assertFalse($this->callMethod([$class, 'isUnique'], [1, 1, 1], true));
577 | }
578 |
579 | /**
580 | * @param $class string|ArrObj
581 | *
582 | * @dataProvider arrayClassProvider
583 | */
584 | public function testIsNested($class)
585 | {
586 | $this->assertFalse($this->callMethod([$class, 'isNested'], []));
587 | $this->assertFalse($this->callMethod([$class, 'isNested'], [1, 2, 3, 'a', 'b']));
588 | $this->assertTrue($this->callMethod([$class, 'isNested'], [[], []]));
589 | $this->assertTrue($this->callMethod([$class, 'isNested'], [[]]));
590 | $this->assertTrue($this->callMethod([$class, 'isNested'], [1, 2 => []]));
591 | }
592 |
593 | /**
594 | * @param $class string|ArrObj
595 | *
596 | * @dataProvider arrayClassProvider
597 | */
598 | public function testIsArrayOfArrays($class)
599 | {
600 | $this->assertFalse($this->callMethod([$class, 'isArrayOfArrays'], []));
601 | $this->assertTrue($this->callMethod([$class, 'isArrayOfArrays'], [[], []]));
602 | $this->assertTrue($this->callMethod([$class, 'isArrayOfArrays'], [[]]));
603 | $this->assertFalse($this->callMethod([$class, 'isArrayOfArrays'], [1, 2 => []]));
604 | }
605 |
606 | /********************************* Manipulation *********************************/
607 |
608 | /**
609 | * @param $class string|ArrObj
610 | *
611 | * @dataProvider arrayClassProvider
612 | */
613 | public function testMap($class)
614 | {
615 | $array = ['a', 'b', 'c'];
616 | $array2 = [
617 | 'key1' => [
618 | 'key2' => [
619 | 'key3' => ['test', 'test2'],
620 | 'key4' => 'test3'
621 | ],
622 | 1
623 | ],
624 | 2,
625 | 4 => 56
626 | ];
627 | $array3 = [
628 | 1 => [
629 | 2 => [
630 | 3 => [
631 | 4 => 'test'
632 | ],
633 | 4 => 'test2',
634 | ],
635 | 5 => [
636 | 6 => 'test3',
637 | 7 => 'test4'
638 | ],
639 | ],
640 | 8 => [
641 | 'a' => [
642 | 'b' => 'test5',
643 | 'c' => [
644 | 'd' => 'test6'
645 | ],
646 | ],
647 | ],
648 | ];
649 |
650 | $funcKeyVal = function ($key, $value) {
651 | return "{$key}{$value}";
652 | };
653 | $funcValKey = function ($value, $key) {
654 | return "{$key}{$value}";
655 | };
656 | $funcKeysVal = function ($keys, $value) {
657 | return implode($keys, '-') . ':' . $value;
658 | };
659 | $funcValKeysList = function ($value, $key1, $key2, $key3) {
660 | return "$key2.$key1.$key3.$value";
661 | };
662 |
663 | // Old test with new syntax
664 | $this->assertSame(['0a', '1b', '2c'], $this->callMethod([$class, 'map'], $array, $funcKeyVal));
665 | $this->assertSame(['0a', '1b', '2c'], $this->callMethod([$class, 'map'], $array, $funcValKey, Arr::MAP_ARRAY_VALUE_KEY));
666 | $this->assertSame([], $this->callMethod([$class, 'map'], [], $funcKeyVal));
667 | $this->assertSame([], $this->callMethod([$class, 'map'], [], $funcValKey, Arr::MAP_ARRAY_VALUE_KEY));
668 | $this->assertSame(range(0, 2), $this->callMethod([$class, 'map'], $array, function ($key) {
669 | return $key;
670 | }));
671 |
672 | // Test keys array
673 | $this->assertSame([
674 | 'key1' => [
675 | 'key2' => [
676 | 'key3' => [
677 | 'key1-key2-key3-0:test',
678 | 'key1-key2-key3-1:test2'
679 | ],
680 | 'key4' => 'key1-key2-key4:test3'
681 | ],
682 | 'key1-0:1'
683 | ],
684 | '0:2',
685 | 4 => '4:56'
686 | ], $this->callMethod([$class, 'map'], $array2, $funcKeysVal, Arr::MAP_ARRAY_KEYS_ARRAY_VALUE));
687 |
688 | // Test keys list
689 | $this->assertSame([
690 | 1 => [
691 | 2 => [
692 | 3 => [
693 | 4 => '2.1.3.test'
694 | ],
695 | 4 => '2.1.4.test2',
696 | ],
697 | 5 => [
698 | 6 => '5.1.6.test3',
699 | 7 => '5.1.7.test4'
700 | ],
701 | ],
702 | 8 => [
703 | 'a' => [
704 | 'b' => 'a.8.b.test5',
705 | 'c' => [
706 | 'd' => 'a.8.c.test6'
707 | ],
708 | ],
709 | ],
710 | ], $this->callMethod([$class, 'map'], $array3, $funcValKeysList, Arr::MAP_ARRAY_VALUE_KEYS_LIST));
711 |
712 | $this->expectException(Deprecated::class);
713 | $this->expectExceptionCode(E_USER_DEPRECATED);
714 |
715 | $this->assertSame(['0a', '1b', '2c'], Arr::map($funcKeyVal, $array));
716 | $this->assertSame(['0a', '1b', '2c'], Arr::map($funcValKey, $array, Arr::MAP_ARRAY_VALUE_KEY));
717 | $this->assertSame([], Arr::map($funcKeyVal, []));
718 | $this->assertSame([], Arr::map($funcValKey, [], Arr::MAP_ARRAY_VALUE_KEY));
719 | $this->assertSame(range(0, 2), Arr::map(function ($key) {
720 | return $key;
721 | }, $array));
722 | }
723 |
724 | /**
725 | * @param $class string|ArrObj
726 | *
727 | * @dataProvider arrayClassProvider
728 | */
729 | public function testMapObjects($class)
730 | {
731 | $method1 = function ($arg1, $arg2 = 0) {
732 | return $arg1 + $arg2;
733 | };
734 | $method2 = function ($arg1) {
735 | return $arg1 ** 2;
736 | };
737 | $method3 = function ($arg1, $arg2 = null, $arg3 = null) {
738 | return "{$arg1}{$arg2}{$arg3}";
739 | };
740 | $array = ['test', $this->createObjectWithMethod($method1), $this->createObjectWithMethod($method2), $this->createObjectWithMethod($method3)];
741 |
742 | $this->assertSame(['test', 3, 4, '213'], $this->callMethod([$class, 'mapObjects'], $array, 'test', 2, 1, 3));
743 | $this->assertSame(['test', 3, 4, '21'], $this->callMethod([$class, 'mapObjects'], $array, 'test', 2, 1));
744 | $this->assertSame(['test', 2, 4, '2'], $this->callMethod([$class, 'mapObjects'], $array, 'test', 2));
745 | $this->assertSame([], $this->callMethod([$class, 'mapObjects'], [], 'test'));
746 |
747 | $object = new class() {
748 | function test($arg = 0)
749 | {
750 | return 1 + $arg;
751 | }
752 | };
753 | $array = [$object, $object, $object];
754 |
755 | $this->assertSame([1, 1, 1], $this->callMethod([$class, 'mapObjects'], $array, 'test'));
756 | $this->assertSame([3, 3, 3], $this->callMethod([$class, 'mapObjects'], $array, 'test', 2));
757 | }
758 |
759 | /**
760 | * @param $class string|ArrObj
761 | * @dataProvider arrayClassProvider
762 | */
763 | public function testEach($class)
764 | {
765 | foreach ($this->arrayProvider() as [$array]) {
766 |
767 | // Check both pure array and iterable object
768 | foreach ([$array, new ArrayObject($array)] as $iterable) {
769 | // Just value
770 | $index = 0;
771 | $values = array_values($iterable instanceof ArrayObject ? $iterable->getArrayCopy() : $iterable);
772 | $this->callMethod([$class, 'each'], $iterable, function ($value) use ($values, &$index) {
773 | $this->assertSame($values[$index++], $value);
774 | }, Arr::EACH_VALUE);
775 |
776 | // Also check default
777 | $index = 0;
778 | $this->callMethod([$class, 'each'], $iterable, function ($value) use ($values, &$index) {
779 | $this->assertSame($values[$index++], $value);
780 | });
781 |
782 | // Key, Value
783 | $this->callMethod([$class, 'each'], $iterable, function ($key, $value) use ($iterable) {
784 | $this->assertSame($iterable[$key], $value);
785 | }, Arr::EACH_KEY_VALUE);
786 |
787 | // Value, Key
788 | $this->callMethod([$class, 'each'], $iterable, function ($value, $key) use ($iterable) {
789 | $this->assertSame($iterable[$key], $value);
790 | }, Arr::EACH_VALUE_KEY);
791 | }
792 |
793 | // Value, Keys list
794 | $this->callMethod([$class, 'each'], $array, function ($value, ...$keys) use ($class, $array) {
795 | $this->assertSame($this->callMethod([$class, 'get'], $array, $keys), $value);
796 | }, Arr::EACH_VALUE_KEYS_LIST);
797 |
798 | // Keys array, Value
799 | $this->callMethod([$class, 'each'], $array, function ($keys, $value) use ($class, $array) {
800 | $this->assertSame($this->callMethod([$class, 'get'], $array, $keys), $value);
801 | }, Arr::EACH_KEYS_ARRAY_VALUE);
802 | }
803 | }
804 |
805 | /**
806 | * @param $class string|ArrObj
807 | *
808 | * @dataProvider arrayClassProvider
809 | */
810 | public function testFilterByKeys($class)
811 | {
812 | $array = ['a' => 1, 'b' => 2, 3 => 'c', 4 => 5];
813 |
814 | $this->assertSame($array, $this->callMethod([$class, 'filterByKeys'], $array, null));
815 | $this->assertSame($array, $this->callMethod([$class, 'filterByKeys'], $array, null, true));
816 | $this->assertSame(['a' => 1, 'b' => 2, 3 => 'c'], $this->callMethod([$class, 'filterByKeys'], $array, 'a.b.3'));
817 | $this->assertSame(['a' => 1, 'b' => 2, 3 => 'c'], $this->callMethod([$class, 'filterByKeys'], $array, ['a', null, 'b', 3, null]));
818 | $this->assertSame([3 => 'c', 4 => 5], $this->callMethod([$class, 'filterByKeys'], $array, [0, 4, 3]));
819 | $this->assertSame([], $this->callMethod([$class, 'filterByKeys'], $array, []));
820 | $this->assertSame([], $this->callMethod([$class, 'filterByKeys'], $array, [null, 0, '']));
821 | $this->assertSame($array, $this->callMethod([$class, 'filterByKeys'], $array, [null, 0, ''], true));
822 | $this->assertSame([4 => 5], $this->callMethod([$class, 'filterByKeys'], $array, 'a.b.3', true));
823 | $this->assertSame([4 => 5], $this->callMethod([$class, 'filterByKeys'], $array, ['a', null, 'b', 3, null], true));
824 | $this->assertSame(['a' => 1, 'b' => 2], $this->callMethod([$class, 'filterByKeys'], $array, [0, 4, 3], true));
825 | $this->assertSame($array, $this->callMethod([$class, 'filterByKeys'], $array, [], true));
826 | $this->assertSame($array, $this->callMethod([$class, 'filterByKeys'], $array, [null, 0], true));
827 | }
828 |
829 | /**
830 | * @param $class string|ArrObj
831 | *
832 | * @dataProvider arrayClassProvider
833 | */
834 | public function testFilter($class)
835 | {
836 | $callback1 = function ($value) {
837 | return boolval($value);
838 | };
839 | $callback2 = function ($value) {
840 | return !$value;
841 | };
842 | $callback3 = function ($value, $key) {
843 | return $key == $value;
844 | };
845 | $callback4 = function ($key) {
846 | return boolval($key);
847 | };
848 |
849 | foreach ($this->arrayProvider() as [$array]) {
850 | $this->assertSame(array_filter($array), $this->callMethod([$class, 'filter'], $array));
851 | $this->assertSame(array_filter($array, $callback1), $this->callMethod([$class, 'filter'], $array, $callback1));
852 | $this->assertSame(array_filter($array, $callback2), $this->callMethod([$class, 'filter'], $array, $callback2));
853 | $this->assertSame(array_filter($array, $callback3, ARRAY_FILTER_USE_BOTH), $this->callMethod([$class, 'filter'], $array, $callback3, ARRAY_FILTER_USE_BOTH));
854 | $this->assertSame(array_filter($array, $callback4, ARRAY_FILTER_USE_KEY), $this->callMethod([$class, 'filter'], $array, $callback4, ARRAY_FILTER_USE_KEY));
855 | }
856 | }
857 |
858 | /**
859 | * @param $class string|ArrObj
860 | *
861 | * @dataProvider arrayClassProvider
862 | */
863 | public function testFilterObjects($class)
864 | {
865 | $object1 = $this->createObjectWithMethod(function ($arg) {
866 | return boolval($arg);
867 | });
868 | $object2 = $this->createObjectWithMethod(function ($arg) {
869 | return !$arg;
870 | });
871 | $object3 = $this->createObjectWithMethod(function ($arg1, $arg2) {
872 | return $arg1 && $arg2;
873 | });
874 | $object4 = $this->createObjectWithMethod(function ($arg1, $arg2) {
875 | return $arg1 || $arg2;
876 | });
877 | $array1 = [
878 | 'test1',
879 | $object1,
880 | $object2,
881 | ];
882 | $array2 = [
883 | $object3,
884 | null,
885 | $object4,
886 | ];
887 |
888 | $this->assertSame(['test1', $object1], $this->callMethod([$class, 'filterObjects'], $array1, 'test', 1));
889 | $this->assertSame(['test1', $object1], $this->callMethod([$class, 'filterObjects'], $array1, 'test', 'abc'));
890 | $this->assertSame(['test1', 2 => $object2], $this->callMethod([$class, 'filterObjects'], $array1, 'test', false));
891 | $this->assertSame([], $this->callMethod([$class, 'filterObjects'], $array2, 'test', false, 0));
892 | $this->assertSame([2 => $object4], $this->callMethod([$class, 'filterObjects'], $array2, 'test', false, 1));
893 | $this->assertSame([0 => $object3, 2 => $object4], $this->callMethod([$class, 'filterObjects'], $array2, 'test', true, 1));
894 | }
895 |
896 | /**
897 | * @param $class string|ArrObj
898 | *
899 | * @dataProvider arrayClassProvider
900 | */
901 | public function testGroup($class)
902 | {
903 | $array = [
904 | 'a' => ['key1' => 'test1', 'key2' => 1, 'key3' => 'a'],
905 | 'b' => ['key1' => 'test1', 'key2' => 2],
906 | 2 => ['key1' => 'test2', 'key2' => 3, 'key3' => 'b']
907 | ];
908 |
909 | $this->assertSame([
910 | 'test1' => [
911 | 'a' => ['key1' => 'test1', 'key2' => 1, 'key3' => 'a'],
912 | 'b' => ['key1' => 'test1', 'key2' => 2]
913 | ],
914 | 'test2' => [
915 | 2 => ['key1' => 'test2', 'key2' => 3, 'key3' => 'b']
916 | ],
917 | ], $this->callMethod([$class, 'group'], $array, 'key1'));
918 | $this->assertSame([
919 | 1 => [
920 | 'a' => ['key1' => 'test1', 'key2' => 1, 'key3' => 'a'],
921 | ],
922 | 2 => [
923 | 'b' => ['key1' => 'test1', 'key2' => 2]
924 | ],
925 | 3 => [
926 | 2 => ['key1' => 'test2', 'key2' => 3, 'key3' => 'b']
927 | ],
928 | ], $this->callMethod([$class, 'group'], $array, 'key2'));
929 | $this->assertSame([
930 | 'a' => [
931 | 'a' => ['key1' => 'test1', 'key2' => 1, 'key3' => 'a']
932 | ],
933 | 'b' => [
934 | 2 => ['key1' => 'test2', 'key2' => 3, 'key3' => 'b']
935 | ],
936 | ], $this->callMethod([$class, 'group'], $array, 'key3'));
937 | $this->assertSame([], $this->callMethod([$class, 'group'], [], 'key'));
938 | $this->assertSame([], $this->callMethod([$class, 'group'], $array, 'key4'));
939 |
940 | }
941 |
942 | /**
943 | * @param $class string|ArrObj
944 | *
945 | * @dataProvider arrayClassProvider
946 | */
947 | public function testGroupObjects($class)
948 | {
949 | $object1 = $this->createObjectWithMethod(function () {
950 | return 'test';
951 | });
952 | $object2 = $this->createObjectWithMethod(function () {
953 | return 'test1';
954 | });
955 | $object3 = $this->createObjectWithMethod(function () {
956 | return 'test';
957 | });
958 |
959 | $this->assertSame([
960 | 'test' => [
961 | 1 => $object1,
962 | 3 => $object3
963 | ],
964 | 'test1' => [
965 | 'd' => $object2,
966 | ],
967 | ], $this->callMethod([$class, 'groupObjects'], [
968 | 'abc',
969 | $object1,
970 | 'def',
971 | 'd' => $object2,
972 | 3 => $object3
973 | ], 'test'));
974 | $this->assertSame([], $this->callMethod([$class, 'groupObjects'], ['a', 'b', 'c'], 'test'));
975 | $this->assertSame([
976 | 'test' => [
977 | 3 => $object3
978 | ]
979 | ], $this->callMethod([$class, 'groupObjects'], ['a', 'b', 'c', $object3], 'test'));
980 | }
981 |
982 | /**
983 | * @param array $array
984 | * @dataProvider arrayProvider
985 | */
986 | public function testFind(array $array)
987 | {
988 | $elCount = count($array);
989 | $randomElement = Arr::random($array);
990 | $randomElementKey = array_search($randomElement, $array, true);
991 | $randomElements = $elCount === 1 ? [$randomElementKey => $randomElement] : Arr::random($array, min(3, $elCount));
992 | $callbackSingle = function ($value) use ($randomElement) {
993 | return $value === $randomElement;
994 | };
995 | $callbackAll = function ($value) use ($randomElements) {
996 | return in_array($value, $randomElements, true);
997 | };
998 | $callbackNotFound = function () {
999 | return false;
1000 | };
1001 |
1002 | // Default (value)
1003 | $this->assertSame($randomElement, Arr::find($array, $callbackSingle));
1004 |
1005 | // Return value
1006 | $this->assertSame($randomElement, Arr::find($array, $callbackSingle, Arr::FIND_RETURN_VALUE));
1007 |
1008 | // Return key
1009 | $this->assertSame($randomElementKey, Arr::find($array, $callbackSingle, Arr::FIND_RETURN_KEY));
1010 |
1011 | // Return all
1012 | $this->assertSame($randomElements, Arr::find($array, $callbackAll, Arr::FIND_RETURN_ALL), "Elements " . print_r($randomElements, true));
1013 |
1014 | // Not found (default - value)
1015 | $this->assertSame(null, Arr::find($array, $callbackNotFound));
1016 |
1017 | // Not found (value)
1018 | $this->assertSame(null, Arr::find($array, $callbackNotFound, Arr::FIND_RETURN_VALUE));
1019 |
1020 | // Not found (key)
1021 | $this->assertSame(null, Arr::find($array, $callbackNotFound, Arr::FIND_RETURN_KEY));
1022 |
1023 | // Not found (all)
1024 | $this->assertSame([], Arr::find($array, $callbackNotFound, Arr::FIND_RETURN_ALL));
1025 |
1026 | }
1027 |
1028 | /**
1029 | * @param $class string|ArrObj
1030 | *
1031 | * @dataProvider arrayClassProvider
1032 | */
1033 | public function testOrderByKeys($class)
1034 | {
1035 | $array = [
1036 | 'abc',
1037 | 'd' => 'test1',
1038 | 'e' => 'test3',
1039 | 'test2',
1040 | 'x' => ['t' => 3]
1041 | ];
1042 | $ordered = [
1043 | 'd' => 'test1',
1044 | 0 => 'abc',
1045 | 'x' => ['t' => 3],
1046 | 'e' => 'test3',
1047 | 1 => 'test2',
1048 | ];
1049 |
1050 | $this->assertSame($ordered, $this->callMethod([$class, 'orderByKeys'], $array, 'd.0.x.e.1'));
1051 | $this->assertSame($ordered, $this->callMethod([$class, 'orderByKeys'], $array, ['d', 0, 'x', 'e', 1]));
1052 | $this->assertSame($ordered, $this->callMethod([$class, 'orderByKeys'], $array, 'd.0.x'));
1053 | $this->assertSame(array_slice($ordered, 0, 3, true), $this->callMethod([$class, 'orderByKeys'], $array, 'd.0.x', false));
1054 | $this->assertSame($array, $this->callMethod([$class, 'orderByKeys'], $array, null));
1055 | $this->assertSame($array, $this->callMethod([$class, 'orderByKeys'], $array, []));
1056 | $this->assertSame($array, $this->callMethod([$class, 'orderByKeys'], $array, [null, '']));
1057 | }
1058 |
1059 | /**
1060 | * @param $class string|ArrObj
1061 | *
1062 | * @dataProvider arrayClassProvider
1063 | */
1064 | public function testSortByKeys($class)
1065 | {
1066 | $array1 = [
1067 | 'a' => 3,
1068 | 1,
1069 | 'c' => 6,
1070 | -3,
1071 | 'e' => 0,
1072 | 'f' => 1,
1073 | ];
1074 |
1075 | $array2 = [
1076 | 'a' => ['b' => 3],
1077 | 'c' => ['b' => -1],
1078 | 'd' => ['b' => 0]
1079 | ];
1080 |
1081 | $array3 = [
1082 | 'a' => ['b' => ['c' => 3]],
1083 | 'c' => ['b' => ['c' => -1]],
1084 | 'd' => ['b' => ['c' => 0]]
1085 | ];
1086 |
1087 | $this->assertSame([
1088 | 1 => -3,
1089 | 'e' => 0,
1090 | 0 => 1,
1091 | 'f' => 1,
1092 | 'a' => 3,
1093 | 'c' => 6,
1094 | ], $this->callMethod([$class, 'sortByKeys'], $array1));
1095 |
1096 | $this->assertSame([-3, 0, 1, 1, 3, 6], $this->callMethod([$class, 'sortByKeys'], $array1, null, false));
1097 | $this->assertSame([-3, 0, 1, 1, 3, 6], $this->callMethod([$class, 'sortByKeys'], $array1, [null, ''], false));
1098 | $this->assertSame([], $this->callMethod([$class, 'sortByKeys'], []));
1099 |
1100 | $this->assertSame([
1101 | 'c' => ['b' => -1],
1102 | 'd' => ['b' => 0],
1103 | 'a' => ['b' => 3],
1104 | ], $this->callMethod([$class, 'sortByKeys'], $array2, 'b'));
1105 | $this->assertSame([
1106 | ['b' => -1],
1107 | ['b' => 0],
1108 | ['b' => 3],
1109 | ], $this->callMethod([$class, 'sortByKeys'], $array2, 'b', false));
1110 |
1111 | $this->assertSame([
1112 | 'c' => ['b' => ['c' => -1]],
1113 | 'd' => ['b' => ['c' => 0]],
1114 | 'a' => ['b' => ['c' => 3]],
1115 | ], $this->callMethod([$class, 'sortByKeys'], $array3, 'b.c'));
1116 | $this->assertSame([
1117 | ['b' => ['c' => -1]],
1118 | ['b' => ['c' => 0]],
1119 | ['b' => ['c' => 3]],
1120 | ], $this->callMethod([$class, 'sortByKeys'], $array3, 'b.c', false));
1121 | }
1122 |
1123 | /**
1124 | * @param $class string|ArrObj
1125 | *
1126 | * @dataProvider arrayClassProvider
1127 | */
1128 | public function testSortObjects($class)
1129 | {
1130 | $object1 = new class() {
1131 | public $i = 1;
1132 |
1133 | public function getValue(bool $reverse = false)
1134 | {
1135 | return $reverse ? 1 / $this->i : $this->i;
1136 | }
1137 | };
1138 | $object2 = clone $object1;
1139 | $object2->i = 2;
1140 |
1141 | $object3 = clone $object1;
1142 | $object3->i = 3;
1143 |
1144 | $object4 = clone $object1;
1145 | $object4->i = 4;
1146 |
1147 | $object5 = clone $object1;
1148 | $object5->i = 5;
1149 |
1150 | $proto = [$object1, $object2, $object3, $object4, $object5];
1151 | $array = $proto;
1152 |
1153 | $this->assertSame([1, 2, 3, 4, 5], $this->callMethod([$class, 'mapObjects'], $array, 'getValue'));
1154 | $this->assertSame([1, 1 / 2, 1 / 3, 1 / 4, 1 / 5], $this->callMethod([$class, 'mapObjects'], $array, 'getValue', true));
1155 |
1156 | // Ensure order is not the same
1157 | do {
1158 | $array = $this->callMethod([$class, 'shuffle'], $array);
1159 | } while ($array === $proto);
1160 |
1161 | $this->assertSame($proto, $this->callMethod([$class, 'sortObjects'], $array, 'getValue'));
1162 | $this->assertSame(array_reverse($proto, true), $this->callMethod([$class, 'sortObjects'], $array, 'getValue', true));
1163 | }
1164 |
1165 | /**
1166 | * @param $class string|ArrObj
1167 | *
1168 | * @dataProvider arrayClassProvider
1169 | */
1170 | public function testSum($class)
1171 | {
1172 | $arrays = [
1173 | [
1174 | 'a' => 1,
1175 | 'b' => -3.5,
1176 | 'c' => 0,
1177 | 3
1178 | ],
1179 | [
1180 | 2,
1181 | 'a' => 0,
1182 | 'c' => -5,
1183 | 'd' => PHP_INT_MAX,
1184 | ],
1185 | [
1186 | -5,
1187 | 'b' => 3.5,
1188 | 'a' => -1,
1189 | 'c' => 5,
1190 | ],
1191 | [
1192 | 'd' => PHP_INT_MAX,
1193 | ],
1194 | [
1195 | 'd' => 2 * -PHP_INT_MAX,
1196 | ]
1197 | ];
1198 |
1199 | $this->assertEquals([
1200 | 0,
1201 | 'a' => 0,
1202 | 'b' => 0,
1203 | 'c' => 0,
1204 | 'd' => 0,
1205 | ], $this->callMethod([$class, 'sum'], ...$arrays));
1206 | $this->assertSame([], $this->callMethod([$class, 'sum'], []));
1207 | $this->assertEquals([1, 1, 0], $this->callMethod([$class, 'sum'], [null, '', false], ['1', true, 'test']));
1208 | }
1209 |
1210 | /**
1211 | * @param $class string|ArrObj
1212 | *
1213 | * @dataProvider arrayClassProvider
1214 | */
1215 | public function testDiffObjects($class)
1216 | {
1217 | $object1 = new stdClass();
1218 | $object2 = new stdClass();
1219 | $object3 = new stdClass();
1220 |
1221 | $this->assertSame([1 => $object1], $this->callMethod([$class, 'diffObjects'], [$object3, $object1, $object2], [$object3], [$object2]));
1222 | $this->assertSame([], $this->callMethod([$class, 'diffObjects'], [$object3, $object1, $object2], [$object3], [$object1, $object2]));
1223 | $this->assertSame([$object1], $this->callMethod([$class, 'diffObjects'], [$object1], [$object3], [$object2], []));
1224 | }
1225 |
1226 | /**
1227 | * @param $class string|ArrObj
1228 | *
1229 | * @dataProvider arrayClassProvider
1230 | */
1231 | public function testIntersectObjects($class)
1232 | {
1233 | $object1 = new stdClass();
1234 | $object2 = new stdClass();
1235 | $object3 = new stdClass();
1236 |
1237 | $this->assertSame([2 => $object2], $this->callMethod([$class, 'intersectObjects'], [$object3, $object1, $object2], [$object3, $object2], [$object2]));
1238 | $this->assertSame([], $this->callMethod([$class, 'intersectObjects'], [$object3, $object1, $object2], [$object3], [$object1, $object2]));
1239 | $this->assertSame([$object1, $object2, 3 => $object1], $this->callMethod([$class, 'intersectObjects'], [$object1, $object2, $object3, $object1], [$object1, $object2]));
1240 | }
1241 |
1242 | /**
1243 | * @param $class string|ArrObj
1244 | *
1245 | * @dataProvider arrayClassProvider
1246 | */
1247 | public function testFlatten($class)
1248 | {
1249 | $array = [
1250 | 'a' => [
1251 | 'b' => [
1252 | 1 => [
1253 | 2 => 3
1254 | ],
1255 | 'c' => [
1256 | 4
1257 | ],
1258 | 5
1259 | ],
1260 | 'd' => [
1261 | 'e',
1262 | [
1263 | 'f' => 6
1264 | ]
1265 | ],
1266 | ],
1267 | 'g' => [
1268 | 'h',
1269 | 'i',
1270 | 'j' => [
1271 | 7,
1272 | 2 => 8,
1273 | ]
1274 | ],
1275 | [
1276 | 'k',
1277 | 'l' => [
1278 | 9,
1279 | 10,
1280 | ],
1281 | ]
1282 | ];
1283 |
1284 | $array2 = [
1285 | 'a' => [
1286 | 'b' => [
1287 | 'c' => 'test'
1288 | ],
1289 | 'd' => 1
1290 | ],
1291 | 'b' => [
1292 | 'e' => 2
1293 | ]
1294 | ];
1295 |
1296 |
1297 | $this->assertSame([], $this->callMethod([$class, 'flatten'], [[[[]]]]));
1298 | $this->assertSame([], $this->callMethod([$class, 'flatten'], []));
1299 |
1300 | $this->assertSame(['test', 1, 2], $this->callMethod([$class, 'flatten'], $array2));
1301 | $this->assertSame([3, 4, 5, 'e', 6, 'h', 'i', 7, 8, 'k', 9, 10], $this->callMethod([$class, 'flatten'], $array));
1302 |
1303 | // Test depth
1304 | $this->assertSame(['a'], $this->callMethod([$class, 'flatten'], [[[['a']]]]));
1305 | $this->assertSame(array_values($array), $this->callMethod([$class, 'flatten'], $array, 0));
1306 | $this->assertSame($array, $this->callMethod([$class, 'flatten'], $array, 0, true));
1307 | $this->assertSame([['c' => 'test'], 1, 2], $this->callMethod([$class, 'flatten'], $array2, 1));
1308 |
1309 | $this->assertSame([
1310 | 'c' => 'test',
1311 | 'd' => 1,
1312 | 'e' => 2
1313 | ], $this->callMethod([$class, 'flatten'], $array2, null, true));
1314 |
1315 |
1316 | $this->assertSame([
1317 | [
1318 | 1 => [
1319 | 2 => 3
1320 | ],
1321 | 'c' => [
1322 | 4
1323 | ],
1324 | 5
1325 | ],
1326 | [
1327 | 'e',
1328 | [
1329 | 'f' => 6
1330 | ]
1331 | ],
1332 | 'h',
1333 | 'i',
1334 | [
1335 | 7,
1336 | 2 => 8,
1337 | ],
1338 | 'k',
1339 | [
1340 | 9,
1341 | 10,
1342 | ],
1343 | ], $this->callMethod([$class, 'flatten'], $array, 1));
1344 |
1345 | $this->assertSame([
1346 | 'b' => [
1347 | 1 => [
1348 | 2 => 3
1349 | ],
1350 | 'c' => [
1351 | 4
1352 | ],
1353 | 5
1354 | ],
1355 | 'd' => [
1356 | 'e',
1357 | [
1358 | 'f' => 6
1359 | ]
1360 | ],
1361 | 'h',
1362 | 'i',
1363 | 'j' => [
1364 | 7,
1365 | 2 => 8,
1366 | ],
1367 | 'k',
1368 | 'l' => [
1369 | 9,
1370 | 10,
1371 | ],
1372 | ], $this->callMethod([$class, 'flatten'], $array, 1, true));
1373 |
1374 | $this->assertSame([
1375 | 3,
1376 | 4,
1377 | 5,
1378 | 'e',
1379 | 6,
1380 | 'h',
1381 | 'i',
1382 | 7,
1383 | 8,
1384 | 'k',
1385 | 9,
1386 | 10,
1387 | ], $this->callMethod([$class, 'flatten'], $array, 3));
1388 |
1389 | $this->assertSame([
1390 | 'a' => 1,
1391 | 'b' => 2,
1392 | 'c' => 3,
1393 | 4,
1394 | 'd' => 5,
1395 | 6,
1396 | ], $this->callMethod([$class, 'flatten'], [
1397 | 'a' => [
1398 | 'a' => [
1399 | 'a' => 1,
1400 | 'b' => 2,
1401 | ],
1402 | 'b' => [
1403 | 'c' => 3,
1404 | ],
1405 | ],
1406 | 'b' => [
1407 | 'a' => [
1408 | 'c' => 4
1409 | ],
1410 | [
1411 | 'd' => 5,
1412 | ]
1413 | ],
1414 | [
1415 | [
1416 | 'd' => 6,
1417 | ]
1418 | ],
1419 | ], 2, true));
1420 |
1421 | // Test conflicting string key
1422 | $this->assertSame([
1423 | 'c' => 1,
1424 | 2,
1425 | ], $this->callMethod([$class, 'flatten'], [
1426 | 'a' => [
1427 | 'b' => [
1428 | 'c' => 1,
1429 | ],
1430 | ],
1431 | [
1432 | 'c' => 2
1433 | ]
1434 | ], null, true));
1435 | }
1436 |
1437 | /**
1438 | * @param $class string|ArrObj
1439 | *
1440 | * @dataProvider arrayClassProvider
1441 | */
1442 | public function testFlattenSingle($class)
1443 | {
1444 | $this->assertSame([], $this->callMethod([$class, 'flattenSingle'], []));
1445 | $this->assertSame(['a'], $this->callMethod([$class, 'flattenSingle'], [['a']]));
1446 | $this->assertSame([
1447 | 'a' => 'test',
1448 | 'b' => [
1449 | 'test2',
1450 | 'c' => 'test3'
1451 | ],
1452 | ], $this->callMethod([$class, 'flattenSingle'], [
1453 | 'a' => ['test'],
1454 | 'b' => [
1455 | 'test2',
1456 | 'c' => ['test3']
1457 | ]
1458 | ]));
1459 | $this->assertSame([
1460 | 'a' => 1,
1461 | 'b' => 2,
1462 | ], $this->callMethod([$class, 'flattenSingle'], [
1463 | 'a' => [
1464 | 'b' => 1
1465 | ],
1466 | 'b' => 2,
1467 | ]));
1468 | }
1469 |
1470 | public function testCreatMulti()
1471 | {
1472 | $expected = [
1473 | 'test' => [
1474 | '123',
1475 | 'test2' => [
1476 | 'test3' => 'abc',
1477 | 567
1478 | ],
1479 | [
1480 | 1 => 'def'
1481 | ],
1482 | ],
1483 | ];
1484 | $this->assertSame($expected, Arr::createMulti([
1485 | 'test.[]' => '123',
1486 | 'test.test2.test3' => 'abc',
1487 | 'test.test2.[]' => 567,
1488 | 'test.[].1' => 'def',
1489 | ]));
1490 |
1491 | $this->assertSame($expected, Arr::createMulti([
1492 | ['test', '[]'],
1493 | ['test', 'test2', 'test3'],
1494 | ['test', 'test2', '[]'],
1495 | ['test', '[]', 1],
1496 | ], [
1497 | '123',
1498 | 'abc',
1499 | 567,
1500 | 'def',
1501 | ]));
1502 |
1503 | $this->assertSame([], Arr::createMulti([]));
1504 | $this->assertSame([], Arr::createMulti([], null));
1505 | $this->assertSame([], Arr::createMulti([], []));
1506 |
1507 | $this->expectException(InvalidArgumentException::class);
1508 | Arr::createMulti([1], [1, 2]);
1509 | Arr::createMulti([1, 2], [2]);
1510 | Arr::createMulti([1, 2], []);
1511 | Arr::createMulti([], [2, 3]);
1512 | }
1513 |
1514 | public function testForceArray()
1515 | {
1516 | $object1 = new ArrayObject();
1517 | $object2 = new stdClass();
1518 | $function = function () {
1519 | };
1520 |
1521 | $this->assertSame(['a' => 1], Arr::forceArray(['a' => 1]));
1522 | $this->assertSame([], Arr::forceArray([]));
1523 | $this->assertSame([null], Arr::forceArray(null));
1524 | $this->assertSame(null, Arr::forceArray(null, Arr::FORCE_ARRAY_PRESERVE_NULL));
1525 | $this->assertSame([$object1], Arr::forceArray($object1));
1526 | $this->assertSame($object1, Arr::forceArray($object1, Arr::FORCE_ARRAY_PRESERVE_ARRAY_OBJECTS));
1527 | $this->assertSame([$object2], Arr::forceArray($object2));
1528 | $this->assertSame([$object2], Arr::forceArray($object2, Arr::FORCE_ARRAY_PRESERVE_ARRAY_OBJECTS));
1529 | $this->assertSame($object2, Arr::forceArray($object2, Arr::FORCE_ARRAY_PRESERVE_OBJECTS));
1530 | $this->assertSame($object2, Arr::forceArray($object2, Arr::FORCE_ARRAY_PRESERVE_NULL | Arr::FORCE_ARRAY_PRESERVE_OBJECTS | Arr::FORCE_ARRAY_PRESERVE_ARRAY_OBJECTS));
1531 | $this->assertSame([1], Arr::forceArray(1));
1532 | $this->assertSame([1.5], Arr::forceArray(1.5));
1533 | $this->assertSame([0], Arr::forceArray(0));
1534 | $this->assertSame(['test'], Arr::forceArray('test'));
1535 | $this->assertSame(['1'], Arr::forceArray('1'));
1536 | $this->assertSame([$function], Arr::forceArray($function));
1537 | }
1538 |
1539 | /**
1540 | * @param $class string|ArrObj
1541 | *
1542 | * @dataProvider arrayClassProvider
1543 | */
1544 | public function testGetDepth($class)
1545 | {
1546 | $this->assertSame(1, $this->callMethod([$class, 'getDepth'], []));
1547 | $this->assertSame(1, $this->callMethod([$class, 'getDepth'], [1, 2, 3, 4, 'a', 'b', 'c']));
1548 | $this->assertSame(2, $this->callMethod([$class, 'getDepth'], [[]]));
1549 | $this->assertSame(5, $this->callMethod([$class, 'getDepth'], [
1550 | [],
1551 | 'a' => [
1552 | 'b' => [
1553 | 'c' => 2,
1554 | 'd' => [
1555 | [],
1556 | 'e' => 'test',
1557 | ],
1558 | [
1559 | 'f' => []
1560 | ]
1561 | ]
1562 | ],
1563 | 10,
1564 | 'foo' => [
1565 | 'bar',
1566 | []
1567 | ]
1568 | ]));
1569 | }
1570 |
1571 | /**
1572 | * @param $class string|ArrObj
1573 | *
1574 | * @dataProvider arrayClassProvider
1575 | */
1576 | public function testClone($class)
1577 | {
1578 | $object = new stdClass();
1579 | $object2 = new ArrayObject();
1580 | $object3 = new class() {
1581 | public $counter = 1;
1582 |
1583 | function __clone()
1584 | {
1585 | $this->counter = 2;
1586 | }
1587 | };
1588 |
1589 | $this->assertSame([1, 2, 'a'], $this->callMethod([$class, 'clone'], [1, 2, 'a']));
1590 | $this->assertSame([], $this->callMethod([$class, 'clone'], []));
1591 | $this->assertEquals([$object], $this->callMethod([$class, 'clone'], [$object]));
1592 | $this->assertEquals([$object2], $this->callMethod([$class, 'clone'], [$object2]));
1593 | $this->assertEquals(['a' => $object2, [[$object]]], $this->callMethod([$class, 'clone'], ['a' => $object2, [[$object]]]));
1594 | $this->assertNotEquals([$object], $this->callMethod([$class, 'clone'], [$object2]));
1595 |
1596 | $array = ['test' => $object3];
1597 | $newArray = $this->callMethod([$class, 'clone'], $array);
1598 | $this->assertSame(1, $array['test']->counter);
1599 | $this->assertSame(2, $newArray['test']->counter);
1600 | }
1601 |
1602 | /**
1603 | * @param $class string|ArrObj
1604 | *
1605 | * @dataProvider arrayClassProvider
1606 | */
1607 | public function testRandom($class)
1608 | {
1609 | $array = [1, 2, 3];
1610 | $array2 = ['test', 'abc'];
1611 |
1612 | $this->assertContains($this->callMethod([$class, 'random'], $array), $array);
1613 | $this->assertIsInt($this->callMethod([$class, 'random'], $array));
1614 | $this->assertContains($this->callMethod([$class, 'random'], $array2), $array2);
1615 | $this->assertIsString($this->callMethod([$class, 'random'], $array2));
1616 | $this->assertIsNotArray($this->callMethod([$class, 'random'], $array));
1617 | $this->assertIsNotArray($this->callMethod([$class, 'random'], $array2));
1618 |
1619 | $random = $this->callMethod([$class, 'random'], $array, 2);
1620 | $this->assertIsArray($random);
1621 | $this->assertCount(2, $random);
1622 | $this->assertSame($random, array_intersect($array, $random));
1623 | $this->assertSame(null, $this->callMethod([$class, 'random'], []));
1624 |
1625 | $this->expectException(InvalidArgumentException::class);
1626 | $this->callMethod([$class, 'random'], $array2, 4);
1627 | }
1628 |
1629 | /**
1630 | * @param $class string|ArrObj
1631 | *
1632 | * @dataProvider arrayClassProvider
1633 | */
1634 | public function testNth($class)
1635 | {
1636 | $array = range(0, 10);
1637 |
1638 | $this->assertEqualsCanonicalizing([0, 2, 4, 6, 8, 10], $this->callMethod([$class, 'even'], $array));
1639 | $this->assertEqualsCanonicalizing([1, 3, 5, 7, 9], $this->callMethod([$class, 'odd'], $array));
1640 | $this->assertEqualsCanonicalizing([2, 6, 10], $this->callMethod([$class, 'nth'], $array, 4, 2));
1641 | $this->assertSame($array, $this->callMethod([$class, 'nth'], $array));
1642 | $this->assertSame(['b' => 2, 'd' => 4], $this->callMethod([$class, 'nth'], ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4], 2, 1));
1643 | $this->assertSame([], $this->callMethod([$class, 'nth'], [], 100, 100));
1644 | }
1645 |
1646 | /**
1647 | * @param $class string|ArrObj
1648 | *
1649 | * @dataProvider arrayClassProvider
1650 | */
1651 | public function testShuffle($class)
1652 | {
1653 | $array = ['a' => 1, 'b' => 2, 'c' => 3, 'd' => 4];
1654 | $this->assertTrue($this->callMethod([$class, 'hasKeys'], $this->callMethod([$class, 'shuffle'], $array), array_keys($array), true));
1655 | $this->assertSame([], array_diff($this->callMethod([$class, 'shuffle'], $array), $array));
1656 | }
1657 |
1658 | /**
1659 | * @param array $array
1660 | * @return array
1661 | *
1662 | * @dataProvider arrayProvider
1663 | */
1664 | public function testGetFirstKey(array $array)
1665 | {
1666 | $this->assertSame(null, Arr::getFirstKey([]));
1667 | $this->assertSame(null, Arr::obj([])->getFirstKey());
1668 | reset($array);
1669 | $this->assertSame(key($array), Arr::getFirstKey($array));
1670 | $this->assertSame(key($array), Arr::obj($array)->getFirstKey());
1671 |
1672 | return $array;
1673 | }
1674 |
1675 | /**
1676 | * @param array $array
1677 | *
1678 | * @dataProvider arrayProvider
1679 | */
1680 | public function testGetLastKey(array $array)
1681 | {
1682 | $this->assertSame(null, Arr::getLastKey([]));
1683 | $this->assertSame(null, Arr::obj([])->getLastKey());
1684 | end($array);
1685 | $this->assertSame(key($array), Arr::getLastKey($array));
1686 | $this->assertSame(key($array), Arr::obj($array)->getLastKey());
1687 | }
1688 |
1689 | /**
1690 | * @param array $array
1691 | *
1692 | * @dataProvider arrayProvider
1693 | */
1694 | public function testGetFirstValue(array $array)
1695 | {
1696 | $this->assertSame(null, Arr::getFirstValue([]));
1697 | $this->assertSame(null, Arr::obj([])->getFirstValue());
1698 | $this->assertSame(reset($array), Arr::getFirstValue($array));
1699 | $this->assertSame(reset($array), Arr::obj($array)->getFirstValue());
1700 | }
1701 |
1702 | /**
1703 | * @param array $array
1704 | *
1705 | * @dataProvider arrayProvider
1706 | */
1707 | public function testGetLastValue(array $array)
1708 | {
1709 | $this->assertSame(null, Arr::getLastValue([]));
1710 | $this->assertSame(null, Arr::obj([])->getLastValue());
1711 | $this->assertSame(end($array), Arr::getLastValue($array));
1712 | $this->assertSame(end($array), Arr::obj($array)->getLastValue());
1713 | }
1714 |
1715 | public function testReturnArrObj()
1716 | {
1717 | $this->assertIsObject(Arr::obj());
1718 | $this->assertInstanceOf(ArrObj::class, Arr::obj());
1719 | $this->assertSame(ArrObj::class, get_class(Arr::obj()));
1720 | }
1721 | }
1722 |
1723 | class TestCheckMethod
1724 | {
1725 | public function testOneArg($value)
1726 | {
1727 | return is_numeric($value);
1728 | }
1729 |
1730 | public function testTwoArg($value, $key)
1731 | {
1732 | return is_int($key) || is_int($value);
1733 | }
1734 |
1735 | public static function testStaticOneArg($value)
1736 | {
1737 | return $value;
1738 | }
1739 |
1740 | public static function testStaticTwoArg($value, $key)
1741 | {
1742 | return $key < 3 && $value;
1743 | }
1744 | }
1745 |
--------------------------------------------------------------------------------
/tests/ArrObj/ArrObjTest.php:
--------------------------------------------------------------------------------
1 | obj = new ArrObj();
19 | }
20 |
21 | public function notChainableMethodsProvider(): array
22 | {
23 | return Arr::map(array_diff(ArrObj::METHODS, ArrObj::CHAINABLE_METHODS), function ($m) {
24 | return [$m];
25 | }, Arr::MAP_ARRAY_VALUE_KEY);
26 | }
27 |
28 | public function chainableMethodsProvider(): array
29 | {
30 | return Arr::map(ArrObj::CHAINABLE_METHODS, function ($m) {
31 | return [$m];
32 | }, Arr::MAP_ARRAY_VALUE_KEY);
33 | }
34 |
35 | public function testIsProperlyInitializing()
36 | {
37 | $this->assertSame([], (new ArrObj())->getArray());
38 | $this->assertSame([], (new ArrObj([]))->getArray());
39 | $this->assertSame([1, 2, 3], (new ArrObj([1, 2, 3]))->getArray());
40 | $this->assertSame([1, 2, 3], Arr::obj([1, 2, 3])->getArray());
41 |
42 | $arrayObject = new ArrayObject();
43 | $this->assertSame($arrayObject, Arr::obj($arrayObject)->getArray());
44 | }
45 |
46 | /**
47 | * @param string $method
48 | * @throws ReflectionException
49 | * @dataProvider chainableMethodsProvider
50 | */
51 | public function testReturnSelf(string $method)
52 | {
53 | $this->assertSame($this->obj, $this->obj->$method(...$this->getMockedParams($method)));
54 | }
55 |
56 | /**
57 | * @param string $method
58 | * @throws ReflectionException
59 | * @dataProvider notChainableMethodsProvider
60 | */
61 | public function testNotReturnSelf(string $method)
62 | {
63 | $this->assertNotSame($this->obj, $this->obj->$method(...$this->getMockedParams($method)));
64 | }
65 |
66 | public function testIsChainable()
67 | {
68 | $arr = new ArrObj();
69 |
70 | $this->assertSame([
71 | 'test' => 2,
72 | 'test2' => [
73 | 3
74 | ]
75 | ], $arr
76 | ->set('test', 1)
77 | ->set('test2.[]', 2)
78 | ->map(function ($val) {
79 | return $val + 1;
80 | }, Arr::MAP_ARRAY_VALUE_KEYS_LIST)
81 | ->set('test3', null)
82 | ->filterByKeys(['test3'], true)
83 | ->getArray()
84 | );
85 | }
86 |
87 | public function testThrowsExceptionOnInvalidMethod()
88 | {
89 | $this->expectException(BadMethodCallException::class);
90 | /** @noinspection PhpUndefinedMethodInspection */
91 | (new ArrObj())->invalidMethodTest();
92 | }
93 |
94 | /**
95 | * @param array $array
96 | *
97 | * @dataProvider arrayProvider
98 | */
99 | public function testArrayAccess($array)
100 | {
101 | $obj = new ArrObj($array);
102 | $key = Arr::getFirstKey($array);
103 |
104 | $this->assertSame($array[$key], $obj[$key]);
105 | $this->assertSame(isset($array[$key]), isset($obj[$key]));
106 | $this->assertSame(isset($array['non_existent_key_643543543']), isset($obj['non_existent_key_643543543']));
107 |
108 | $newKey = 'very_long_unique_key';
109 | $newValue = 'dump_value_123';
110 | $this->assertSame($array[$newKey] = $newValue, $obj[$newKey] = $newValue);
111 | $this->assertSame($array[$newKey], $obj[$newKey]);
112 | $this->assertSame(isset($array[$newKey]), isset($obj[$newKey]));
113 |
114 | unset($array[$newKey], $obj[$newKey]);
115 | $this->assertSame(isset($array[$newKey]), isset($obj[$newKey]));
116 | }
117 |
118 | /**
119 | * @param array $array
120 | *
121 | * @dataProvider arrayProvider
122 | */
123 | public function testArrayCount($array)
124 | {
125 | $obj = new ArrObj($array);
126 |
127 | $this->assertSame(count($array), count($obj));
128 | $firstKey = Arr::getFirstKey($array);
129 | $lastKey = Arr::getLastKey($array);
130 |
131 | unset($array[$firstKey], $obj[$firstKey], $array[$lastKey], $obj[$lastKey]);
132 | $this->assertSame(count($array), count($obj));
133 | }
134 |
135 | /**
136 | * @param array $array
137 | *
138 | * @dataProvider arrayProvider
139 | */
140 | public function testArrayIterator($array)
141 | {
142 | $obj = new ArrObj($array);
143 |
144 | foreach ($obj as $key => $value) {
145 | $this->assertSame($array[$key], $value);
146 | }
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/tests/ArrTestCase.php:
--------------------------------------------------------------------------------
1 | '',
15 | 'array' => [],
16 | 'int' => 1,
17 | 'bool' => true,
18 | 'float' => 1.0,
19 | 'null' => null,
20 | 'callable' => 'boolval',
21 | ];
22 |
23 | /**
24 | * @param string $method
25 | * @param bool $includeFirst
26 | * @return array
27 | * @throws ReflectionException
28 | */
29 | protected function getMockedParams(string $method, bool $includeFirst = false)
30 | {
31 | $reflection = new ReflectionClass('\Minwork\Helper\Arr');
32 | $reflectionMethod = $reflection->getMethod($method);
33 | $reflectionParams = $reflectionMethod->getParameters();
34 |
35 | if (!$includeFirst) {
36 | // Skip first param cause it's array supplied from object
37 | array_shift($reflectionParams);
38 | }
39 |
40 | $params = [];
41 |
42 | foreach ($reflectionParams as $reflectionParam) {
43 | $reflectionType = $reflectionParam->getType();
44 | $params[] = $this->typesMock[$reflectionType ? $reflectionType->getName() : 'null'];
45 | }
46 |
47 | return $params;
48 | }
49 |
50 | protected function createObjectWithMethod(callable $method)
51 | {
52 | return new class($method)
53 | {
54 | protected $method;
55 |
56 | function __construct($method)
57 | {
58 | $this->method = $method;
59 | }
60 |
61 | function __call($func, $params)
62 | {
63 | return ($this->method)(...$params);
64 | }
65 | };
66 | }
67 |
68 | public function arrayProvider(): array
69 | {
70 | return [
71 | [[3]],
72 | [['key']],
73 | [[1, 'test']],
74 | [['key1' => 1, 'key2' => 2, 'key3' => 3]],
75 | [[[[]]]],
76 | [[0, '', null, false]],
77 | [[1, true, 'true', 'false', '0', ' 1', PHP_INT_MIN, PHP_INT_MAX]],
78 | [[
79 | 'test' => [
80 | 'test1',
81 | 'test2' => [
82 | 'test3' => 'abc',
83 | 'test4'
84 | ],
85 | [
86 | 'test6' => 'def'
87 | ],
88 | ],
89 | ]],
90 | ['a' => [
91 | 'b' => [
92 | 1 => [
93 | PHP_INT_MIN,
94 | 2 => 3
95 | ],
96 | 'c' => [
97 | 4,
98 | true,
99 | ],
100 | 5
101 | ],
102 | 'd' => [
103 | 'e',
104 | [
105 | 'f' => 6
106 | ]
107 | ],
108 | ],
109 | 'g' => [
110 | 'h',
111 | 'i',
112 | PHP_INT_MAX,
113 | 'j' => [
114 | 7,
115 | 2 => 8,
116 | null,
117 | ]
118 | ],
119 | [
120 | 'k',
121 | 'l' => [
122 | 9,
123 | 10,
124 | false,
125 | ],
126 | ]]
127 | ];
128 | }
129 |
130 | protected function callMethod(callable $callable, $array, ...$args)
131 | {
132 | if (!is_array($callable)) {
133 | throw new InvalidArgumentException('Callable must be array');
134 | }
135 |
136 | $class = $callable[0];
137 | $method = $callable[1];
138 |
139 | // If calling ArrObj
140 | if (is_object($class)) {
141 | /** @var ArrObj $class */
142 |
143 | $class->setArray($array);
144 | // Call method
145 | $result = $class->$method(...$args);
146 |
147 | // If method is chainable then get current array value for proper assertion
148 | if (in_array($method, ArrObj::CHAINABLE_METHODS)) {
149 | return $class->getArray();
150 | } else {
151 | return $result;
152 | }
153 | }
154 |
155 | return call_user_func("{$class}::{$method}", $array, ...$args);
156 | }
157 |
158 | public function arrayClassProvider(): array
159 | {
160 | return [
161 | [Arr::class],
162 | [new ArrObj()],
163 | ];
164 | }
165 | }
166 |
--------------------------------------------------------------------------------