├── .gitignore ├── .travis.yml ├── ArrayFinder.php ├── LICENSE ├── README.md ├── Tests └── ArrayFinderTest.php ├── composer.json └── phpunit.xml.dist /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | composer.lock 3 | phpunit.xml 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | matrix: 4 | include: 5 | - php: 5.4 6 | dist: trusty 7 | - php: 5.5 8 | dist: trusty 9 | - php: 5.6 10 | dist: trusty 11 | - php: 7.0 12 | dist: trusty 13 | - php: 7.1 14 | dist: bionic 15 | - php: 7.2 16 | dist: bionic 17 | - php: 7.3 18 | dist: bionic 19 | - php: 7.4 20 | dist: bionic 21 | 22 | install: travis_retry composer install 23 | script: ./vendor/bin/phpunit 24 | -------------------------------------------------------------------------------- /ArrayFinder.php: -------------------------------------------------------------------------------- 1 | content = $content; 23 | } 24 | 25 | /** 26 | * {@inheritdoc} 27 | */ 28 | public function offsetExists($offset) 29 | { 30 | if (strpos($offset, $this->pathSeparator) !== false) { 31 | 32 | $explodedPath = explode($this->pathSeparator, $offset); 33 | $lastOffset = array_pop($explodedPath); 34 | 35 | $offsetExists = false; 36 | $containerPath = implode($this->pathSeparator, $explodedPath); 37 | 38 | $this->callAtPath($containerPath, function($container) use ($lastOffset, &$offsetExists) { 39 | $offsetExists = isset($container[$lastOffset]); 40 | }); 41 | 42 | return $offsetExists; 43 | 44 | } else { 45 | return isset($this->content[$offset]); 46 | } 47 | } 48 | 49 | /** 50 | * {@inheritdoc} 51 | */ 52 | public function offsetGet($offset) 53 | { 54 | return $this->get($offset); 55 | } 56 | 57 | /** 58 | * {@inheritdoc} 59 | */ 60 | public function offsetSet($offset, $value) 61 | { 62 | if (is_null($offset)) { 63 | $this->content[] = $value; 64 | } else { 65 | $this->content[$offset] = $value; 66 | } 67 | } 68 | 69 | /** 70 | * {@inheritdoc} 71 | */ 72 | public function offsetUnset($offset) 73 | { 74 | $path = explode($this->pathSeparator, $offset); 75 | $pathToUnset = array_pop($path); 76 | 77 | $this->callAtPath(implode($this->pathSeparator, $path), function(&$offset) use (&$pathToUnset) { 78 | unset($offset[$pathToUnset]); 79 | }); 80 | } 81 | 82 | /** 83 | * {@inheritdoc} 84 | */ 85 | public function count() 86 | { 87 | return count($this->content); 88 | } 89 | 90 | /** 91 | * {@inheritdoc} 92 | */ 93 | public function current() 94 | { 95 | $keys = array_keys($this->content); 96 | return $this->content[$keys[$this->position]]; 97 | } 98 | 99 | /** 100 | * {@inheritdoc} 101 | */ 102 | public function next() 103 | { 104 | ++$this->position; 105 | } 106 | 107 | /** 108 | * {@inheritdoc} 109 | */ 110 | public function key() 111 | { 112 | $keys = array_keys($this->content); 113 | return $keys[$this->position]; 114 | } 115 | 116 | /** 117 | * {@inheritdoc} 118 | */ 119 | public function valid() 120 | { 121 | $keys = array_keys($this->content); 122 | return isset($keys[$this->position]); 123 | } 124 | 125 | /** 126 | * {@inheritdoc} 127 | */ 128 | public function rewind() 129 | { 130 | $this->position = 0; 131 | } 132 | 133 | /** 134 | * {@inheritdoc} 135 | */ 136 | public function serialize() { 137 | return serialize($this->content); 138 | } 139 | 140 | /** 141 | * {@inheritdoc} 142 | */ 143 | public function unserialize($content) { 144 | $this->content = unserialize($content); 145 | } 146 | 147 | /** 148 | * Change the path separator of the array wrapper. 149 | * 150 | * By default, the separator is: . 151 | * 152 | * @param string $separator Separator to set. 153 | * 154 | * @return ArrayFinder Current instance. 155 | */ 156 | public function changeSeparator($separator) 157 | { 158 | $this->pathSeparator = $separator; 159 | return $this; 160 | } 161 | 162 | /** 163 | * Return a value from the array corresponding to the path. 164 | * If the path is not set in the array, then $default is returned. 165 | * 166 | * ex: 167 | * $a = ['a' => ['b' => 'yeah']]; 168 | * echo $this->get('a.b'); // yeah 169 | * echo $this->get('a.b.c', 'nope'); // nope 170 | * 171 | * @param string|int|null $path Path to the value. If null, return all the content. 172 | * @param mixed $default Default value to return when path is not contained in the array. 173 | * 174 | * @return mixed|null Value on the array corresponding to the path, null if the key does not exist. 175 | */ 176 | public function get($path = null, $default = null) 177 | { 178 | if ($path === null) { 179 | return $this->content; 180 | } 181 | 182 | $value = $default; 183 | $this->callAtPath($path, function(&$offset) use (&$value) { 184 | $value = $offset; 185 | }); 186 | 187 | return $value; 188 | } 189 | 190 | /** 191 | * Insert a value to the array at the specified path. 192 | * 193 | * ex: 194 | * $this->set('a.b', 'yeah); // ['a' => ['b' => 'yeah']] 195 | * 196 | * @param string $path Path where the values will be insered. 197 | * @param mixed $value Value ti insert. 198 | * 199 | * @return ArrayFinder Current instance. 200 | */ 201 | public function set($path, $value) 202 | { 203 | $this->callAtPath($path, function(&$offset) use ($value) { 204 | $offset = $value; 205 | }, true); 206 | 207 | return $this; 208 | } 209 | 210 | private function callAtPath($path, callable $callback, $createPath = false, &$currentOffset = null) 211 | { 212 | if ($currentOffset === null) { 213 | $currentOffset = &$this->content; 214 | 215 | if (is_string($path) && $path == '') { 216 | $callback($currentOffset); 217 | return; 218 | } 219 | } 220 | 221 | $explodedPath = explode($this->pathSeparator, $path); 222 | $nextPath = array_shift($explodedPath); 223 | 224 | if (!isset($currentOffset[$nextPath])) { 225 | if ($createPath) { 226 | $currentOffset[$nextPath] = []; 227 | } else { 228 | return; 229 | } 230 | } 231 | 232 | if (count($explodedPath) > 0) { 233 | $this->callAtPath( 234 | implode($this->pathSeparator, $explodedPath), 235 | $callback, 236 | $createPath, 237 | $currentOffset[$nextPath] 238 | ); 239 | } else { 240 | $callback($currentOffset[$nextPath]); 241 | } 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Julien Martin 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 | [![Build Status](https://travis-ci.org/Shudrum/ArrayFinder.svg?branch=master)](https://travis-ci.org/Shudrum/ArrayFinder) 2 | 3 | # ArrayFinder Component 4 | 5 | The ArrayFinder component allow you to manage large nested arrays with ease. 6 | 7 | Here is a simple example that shows how to easily get a value from an array: 8 | 9 | ```php 10 | use Shudrum\Component\ArrayFinder\ArrayFinder; 11 | 12 | $arrayFinder = new ArrayFinder([ 13 | 'level_1' => [ 14 | 'level_2' => [ 15 | 'level_3' => 'value', 16 | ], 17 | ], 18 | ]); 19 | 20 | $myValue = $arrayFinder->get('level_1.level_2.level_3'); 21 | // OR 22 | $myValue = $arrayFinder['level_1.level_2.level_3']; 23 | ``` 24 | 25 | To install this package, you can simply use composer: 26 | 27 | ``` 28 | composer require shudrum/array-finder 29 | ``` 30 | 31 | ## Documentation 32 | 33 | ### Methods 34 | 35 | #### get($path) 36 | 37 | You can get a value following a path separated by a '.'. 38 | 39 | ```php 40 | use Shudrum\Component\ArrayFinder\ArrayFinder; 41 | 42 | $arrayFinder = new ArrayFinder([ 43 | 'a' => [ 44 | 'b' => [ 45 | 'c' => 'value1', 46 | ], 47 | 'value2', 48 | ], 49 | 'value3', 50 | ]); 51 | 52 | $myValue = $arrayFinder->get('a.b.c'); // value1 53 | $myValue = $arrayFinder->get('a.0'); // value2 54 | $myValue = $arrayFinder->get(0); // value3 55 | ``` 56 | 57 | If the path is `null`, all the content will be returned. 58 | 59 | #### set($path, $value) 60 | 61 | You can add a value to a specific path separated by a '.'. If the nested arrays does not exists, it will be created. 62 | 63 | ```php 64 | use Shudrum\Component\ArrayFinder\ArrayFinder; 65 | 66 | $arrayFinder = new ArrayFinder(); 67 | $arrayFinder->set('a.b', 'value'); 68 | 69 | $arrayFinder->get(); // ['a' => ['b' => 'value]] 70 | ``` 71 | 72 | #### changeSeparator($separator) 73 | 74 | If the default separator (.) does not fit to your needs, you can call this method to change it. 75 | 76 | ```php 77 | use Shudrum\Component\ArrayFinder\ArrayFinder; 78 | 79 | $arrayFinder = new ArrayFinder([…]); 80 | 81 | $myValue = $arrayFinder->changeSeparator('/'); 82 | $myValue = $arrayFinder->get('a/b/c'); 83 | ``` 84 | 85 | ### Implementations 86 | 87 | The ArrayFinder component implements some usefull interfaces: 88 | 89 | #### ArrayAccess 90 | 91 | You can use this object like an array: 92 | 93 | ```php 94 | use Shudrum\Component\ArrayFinder\ArrayFinder; 95 | 96 | $arrayFinder = new ArrayFinder([…]); 97 | 98 | $value = $arrayFinder['a.b']; 99 | $arrayFinder['a.b.c'] = 'value'; 100 | unset($arrayFinder['a.b']); 101 | ``` 102 | 103 | #### Countable 104 | 105 | You can use count on this object: 106 | 107 | ```php 108 | use Shudrum\Component\ArrayFinder\ArrayFinder; 109 | 110 | $arrayFinder = new ArrayFinder([…]); 111 | 112 | count($arrayFinder); 113 | count($arrayFinder['a.b']); 114 | ``` 115 | 116 | #### Iterator 117 | 118 | You can iterate on this object: 119 | 120 | ```php 121 | use Shudrum\Component\ArrayFinder\ArrayFinder; 122 | 123 | $arrayFinder = new ArrayFinder([…]); 124 | 125 | foreach ($arrayFinder as $key => $value) { 126 | // … 127 | } 128 | ``` 129 | 130 | #### Serializable 131 | 132 | You can easily serialize / unserialize this object. 133 | 134 | ##Resources 135 | 136 | You can run the unit tests with the following command: 137 | 138 | $ cd path/to/Shudrum/Component/ArrayFinder/ 139 | $ composer install 140 | $ phpunit 141 | -------------------------------------------------------------------------------- /Tests/ArrayFinderTest.php: -------------------------------------------------------------------------------- 1 | arrayFinder = new ArrayFinder([ 26 | 'hello', 27 | 'world', 28 | 'a' => [ 29 | 'b' => [ 30 | 'c' => 'end', 31 | ], 32 | 'value_without_key', 33 | [ 34 | 'value_without_key_bis', 35 | ] 36 | ], 37 | 'here' => 'is_a_key', 38 | ]); 39 | } 40 | 41 | public function testArrayAccessReading() 42 | { 43 | $this->assertEquals('hello', $this->arrayFinder[0]); 44 | } 45 | 46 | public function testArrayAccessInsertValueWithoutKey() 47 | { 48 | $this->arrayFinder[] = '!'; 49 | $this->assertEquals('!', $this->arrayFinder[2]); 50 | } 51 | 52 | public function testArrayAccessInsertValueWithKey() 53 | { 54 | $this->arrayFinder['last'] = '!'; 55 | $this->assertEquals('!', $this->arrayFinder['last']); 56 | } 57 | 58 | public function testArrayAccessUnset() 59 | { 60 | unset($this->arrayFinder[1]); 61 | $this->assertNull($this->arrayFinder[1]); 62 | } 63 | 64 | public function testArrayAccessIsset() 65 | { 66 | $this->assertEquals(true, isset($this->arrayFinder['here'])); 67 | } 68 | 69 | public function testArrayAccessIssetRecursive() 70 | { 71 | $this->assertEquals(true, isset($this->arrayFinder['a.b'])); 72 | $this->assertEquals(true, isset($this->arrayFinder['a.b.c'])); 73 | } 74 | 75 | public function testArrayAccessIssetRecursiveOnNonExistingPath() 76 | { 77 | $this->assertEquals(false, isset($this->arrayFinder['a.b.c.d.e'])); 78 | } 79 | 80 | public function testSimpleGet() 81 | { 82 | $this->assertEquals('hello', $this->arrayFinder->get('0')); 83 | $this->assertEquals('hello', $this->arrayFinder->get(0)); 84 | } 85 | 86 | public function testGetWithStringPath() 87 | { 88 | $this->assertEquals('end', $this->arrayFinder->get('a.b.c')); 89 | } 90 | 91 | public function testGetWithIndexPath() 92 | { 93 | $this->assertEquals('value_without_key', $this->arrayFinder->get('a.0')); 94 | $this->assertEquals('value_without_key_bis', $this->arrayFinder->get('a.1.0')); 95 | } 96 | 97 | public function testArrayAccessReadingWithPath() 98 | { 99 | $this->assertEquals('end', $this->arrayFinder['a.b.c']); 100 | } 101 | 102 | public function testGetReturnNullIfDoesNotExist() 103 | { 104 | $this->assertNull($this->arrayFinder->get('a.b.d')); 105 | } 106 | 107 | public function testSetCorrectlyAddValueToRoot() 108 | { 109 | $this->arrayFinder->set('new', 'yeah'); 110 | $this->assertEquals('yeah', $this->arrayFinder['new']); 111 | } 112 | 113 | public function testSetCorrectlyAddValueIfPathExist() 114 | { 115 | $this->arrayFinder->set('a.b.c', 'c_replaced'); 116 | $this->assertEquals('c_replaced', $this->arrayFinder['a.b.c']); 117 | } 118 | 119 | public function testSetCorrectlyReturnTheInstance() 120 | { 121 | $this->assertInstanceOf('\Shudrum\Component\ArrayFinder\ArrayFinder', $this->arrayFinder->set('a', 'b')); 122 | } 123 | 124 | public function testSetCorrecltyAddValueIfPathDoesNotExist() 125 | { 126 | $this->arrayFinder->set('d.e.f', 'f_setted'); 127 | $this->assertEquals('f_setted', $this->arrayFinder['d.e.f']); 128 | } 129 | 130 | public function testSetCorrectlyAddAnArray() 131 | { 132 | $this->arrayFinder->set('b', ['it' => ['works' => ['cool']]]); 133 | $this->assertEquals('cool', $this->arrayFinder['b.it.works.0']); 134 | } 135 | 136 | public function testSetCorrectlyReplaceArray() 137 | { 138 | $this->arrayFinder->set('a', 'value'); 139 | $this->assertEquals('value', $this->arrayFinder['a']); 140 | } 141 | 142 | public function testSetEmptyValue() 143 | { 144 | $this->arrayFinder->set('a', null); 145 | $this->assertNull($this->arrayFinder['a']); 146 | } 147 | 148 | public function testSetEmptyArray() 149 | { 150 | $this->arrayFinder->set('this.is.empty', []); 151 | $this->assertEmpty($this->arrayFinder['this.is.empty']); 152 | } 153 | 154 | public function testCount() 155 | { 156 | $this->assertEquals(4, count($this->arrayFinder)); 157 | } 158 | 159 | public function testArrayAccessReference() 160 | { 161 | $this->arrayFinder['access'] = 'work'; 162 | $this->assertEquals('work', $this->arrayFinder->get('access')); 163 | } 164 | 165 | public function testIteratorImplementation() 166 | { 167 | $id = 0; 168 | foreach ($this->arrayFinder as $key => $value) { 169 | switch ($id) { 170 | case 0: 171 | $this->assertEquals(0, $key); 172 | $this->assertEquals('hello', $value); 173 | break; 174 | case 2: 175 | $this->assertEquals('a', $key); 176 | $this->assertCount(3, $value); 177 | break; 178 | } 179 | $id++; 180 | } 181 | } 182 | 183 | public function testSerializableImplementation() 184 | { 185 | $serialized = serialize($this->arrayFinder); 186 | 187 | /** @var ArrayFinder $newArrayFinder */ 188 | $newArrayFinder = unserialize($serialized); 189 | 190 | $this->assertEquals('end', $newArrayFinder['a.b.c']); 191 | } 192 | 193 | public function testArrayAccessUnsetWithPath() 194 | { 195 | unset($this->arrayFinder['a.b']); 196 | $this->assertNull($this->arrayFinder['a.b']); 197 | } 198 | 199 | public function testArrayAccessUnsetWithInt() 200 | { 201 | unset($this->arrayFinder['a.1']); 202 | $this->assertNull($this->arrayFinder['a.1']); 203 | } 204 | 205 | public function testCountOnFirstAccess() 206 | { 207 | $this->assertCount(1, $this->arrayFinder['a.b']); 208 | } 209 | 210 | public function testChangeSeparator() 211 | { 212 | $this->arrayFinder->changeSeparator('/'); 213 | $this->assertEquals('value_without_key_bis', $this->arrayFinder->get('a/1/0')); 214 | $this->assertEquals('value_without_key_bis', $this->arrayFinder['a/1/0']); 215 | } 216 | 217 | public function testChangeSeparatorReturnTheInstance() 218 | { 219 | $this->assertInstanceOf( 220 | '\Shudrum\Component\ArrayFinder\ArrayFinder', 221 | $this->arrayFinder->changeSeparator('/') 222 | ); 223 | } 224 | 225 | public function testArrayAccessAddValueToRoot() 226 | { 227 | $arrayFinder = new ArrayFinder(); 228 | $arrayFinder[] = 'added'; 229 | 230 | $this->assertCount(1, $arrayFinder); 231 | $this->assertEquals('added', $arrayFinder[0]); 232 | } 233 | 234 | public function testGetWithoutParamWillReturnAllTheArray() 235 | { 236 | $content = $this->arrayFinder->get(); 237 | 238 | $this->assertEquals('end', $content['a']['b']['c']); 239 | } 240 | 241 | public function testGetAcceptsADefaultArgument() 242 | { 243 | // ensure the default value is not returned when 244 | // the array does have the key 245 | $this->assertEquals( 246 | "is defined", 247 | (new ArrayFinder(["some" => ["key" => "is defined"]]))->get("some.key", "default value") 248 | ); 249 | 250 | // ensure that when the key is not defined, the default 251 | // value is used 252 | $this->assertEquals( 253 | "default value", 254 | (new ArrayFinder([]))->get("some.key", "default value") 255 | ); 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shudrum/array-finder", 3 | "type": "library", 4 | "description": "ArrayFinder component", 5 | "keywords": [], 6 | "license": "MIT", 7 | "homepage": "https://github.com/Shudrum/ArrayFinder", 8 | "authors": [ 9 | { 10 | "name": "Julien Martin", 11 | "email": "martin.julien82@gmail.com" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=5.4" 16 | }, 17 | "autoload": { 18 | "psr-4": { "Shudrum\\Component\\ArrayFinder\\": "" }, 19 | "exclude-from-classmap": [ 20 | "/Tests/" 21 | ] 22 | }, 23 | "require-dev": { 24 | "phpunit/phpunit": "^4.8 || ^7.5" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ./Tests/ 16 | 17 | 18 | 19 | 20 | 21 | ./ 22 | 23 | ./Resources 24 | ./Tests 25 | ./vendor 26 | 27 | 28 | 29 | 30 | --------------------------------------------------------------------------------