Skip to content

Commit

Permalink
Added wildcard to GET resolves: #40
Browse files Browse the repository at this point in the history
  • Loading branch information
David Schoenbauer committed Dec 8, 2016
1 parent 1ef7233 commit f1cd126
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 27 deletions.
110 changes: 102 additions & 8 deletions src/DotNotation/ArrayDotNotation.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,35 @@
* An easier way to deal with complex PHP arrays
*
* @author David Schoenbauer
* @version 1.1.1
* @version 1.3.0
*/
class ArrayDotNotation {

const WILD_CARD_CHARACTER = "*";

/**
* Returns only the values that match the dot notation path. Used only with wild cards.
*/
const MODE_RETURN_FOUND = 'found';

/**
* Returns a default value if the dot notation path is not found.
*/
const MODE_RETURN_DEFAULT = 'default';

/**
* Throws a DSchoenbauer\DotNotation\Exception\PathNotFoundException if the path is not found in the data.
*/
const MODE_THROW_EXCEPTION = 'exception';

/**
* Property that houses the data that the dot notation should access
* @var array
*/
private $_data = [];
private $_notationType = ".";
private $_defaultValue;
private $_getMode;

/**
* Sets the data to parse in a chain
Expand All @@ -65,7 +84,7 @@ public static function with(array $data = []) {
* @param array $data optional Array of data that will be accessed via dot notation.
*/
public function __construct(array $data = []) {
$this->setData($data);
$this->setData($data)->setGetMode(self::MODE_RETURN_DEFAULT);
}

/**
Expand Down Expand Up @@ -103,7 +122,8 @@ public function setData(array $data) {
* @return mixed value found via dot notation in the array of data
*/
public function get($dotNotation, $defaultValue = null) {
return $this->recursiveGet($this->getData(), $this->getKeys($dotNotation), $defaultValue);
$this->setDefaultValue($defaultValue);
return $this->recursiveGet($this->getData(), $this->getKeys($dotNotation));
}

/**
Expand All @@ -114,14 +134,32 @@ public function get($dotNotation, $defaultValue = null) {
* @param mixed $defaultValue value to return when a key is not found
* @return mixed value that the keys find in the data array
*/
protected function recursiveGet($data, $keys, $defaultValue) {
protected function recursiveGet($data, $keys) {
$key = array_shift($keys);
if (is_array($data) && $key && count($keys) == 0) { //Last Key
return array_key_exists($key, $data) ? $data[$key] : $defaultValue;
if (is_array($data) && $key === static::WILD_CARD_CHARACTER) {
return $this->wildCardGet($keys, $data);
} elseif (is_array($data) && $key && count($keys) == 0) { //Last Key
return array_key_exists($key, $data) ? $data[$key] : $this->getDefaultValue($key);
} elseif (is_array($data) && array_key_exists($key, $data)) {
return $this->recursiveGet($data[$key], $keys, $defaultValue);
return $this->recursiveGet($data[$key], $keys);
}
return $defaultValue;
return $this->getDefaultValue($key);
}

protected function wildCardGet(array $keys, $data) {
$output = [];
foreach ($data as $key => $value) {
try {
$tempKeys = $keys;
array_unshift($tempKeys, $key);
$output[] = $this->recursiveGet($data, $tempKeys);
} catch (\Exception $exc) {
if ($this->getGetMode() !== self::MODE_RETURN_FOUND) {
throw $exc;
}
}
}
return $output;
}

/**
Expand Down Expand Up @@ -224,6 +262,7 @@ protected function recursiveRemove(array &$data, array $keys) {

/**
* consistently parses notation keys
* @since 1.2.0
* @param type $notation key path to a value in an array
* @return array array of keys as delimited by the notation type
*/
Expand All @@ -234,6 +273,7 @@ protected function getKeys($notation) {
/**
* Returns the current notation type that delimits the notation path.
* Default: "."
* @since 1.2.0
* @return string current notation character delimiting the notation path
*/
public function getNotationType() {
Expand All @@ -242,6 +282,7 @@ public function getNotationType() {

/**
* Sets the current notation type used to delimit the notation path.
* @since 1.2.0
* @param string $notationType
* @return $this
*/
Expand All @@ -252,6 +293,8 @@ public function setNotationType($notationType = ".") {

/**
* Checks to see if a dot notation path is present in the data set.
*
* @since 1.2.0
* @param string $dotNotation dot notation representation of keys of where to remove a value
* @return boolean returns true if the dot notation path exists in the data
*/
Expand All @@ -268,4 +311,55 @@ public function has($dotNotation) {
return true;
}

/**
* Returns the current default value.
* @note should be a protected method
* @since 1.3.0
* @param type $key
* @return mixed value to be used instead of a real value is not found
* @throws PathNotFoundException when the key
*/
public function getDefaultValue($key = null) {
if ($key !== null && in_array($this->getGetMode(), [self::MODE_THROW_EXCEPTION, self::MODE_RETURN_FOUND])) {
throw new PathNotFoundException($key);
}
return $this->_defaultValue;
}

/**
* The default value that get will return. Do not set the value with this method. Set the default value in the get method.
* @note should be a protected method
* @since 1.3.0
* @param mixed $defaultValue value to be used instead of a real value is not found
* @return $this
*/
public function setDefaultValue($defaultValue) {
$this->_defaultValue = $defaultValue;
return $this;
}

/**
* Returns the behavior defined for how get will return a value.
* @since 1.3.0
* @return enum values are constants of this class MODE_RETURN_DEFAULT, MODE_RETURN_FOUND, MODE_THROW_EXCEPTION
*/
public function getGetMode() {
return $this->_getMode;
}

/**
* Defines the behavior on how get returns a value.
* @since 1.3.0
* @param enum $getMode values are constants of this class MODE_RETURN_DEFAULT, MODE_RETURN_FOUND, MODE_THROW_EXCEPTION
* @return $this
*/
public function setGetMode($getMode) {
$modes = [self::MODE_RETURN_DEFAULT, self::MODE_RETURN_FOUND, self::MODE_THROW_EXCEPTION];
if (!in_array($getMode, $modes)) {
throw new Exception\InvalidArgumentException('Not a supported mode. Please use on of:' . implode(', ', $modes));
}
$this->_getMode = $getMode;
return $this;
}

}
98 changes: 79 additions & 19 deletions tests/DotNotation/ArrayDotNotationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace DSchoenbauer\DotNotation;

use DSchoenbauer\DotNotation\Exception\PathNotArrayException;
use DSchoenbauer\DotNotation\Exception\PathNotFoundException;
use DSchoenbauer\DotNotation\Exception\UnexpectedValueException;
use PHPUnit_Framework_TestCase;

Expand Down Expand Up @@ -37,32 +39,70 @@ public function testDataConstructor() {
$object = new ArrayDotNotation($data);
$this->assertEquals($data, $object->getData());
}
public function testWith(){

public function testWith() {
$this->assertInstanceOf(ArrayDotNotation::class, ArrayDotNotation::with());
}

public function testWithData(){
$data = ['test'=>'value'];
public function testWithData() {
$data = ['test' => 'value'];
$this->assertEquals($data, ArrayDotNotation::with($data)->getData());
}
public function testWithNoData(){

public function testWithNoData() {
$this->assertEquals([], ArrayDotNotation::with()->getData());
}

public function testGet() {
$this->assertEquals('someValueB', $this->_object->get('levelA.levelB'));
}

public function testGetWildCardEndWithWild() {
$this->assertEquals(['someValueB'], $this->_object->get('levelA.*'));
}

public function testGetWildCardDefault() {
$this->assertEquals(['someValueB', null, null], $this->_object->get('*.levelB'));
}

public function testGetWildCardOnlyFound() {
$this->assertEquals(['someValueB'], $this->_object->setGetMode(ArrayDotNotation::MODE_RETURN_FOUND)->get('*.levelB'));
}

public function testGetWildTable() {
$data = [
'test' => [
'test' => [
['value' => 'a', 'ontme' => 1],
['value' => 'b', 'ontme' => 1],
['value' => 'c', 'ontme' => 1],
['value' => 'd', 'ontme' => 1],
['value' => 'e', 'ontme' => 1],
['value' => 'f', 'ontme' => 1],
]
]
];
$this->assertEquals(['a', 'b', 'c', 'd', 'e', 'f'], $this->_object->setData($data)->get('test.test.*.value'));
}

public function testGetWildCardNotFoundException(){
$this->expectException(PathNotFoundException::class);
$this->_object->setGetMode(ArrayDotNotation::MODE_THROW_EXCEPTION)->get('*.levelC', 'noValue');
}

public function testGetNoFindDefaultValue() {
$this->assertEquals('noValue', $this->_object->get('levelA.levelB.levelC', 'noValue'));
}

public function testGetSameLevelDefaultValue() {
public function testGetDefaultValue() {
$this->assertEquals('noValue', $this->_object->get('levelA.levelC', 'noValue'));
}

public function testGetException() {
$this->expectException(PathNotFoundException::class);
$this->_object->setGetMode(ArrayDotNotation::MODE_THROW_EXCEPTION)->get('levelA.levelC', 'noValue');
}

public function testSetInitialLevelNewValue() {
$this->assertEquals('noValue', $this->_object->get('levelD', 'noValue'));
$this->assertEquals('newValue', $this->_object->set('levelD', 'newValue')->get('levelD'));
Expand Down Expand Up @@ -147,27 +187,22 @@ public function testRemove() {
$this->assertEquals($data, $this->_object->remove('levelA.levelB')->getData());
}

/**
* @expectedException \DSchoenbauer\DotNotation\Exception\PathNotFoundException
*/
public function testRemovePathNotFound() {
$this->expectException(PathNotFoundException::class);
$this->_object->remove('levelA.levelC');
}

/**
* @expectedException \DSchoenbauer\DotNotation\Exception\PathNotFoundException
*/

public function testRemovePathNotFoundShort() {
$this->expectException(PathNotFoundException::class);
$this->_object->remove('level0');
}
/**
* @expectedException \DSchoenbauer\DotNotation\Exception\PathNotArrayException
*/

public function testRemovePathNotArray() {
$this->expectException(PathNotArrayException::class);
$this->_object->remove('levelA.levelB.levelD');
}

public function testChangeNotationType(){
public function testChangeNotationType() {
$this->assertEquals('someValueB', $this->_object->setNotationType('-')->get('levelA-levelB'));
}

Expand All @@ -179,7 +214,7 @@ public function testChangeNotationTypeSameLevelDefaultValue() {
$this->assertEquals('noValue', $this->_object->setNotationType('-')->get('levelA-levelC', 'noValue'));
}

public function testHas(){
public function testHas() {
$this->assertTrue($this->_object->has('levelA'));
$this->assertTrue($this->_object->has('levelA.levelB'));
$this->assertTrue($this->_object->has('levelB'));
Expand All @@ -189,4 +224,29 @@ public function testHas(){
$this->assertFalse($this->_object->has('level2'));
}

public function testGetMode() {
$this->assertEquals(ArrayDotNotation::MODE_RETURN_DEFAULT, $this->_object->setGetMode(ArrayDotNotation::MODE_RETURN_DEFAULT)->getGetMode());
$this->assertEquals(ArrayDotNotation::MODE_RETURN_FOUND, $this->_object->setGetMode(ArrayDotNotation::MODE_RETURN_FOUND)->getGetMode());
$this->assertEquals(ArrayDotNotation::MODE_THROW_EXCEPTION, $this->_object->setGetMode(ArrayDotNotation::MODE_THROW_EXCEPTION)->getGetMode());

$this->expectException(Exception\InvalidArgumentException::class);
$this->_object->setGetMode('not a mode');
}

public function testDefaultValue() {
$this->assertEquals('test', $this->_object->setDefaultValue('test')->getDefaultValue(), 'Straight pass through');
$this->assertEquals('test', $this->_object->setDefaultValue('test')->getDefaultValue('key'), 'Key Ignored due to mode');

$this->assertEquals('test', $this->_object->setDefaultValue('test')->setGetMode(ArrayDotNotation::MODE_THROW_EXCEPTION)->getDefaultValue(), 'Mode ignored without key');
$this->assertEquals('test', $this->_object->setDefaultValue('test')->setGetMode(ArrayDotNotation::MODE_RETURN_FOUND)->getDefaultValue(), 'Mode ignored without key');

$this->expectException(PathNotFoundException::class);
$this->_object->setDefaultValue('test')->setGetMode(ArrayDotNotation::MODE_THROW_EXCEPTION)->getDefaultValue('key');
}

public function testDefaultValueOnlyFound() {
$this->expectException(PathNotFoundException::class);
$this->_object->setDefaultValue('test')->setGetMode(ArrayDotNotation::MODE_RETURN_FOUND)->getDefaultValue('key');
}

}

0 comments on commit f1cd126

Please sign in to comment.