From ac904cd4d45e7c97c9014a9ced10917da046f46c Mon Sep 17 00:00:00 2001 From: nate Date: Thu, 5 Apr 2007 05:57:52 +0000 Subject: [PATCH] Adding new Set::merge() and Set class test case, thanks Felix git-svn-id: https://svn.cakephp.org/repo/branches/1.2.x.x@4779 3807eeeb-6ff5-0310-8944-8be069107fe0 --- cake/libs/set.php | 80 +++++++++++++---- cake/tests/cases/libs/set.test.php | 140 +++++++++++++++++++++++++++++ 2 files changed, 202 insertions(+), 18 deletions(-) create mode 100755 cake/tests/cases/libs/set.test.php diff --git a/cake/libs/set.php b/cake/libs/set.php index ecb858358..c0fa0911b 100644 --- a/cake/libs/set.php +++ b/cake/libs/set.php @@ -60,23 +60,67 @@ class Set extends Object { function &get() { return $this->value; } -/** - * Merges the contents of the array object with $array - * - * @param mixed $array An array, another Set object, or a value to be appended - * @return array - * @access public - */ - function merge($array = null, $array2 = null) { - if ($array2 !== null && is_array($array2)) { - return array_merge_recursive($array, $array2); - } - if(!isset($this->value)) { - $this->value = array(); +/** + * This function can be thought of as a hybrid between PHP's array_merge and array_merge_recursive. The difference + * to the two is that if an array key contains another array then the function behaves recursive (unlike array_merge) + * but does not do if for keys containing strings (unlike array_merge_recursive). See the unit test for more information. + * + * Note: This function will work with an unlimited amount of arguments and typecasts non-array parameters into arrays. + * + * @param array $arr1 + * @param array $arr2 + * @return array + */ + function merge($arr1, $arr2 = null) { + // Get all arguments that were passed to the function + $args = func_get_args(); + + // If $this is a Set class + if (is_a($this, 'set')) { + // Get the call stack + $backtrace = debug_backtrace(); + // And the previous call + $previousCall = low($backtrace[1]['class'].'::'.$backtrace[1]['function']); + // If this is not a recursive call + if ($previousCall != 'set::merge') { + // Reference this Set's value as our resulting $r array + $r =& $this->value; + // And push an empty args at the beginning of the $args array which will be discarded later on + array_unshift($args, null); + } } - $this->value = array_merge_recursive($this->value, Set::__array($array)); - return $this->value; + + // If $r has not been set yet + if (!isset($r)) { + // Tpecast the first argument into an array and use it as our resulting $r array + $r = (array)current($args); + } + + // Loop through all $args we were given + while(($arg = next($args)) !== false) { + // If a Set object was passed in + if (is_a($arg, 'set')) { + // Use it's value for the merging + $arg = $arg->get(); + } + // Loop through all $key / $val pairs of our current $arg + foreach ((array)$arg as $key => $val) { + // If the current $key holds an array and the current $arg and all previous ones ($r) + if (is_array($val) && isset($r[$key]) && is_array($r[$key])) { + // Go for recursive merging + $r[$key] = Set::merge($r[$key], $val); + } elseif (is_int($key)) { + // If it's a numerical index go for auto-incremeting + $r[] = $val; + } else { + // And in case of an associative one do an overwrite + $r[$key] = $val; + } + } + } + // Return the merged array + return $r; } /** * Pushes the differences in $array2 onto the end of $array @@ -117,7 +161,7 @@ class Set extends Object { if (is_array($class)) { $val = $class; $class = $tmp; - } elseif (is_a($this, 'set') || is_a($this, 'Set')) { + } elseif (is_a($this, 'set')) { $val = $this->get(); } @@ -129,10 +173,10 @@ class Set extends Object { function __array($array) { if ($array == null) { $array = $this->value; - } elseif (is_object($array) && (is_a($array, 'set') || is_a($array, 'Set'))) { + } elseif (is_object($array) && (is_a($array, 'set'))) { $array = $array->get(); } elseif (is_object($array)) { - // Throw an error + $array = get_object_vars($array); } elseif (!is_array($array)) { $array = array($array); } diff --git a/cake/tests/cases/libs/set.test.php b/cake/tests/cases/libs/set.test.php new file mode 100755 index 000000000..e57a6e06a --- /dev/null +++ b/cake/tests/cases/libs/set.test.php @@ -0,0 +1,140 @@ +assertIdentical($r, array('foo')); + + // Test that passing in a non-array turns it into one + $r = Set::merge('foo'); + $this->assertIdentical($r, array('foo')); + + // Test that this works for 2 strings as well + $r = Set::merge('foo', 'bar'); + $this->assertIdentical($r, array('foo', 'bar')); + + // Test that this works for arguments of mixed types as well + $r = Set::merge('foo', array('user' => 'bob', 'no-bar'), 'bar'); + $this->assertIdentical($r, array('foo', 'user' => 'bob', 'no-bar', 'bar')); + + // Test merging two simple numerical indexed arrays + $a = array('foo', 'foo2'); + $b = array('bar', 'bar2'); + $this->assertIdentical(Set::merge($a, $b), array('foo', 'foo2', 'bar', 'bar2')); + + // Test merging two simple associative arrays + $a = array('foo' => 'bar', 'bar' => 'foo'); + $b = array('foo' => 'no-bar', 'bar' => 'no-foo'); + $this->assertIdentical(Set::merge($a, $b), array('foo' => 'no-bar', 'bar' => 'no-foo')); + + // Test merging two simple nested arrays + $a = array('users' => array('bob', 'jim')); + $b = array('users' => array('lisa', 'tina')); + $this->assertIdentical(Set::merge($a, $b), array( + 'users' => array('bob', 'jim', 'lisa', 'tina') + )); + + // Test that merging an key holding a string over an array one causes an overwrite + $a = array('users' => array('jim', 'bob')); + $b = array('users' => 'none'); + $this->assertIdentical(Set::merge($a, $b), array('users' => 'none')); + + // Test merging two somewhat complex nested arrays + $a = array( + 'users' => array( + 'lisa' => array( + 'id' => 5, + 'pw' => 'secret' + ) + ), + 'cakephp' + ); + $b = array( + 'users' => array( + 'lisa' => array( + 'pw' => 'new-pass', + 'age' => 23 + ) + ), + 'ice-cream' + ); + $this->assertIdentical(Set::merge($a, $b), array( + 'users' => array( + 'lisa' => array( + 'id' => 5, + 'pw' => 'new-pass', + 'age' => 23 + ) + ), + 'cakephp', + 'ice-cream' + )); + + // And now go for the ultimate tripple-play ; ) + $c = array( + 'users' => array( + 'lisa' => array( + 'pw' => 'you-will-never-guess', + 'age' => 25, + 'pet' => 'dog' + ) + ), + 'chocolate' + ); + $expected = array( + 'users' => array( + 'lisa' => array( + 'id' => 5, + 'pw' => 'you-will-never-guess', + 'age' => 25, + 'pet' => 'dog' + ) + ), + 'cakephp', + 'ice-cream', + 'chocolate' + ); + $this->assertIdentical(Set::merge($a, $b, $c), $expected); + + // Test that passing in an empty array does not mess things up + $this->assertIdentical(Set::merge($a, $b, array(), $c), $expected); + + // Create a new Set instance from the $a array + $Set =& new Set($a); + // Merge $b, an empty array and $c over it + $r = $Set->merge($b, array(), $c); + // And test that it produces the same result as a static call would + $this->assertIdentical($r, $expected); + // And also updates it's own value property + $this->assertIdentical($Set->value, $expected); + + // Let the garbage collector eat the Set instance + unset($Set); + + $Set =& new Set(); + + $SetA =& new Set($a); + $SetB =& new Set($b); + $SetC =& new Set($c); + + $r = $Set->merge($SetA, $SetB, $SetC); + // And test that it produces the same result as a static call would + $this->assertIdentical($r, $expected); + // And also updates it's own value property + $this->assertIdentical($Set->value, $expected); + } +} + +?> \ No newline at end of file