Dot notation for access multidimensional arrays.
<?php | |
/** | |
* Dot notation for access multidimensional arrays. | |
* | |
* $dn = new DotNotation(['bar'=>['baz'=>['foo'=>true]]]); | |
* | |
* $value = $dn->get('bar.baz.foo'); // $value == true | |
* | |
* $dn->set('bar.baz.foo', false); // ['foo'=>false] | |
* | |
* $dn->add('bar.baz', ['boo'=>true]); // ['foo'=>false,'boo'=>true] | |
* | |
* @author Anton Medvedev <anton (at) elfet (dot) ru> | |
* @version 2.0 | |
* @license MIT | |
*/ | |
class DotNotation | |
{ | |
const SEPARATOR = '/[:\.]/'; | |
/** | |
* @var array | |
*/ | |
protected $values = array(); | |
/** | |
* @var array | |
*/ | |
public function __construct(array $values) | |
{ | |
$this->values = $values; | |
} | |
/** | |
* @param string $path | |
* @param string $default | |
* @return mixed | |
*/ | |
public function get($path, $default = null) | |
{ | |
$array = $this->values; | |
if (!empty($path)) { | |
$keys = $this->explode($path); | |
foreach ($keys as $key) { | |
if (isset($array[$key])) { | |
$array = $array[$key]; | |
} else { | |
return $default; | |
} | |
} | |
} | |
return $array; | |
} | |
/** | |
* @param string $path | |
* @param mixed $value | |
*/ | |
public function set($path, $value) | |
{ | |
if (!empty($path)) { | |
$at = & $this->values; | |
$keys = $this->explode($path); | |
while (count($keys) > 0) { | |
if (count($keys) === 1) { | |
if (is_array($at)) { | |
$at[array_shift($keys)] = $value; | |
} else { | |
throw new \RuntimeException("Can not set value at this path ($path) because is not array."); | |
} | |
} else { | |
$key = array_shift($keys); | |
if (!isset($at[$key])) { | |
$at[$key] = array(); | |
} | |
$at = & $at[$key]; | |
} | |
} | |
} else { | |
$this->values = $value; | |
} | |
} | |
/** | |
* @param $path | |
* @param array $values | |
*/ | |
public function add($path, array $values) | |
{ | |
$get = (array)$this->get($path); | |
$this->set($path, $this->arrayMergeRecursiveDistinct($get, $values)); | |
} | |
/** | |
* @param string $path | |
* @return bool | |
*/ | |
public function have($path) | |
{ | |
$keys = $this->explode($path); | |
$array = $this->values; | |
foreach ($keys as $key) { | |
if (isset($array[$key])) { | |
$array = $array[$key]; | |
} else { | |
return false; | |
} | |
} | |
return true; | |
} | |
/** | |
* @param array $values | |
*/ | |
public function setValues($values) | |
{ | |
$this->values = $values; | |
} | |
/** | |
* @return array | |
*/ | |
public function getValues() | |
{ | |
return $this->values; | |
} | |
protected function explode($path) | |
{ | |
return preg_split(self::SEPARATOR, $path); | |
} | |
/** | |
* array_merge_recursive does indeed merge arrays, but it converts values with duplicate | |
* keys to arrays rather than overwriting the value in the first array with the duplicate | |
* value in the second array, as array_merge does. I.e., with array_merge_recursive, | |
* this happens (documented behavior): | |
* | |
* array_merge_recursive(array('key' => 'org value'), array('key' => 'new value')); | |
* => array('key' => array('org value', 'new value')); | |
* | |
* arrayMergeRecursiveDistinct does not change the datatypes of the values in the arrays. | |
* Matching keys' values in the second array overwrite those in the first array, as is the | |
* case with array_merge, i.e.: | |
* | |
* arrayMergeRecursiveDistinct(array('key' => 'org value'), array('key' => 'new value')); | |
* => array('key' => array('new value')); | |
* | |
* Parameters are passed by reference, though only for performance reasons. They're not | |
* altered by this function. | |
* | |
* If key is integer, it will be merged like array_merge do: | |
* arrayMergeRecursiveDistinct(array(0 => 'org value'), array(0 => 'new value')); | |
* => array(0 => 'org value', 1 => 'new value'); | |
* | |
* @param array $array1 | |
* @param array $array2 | |
* @return array | |
* @author Daniel <daniel (at) danielsmedegaardbuus (dot) dk> | |
* @author Gabriel Sobrinho <gabriel (dot) sobrinho (at) gmail (dot) com> | |
* @author Anton Medvedev <anton (at) elfet (dot) ru> | |
*/ | |
protected function arrayMergeRecursiveDistinct(array &$array1, array &$array2) | |
{ | |
$merged = $array1; | |
foreach ($array2 as $key => &$value) { | |
if (is_array($value) && isset ($merged[$key]) && is_array($merged[$key])) { | |
if (is_int($key)) { | |
$merged[] = $this->arrayMergeRecursiveDistinct($merged[$key], $value); | |
} else { | |
$merged[$key] = $this->arrayMergeRecursiveDistinct($merged[$key], $value); | |
} | |
} else { | |
if (is_int($key)) { | |
$merged[] = $value; | |
} else { | |
$merged[$key] = $value; | |
} | |
} | |
} | |
return $merged; | |
} | |
} |
<?php | |
/** | |
* Tests for DotNotation class. | |
* | |
* @author Anton Medvedev <anton (at) elfet (dot) ru> | |
* @version 2.0 | |
* @license MIT | |
*/ | |
class DotNotationTest extends \PHPUnit_Framework_TestCase | |
{ | |
public function testSet() | |
{ | |
$d = new DotNotation([]); | |
$d->set('one', 1); | |
$this->assertEquals(['one' => 1], $d->getValues()); | |
} | |
public function testSetOverride() | |
{ | |
$d = new DotNotation(['one' => 1]); | |
$d->set('one', 2); | |
$this->assertEquals(['one' => 2], $d->getValues()); | |
} | |
public function testSetPath() | |
{ | |
$d = new DotNotation(['one' => ['two' => 1]]); | |
$d->set('one.two', 2); | |
$this->assertEquals(['one' => ['two' => 2]], $d->getValues()); | |
} | |
public function testPathAppend() | |
{ | |
$d = new DotNotation(['one' => ['two' => 1]]); | |
$d->set('one.other', 1); | |
$this->assertEquals(['one' => ['two' => 1, 'other' => 1]], $d->getValues()); | |
} | |
public function testSetAppend() | |
{ | |
$d = new DotNotation(['one' => ['two' => 1]]); | |
$d->set('two', 2); | |
$this->assertEquals(['one' => ['two' => 1], 'two' => 2], $d->getValues()); | |
} | |
public function testSetAppendArray() | |
{ | |
$d = new DotNotation(['one' => ['two' => 1]]); | |
$d->set('one', ['two' => 2]); | |
$this->assertEquals(['one' => ['two' => 2]], $d->getValues()); | |
} | |
public function testSetOverrideAndAppend() | |
{ | |
$d = new DotNotation(['one' => ['two' => 1]]); | |
$d->set('one', ['two' => 2, 'other' => 3]); | |
$this->assertEquals(['one' => ['two' => 2, 'other' => 3]], $d->getValues()); | |
} | |
public function testSetOverrideByArray() | |
{ | |
$d = new DotNotation(['one' => ['two' => 1]]); | |
$d->set('one', ['other' => 3]); | |
$this->assertEquals(['one' => ['other' => 3]], $d->getValues()); | |
} | |
public function testSetPathByDoubleDots() | |
{ | |
$d = new DotNotation(['one' => ['two' => ['three' => 1]]]); | |
$d->set('one:two:three', 3); | |
$this->assertEquals(['one' => ['two' => ['three' => 3]]], $d->getValues()); | |
} | |
public function testGet() | |
{ | |
$d = new DotNotation(['one' => ['two' => ['three' => 1]]]); | |
$this->assertEquals(['one' => ['two' => ['three' => 1]]], $d->get(null)); | |
$this->assertEquals(['two' => ['three' => 1]], $d->get('one')); | |
$this->assertEquals(['three' => 1], $d->get('one.two')); | |
$this->assertEquals(1, $d->get('one.two.three')); | |
$this->assertEquals(false, $d->get('one.two.three.next', false)); | |
} | |
public function testHave() | |
{ | |
$d = new DotNotation(['one' => ['two' => ['three' => 1]]]); | |
$this->assertTrue($d->have('one')); | |
$this->assertTrue($d->have('one.two')); | |
$this->assertTrue($d->have('one.two.three')); | |
$this->assertFalse($d->have('one.two.three.false')); | |
$this->assertFalse($d->have('one.false.three')); | |
$this->assertFalse($d->have('false')); | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment