Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added wildcard to GET resolves: #40 #45

Merged
merged 1 commit into from
Dec 8, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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');
}

}