From 3d8a955043936e3604e381b728c0aa1deec84b02 Mon Sep 17 00:00:00 2001 From: mark_story Date: Thu, 26 Jan 2012 21:33:03 -0500 Subject: [PATCH] Add remove() and insert() Also add support for multi insert and multi remove. --- lib/Cake/Test/Case/Utility/Set2Test.php | 98 +++++++++++++++++-- lib/Cake/Utility/Set2.php | 122 ++++++++++++++++++------ 2 files changed, 185 insertions(+), 35 deletions(-) diff --git a/lib/Cake/Test/Case/Utility/Set2Test.php b/lib/Cake/Test/Case/Utility/Set2Test.php index ed4ab8829c7..13250804177 100644 --- a/lib/Cake/Test/Case/Utility/Set2Test.php +++ b/lib/Cake/Test/Case/Utility/Set2Test.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'])); } } diff --git a/lib/Cake/Utility/Set2.php b/lib/Cake/Utility/Set2.php index 59a3feef03b..9de8d9c3ea4 100644 --- a/lib/Cake/Utility/Set2.php +++ b/lib/Cake/Utility/Set2.php @@ -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) {