Permalink
Browse files

Add remove() and insert()

Also add support for multi insert and multi remove.
  • Loading branch information...
1 parent 6b69ed2 commit 3d8a955043936e3604e381b728c0aa1deec84b02 @markstory markstory committed Jan 27, 2012
Showing with 185 additions and 35 deletions.
  1. +92 −6 lib/Cake/Test/Case/Utility/Set2Test.php
  2. +93 −29 lib/Cake/Utility/Set2.php
@@ -974,21 +974,107 @@ public function testSortWithOutOfOrderKeys() {
$this->assertEquals($expected, $result);
}
+/**
+ * Test insert()
+ *
+ * @return void
+ */
+ public function testInsertSimple() {
+ $a = array(
+ 'pages' => array('name' => 'page')
+ );
+ $result = Set2::insert($a, 'files', array('name' => 'files'));
+ $expected = array(
+ 'pages' => array('name' => 'page'),
+ 'files' => array('name' => 'files')
+ );
+ $this->assertEquals($expected, $result);
+
+ $a = array(
+ 'pages' => array('name' => 'page')
+ );
+ $result = Set2::insert($a, 'pages.name', array());
+ $expected = array(
+ 'pages' => array('name' => array()),
+ );
+ $this->assertEquals($expected, $result);
+ }
/**
- * Test remove()
+ * Test inserting with multiple values.
+ *
+ * @return void
+ */
+ public function testInsertMulti() {
+ $data = self::articleData();
+
+ $result = Set2::insert($data, '{n}.Article.insert', 'value');
+ $this->assertEquals('value', $result[0]['Article']['insert']);
+ $this->assertEquals('value', $result[1]['Article']['insert']);
+
+ $result = Set2::insert($data, '{n}.Comment.{n}.insert', 'value');
+ $this->assertEquals('value', $result[0]['Comment'][0]['insert']);
+ $this->assertEquals('value', $result[0]['Comment'][1]['insert']);
+ }
+
+/**
+ * Test remove() method.
*
* @return void
*/
public function testRemove() {
+ $a = array(
+ 'pages' => array('name' => 'page'),
+ 'files' => array('name' => 'files')
+ );
+
+ $result = Set2::remove($a, 'files');
+ $expected = array(
+ 'pages' => array('name' => 'page')
+ );
+ $this->assertEquals($expected, $result);
+
+ $a = array(
+ 'pages' => array(
+ 0 => array('name' => 'main'),
+ 1 => array(
+ 'name' => 'about',
+ 'vars' => array('title' => 'page title')
+ )
+ )
+ );
+
+ $result = Set2::remove($a, 'pages.1.vars');
+ $expected = array(
+ 'pages' => array(
+ 0 => array('name' => 'main'),
+ 1 => array('name' => 'about')
+ )
+ );
+ $this->assertEquals($expected, $result);
+
+ $result = Set2::remove($a, 'pages.2.vars');
+ $expected = $a;
+ $this->assertEquals($expected, $result);
+ }
+
+/**
+ * Test removing multiple values.
+ *
+ * @return void
+ */
+ public function testRemoveMulti() {
$data = self::articleData();
- $result = Set2::insert($data, '{n}.Article', array('test'));
- debug($result);
+ $result = Set2::remove($data, '{n}.Article.title');
+ $this->assertFalse(isset($result[0]['Article']['title']));
+ $this->assertFalse(isset($result[1]['Article']['title']));
- $result = Set2::remove($data, '{n}.Article');
- debug($result);
- $this->assertFalse(isset($data[0]['Article']));
+ $result = Set2::remove($data, '{n}.Article.{s}');
+ $this->assertFalse(isset($result[0]['Article']['id']));
+ $this->assertFalse(isset($result[0]['Article']['user_id']));
+ $this->assertFalse(isset($result[0]['Article']['title']));
+ $this->assertFalse(isset($result[0]['Article']['body']));
}
}
View
@@ -94,23 +94,6 @@ public static function extract(array $data, $path) {
return (array) self::get($data, $path);
}
- return self::_traverse($data, $path, function ($value) {
- return $value;
- });
- }
-
-/**
- * Traverses $data for $path. $callback is called for each terminal element.
- * The results of all the callbacks are returned.
- *
- * @param array $data The data to traverse.
- * @param string $path The set path to walk.
- * @param callable $callback to call on the result set.
- * @return array Results of the callback mapped over the leaf nodes of the path expression.
- */
- protected static function _traverse(array &$data, $path, $callback) {
- $result = array();
-
if (strpos('[', $path) === false) {
$tokens = explode('.', $path);
} else {
@@ -150,12 +133,10 @@ protected static function _traverse(array &$data, $path, $callback) {
}
$next = $filter;
}
-
$context = array($_key => $next);
} while (!empty($tokens));
-
- return array_map($callback, $context[$_key]);
+ return $context[$_key];
}
/**
@@ -234,16 +215,58 @@ protected static function _matches(array $data, $selector) {
return true;
}
+/**
+ * Insert $values into an array with the given $path.
+ *
+ * @param array $data The data to insert into.
+ * @param string $path The path to insert at.
+ * @param mixed $values The values to insert.
+ * @return array The data with $values inserted.
+ */
public static function insert(array $data, $path, $values = null) {
- if (empty($path)) {
- return $data;
+ $tokens = explode('.', $path);
+ if (strpos($path, '{') === false) {
+ return self::_simpleInsert($data, $tokens, $values);
+ }
+
+ $token = array_shift($tokens);
+ $nextPath = implode('.', $tokens);
+ foreach ($data as $k => $v) {
+ if (self::_matchToken($k, $token)) {
+ $data[$k] = self::insert($v, $nextPath, $values);
+ }
}
+ return $data;
+ }
- $result = self::_traverse($data, $path, function (&$value) use ($values) {
- $value['test'] = $values;
- return $value;
- });
+/**
+ * Inserts values into simple paths.
+ *
+ * @param array $data Data to insert into.
+ * @param string $path The path to insert into.
+ * @param mixed $values The values to insert.
+ * @return array Data with values inserted at $path.
+ */
+ protected static function _simpleInsert($data, $path, $values) {
+ $_list =& $data;
+ $count = count($path);
+ foreach ($path as $i => $key) {
+ if (is_numeric($key) && intval($key) > 0 || $key === '0') {
+ $key = intval($key);
+ }
+ if ($i === $count - 1 && is_array($_list)) {
+ $_list[$key] = $values;
+ } else {
+ if (!isset($_list[$key])) {
+ $_list[$key] = array();
+ }
+ $_list =& $_list[$key];
+ }
+ if (!is_array($_list)) {
+ return array();
+ }
+ }
return $data;
}
@@ -255,13 +278,54 @@ public static function insert(array $data, $path, $values = null) {
* @return array The modified array.
*/
public static function remove(array $data, $path) {
+ $tokens = explode('.', $path);
+ if (strpos($path, '{') === false) {
+ return self::_simpleRemove($data, $path);
+ }
+
+ $token = array_shift($tokens);
+ $nextPath = implode('.', $tokens);
+ foreach ($data as $k => $v) {
+ $match = self::_matchToken($k, $token);
+ if ($match && is_array($v)) {
+ $data[$k] = self::remove($v, $nextPath);
+ } elseif ($match) {
+ unset($data[$k]);
+ }
+ }
+ return $data;
+ }
+
+/**
+ * Remove values along a simple path.
+ *
+ * @param array $data Array to operate on.
+ * @param string $path The path to remove.
+ * @return array Data with value removed.
+ */
+ protected static function _simpleRemove($data, $path) {
if (empty($path)) {
return $data;
}
+ if (!is_array($path)) {
+ $path = explode('.', $path);
+ }
+ $_list =& $data;
- return self::_traverse($data, $path, function ($value) {
- return $value;
- });
+ foreach ($path as $i => $key) {
+ if (is_numeric($key) && intval($key) > 0 || $key === '0') {
+ $key = intval($key);
+ }
+ if ($i === count($path) - 1) {
+ unset($_list[$key]);
+ } else {
+ if (!isset($_list[$key])) {
+ return $data;
+ }
+ $_list =& $_list[$key];
+ }
+ }
+ return $data;
}
public static function combine(array $data, $keyPath, $valuePath = null) {

0 comments on commit 3d8a955

Please sign in to comment.