From a217556c13ab57ca15df749ae4da02bf7ac4379e Mon Sep 17 00:00:00 2001 From: Adrian Gunawan Date: Wed, 12 Aug 2015 14:30:08 +1000 Subject: [PATCH 1/8] Ability for Hash::sort to sort case-insensitively --- lib/Cake/Test/Case/Utility/HashTest.php | 83 ++++++++++++++++++++++++- lib/Cake/Utility/Hash.php | 21 +++++-- 2 files changed, 99 insertions(+), 5 deletions(-) diff --git a/lib/Cake/Test/Case/Utility/HashTest.php b/lib/Cake/Test/Case/Utility/HashTest.php index 0f8b8b540..51841e1f8 100644 --- a/lib/Cake/Test/Case/Utility/HashTest.php +++ b/lib/Cake/Test/Case/Utility/HashTest.php @@ -1301,6 +1301,43 @@ class HashTest extends CakeTestCase { $this->assertEquals($expected, $result); } +/** + * Test natural sorting ignoring case. + * + * @return void + */ + public function testSortNaturalIgnoreCase() { + if (version_compare(PHP_VERSION, '5.4.0', '<')) { + $this->markTestSkipped('SORT_NATURAL is available since PHP 5.4.'); + } + $items = array( + array('Item' => array('image' => 'img1.jpg')), + array('Item' => array('image' => 'img99.jpg')), + array('Item' => array('image' => 'Img12.jpg')), + array('Item' => array('image' => 'Img10.jpg')), + array('Item' => array('image' => 'img2.jpg')), + ); + $result = Hash::sort($items, '{n}.Item.image', 'desc', 'natural', true); + $expected = array( + array('Item' => array('image' => 'img99.jpg')), + array('Item' => array('image' => 'Img12.jpg')), + array('Item' => array('image' => 'Img10.jpg')), + array('Item' => array('image' => 'img2.jpg')), + array('Item' => array('image' => 'img1.jpg')), + ); + $this->assertEquals($expected, $result); + + $result = Hash::sort($items, '{n}.Item.image', 'asc', 'natural', true); + $expected = array( + array('Item' => array('image' => 'img1.jpg')), + array('Item' => array('image' => 'img2.jpg')), + array('Item' => array('image' => 'Img10.jpg')), + array('Item' => array('image' => 'Img12.jpg')), + array('Item' => array('image' => 'img99.jpg')), + ); + $this->assertEquals($expected, $result); + } + /** * Test that sort() with 'natural' type will fallback to 'regular' as SORT_NATURAL is introduced in PHP 5.4 * @@ -1355,7 +1392,7 @@ class HashTest extends CakeTestCase { * * @return void */ - public function testSortString() { + public function testSortStringKeys() { $toSort = array( 'four' => array('number' => 4, 'some' => 'foursome'), 'six' => array('number' => 6, 'some' => 'sixsome'), @@ -1387,6 +1424,50 @@ class HashTest extends CakeTestCase { $this->assertEquals($expected, $result); } +/** + * test sorting with string ignoring case. + * + * @return void + */ + public function testSortStringIgnoreCase() { + $toSort = array( + array('Item' => array('name' => 'bar')), + array('Item' => array('name' => 'Baby')), + array('Item' => array('name' => 'Baz')), + array('Item' => array('name' => 'bat')), + ); + $sorted = Hash::sort($toSort, '{n}.Item.name', 'asc', 'string', true); + $expected = array( + array('Item' => array('name' => 'Baby')), + array('Item' => array('name' => 'bar')), + array('Item' => array('name' => 'bat')), + array('Item' => array('name' => 'Baz')), + ); + $this->assertEquals($expected, $sorted); + } + +/** + * test regular sorting ignoring case. + * + * @return void + */ + public function testSortRegularIgnoreCase() { + $toSort = array( + array('Item' => array('name' => 'bar')), + array('Item' => array('name' => 'Baby')), + array('Item' => array('name' => 'Baz')), + array('Item' => array('name' => 'bat')), + ); + $sorted = Hash::sort($toSort, '{n}.Item.name', 'asc', 'regular', true); + $expected = array( + array('Item' => array('name' => 'Baby')), + array('Item' => array('name' => 'bar')), + array('Item' => array('name' => 'bat')), + array('Item' => array('name' => 'Baz')), + ); + $this->assertEquals($expected, $sorted); + } + /** * Test insert() * diff --git a/lib/Cake/Utility/Hash.php b/lib/Cake/Utility/Hash.php index ad06cf95b..c14fe5c5b 100644 --- a/lib/Cake/Utility/Hash.php +++ b/lib/Cake/Utility/Hash.php @@ -843,10 +843,11 @@ class Hash { * @param string $path A Set-compatible path to the array value * @param string $dir See directions above. Defaults to 'asc'. * @param string $type See direction types above. Defaults to 'regular'. + * @param string $ignoreCase Case insensitive sorting. Defaults to false. * @return array Sorted array of data * @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::sort */ - public static function sort(array $data, $path, $dir = 'asc', $type = 'regular') { + public static function sort(array $data, $path, $dir = 'asc', $type = 'regular', $ignoreCase = false) { if (empty($data)) { return array(); } @@ -870,9 +871,16 @@ class Hash { $dir = strtolower($dir); $type = strtolower($type); - if ($type === 'natural' && version_compare(PHP_VERSION, '5.4.0', '<')) { - $type = 'regular'; + + // Natural and case insensitive sort is only supported from >= 5.4.0 + if (version_compare(PHP_VERSION, '5.4.0', '<')) { + if ($type === 'natural' || $type === 'natural_ignore_case' || $type === 'regular_ignore_case') { + $type = 'regular'; + } elseif ($type == 'string_ignore_case') { + $type = 'string'; + } } + if ($dir === 'asc') { $dir = SORT_ASC; } else { @@ -887,7 +895,12 @@ class Hash { } else { $type = SORT_REGULAR; } - array_multisort($values, $dir, $type, $keys, $dir, $type); + + if ($ignoreCase) { + $values = array_map('strtolower', $values); + } + array_multisort($values, $dir, $type, $keys, $dir); + $sorted = array(); $keys = array_unique($keys); From f23e6589d0e4a318d3f1da873b034eab010379c8 Mon Sep 17 00:00:00 2001 From: Adrian Gunawan Date: Thu, 13 Aug 2015 11:14:08 +1000 Subject: [PATCH 2/8] Overload $type parameter instead of adding another parameter for case insensitive sort --- lib/Cake/Test/Case/Utility/HashTest.php | 19 +++++++++++--- lib/Cake/Utility/Hash.php | 35 ++++++++++++++++++------- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/lib/Cake/Test/Case/Utility/HashTest.php b/lib/Cake/Test/Case/Utility/HashTest.php index 51841e1f8..64858bfbb 100644 --- a/lib/Cake/Test/Case/Utility/HashTest.php +++ b/lib/Cake/Test/Case/Utility/HashTest.php @@ -1317,7 +1317,7 @@ class HashTest extends CakeTestCase { array('Item' => array('image' => 'Img10.jpg')), array('Item' => array('image' => 'img2.jpg')), ); - $result = Hash::sort($items, '{n}.Item.image', 'desc', 'natural', true); + $result = Hash::sort($items, '{n}.Item.image', 'desc', ['type' => 'natural', 'ignoreCase' => true]); $expected = array( array('Item' => array('image' => 'img99.jpg')), array('Item' => array('image' => 'Img12.jpg')), @@ -1327,7 +1327,7 @@ class HashTest extends CakeTestCase { ); $this->assertEquals($expected, $result); - $result = Hash::sort($items, '{n}.Item.image', 'asc', 'natural', true); + $result = Hash::sort($items, '{n}.Item.image', 'asc', ['type' => 'natural', 'ignoreCase' => true]); $expected = array( array('Item' => array('image' => 'img1.jpg')), array('Item' => array('image' => 'img2.jpg')), @@ -1436,7 +1436,7 @@ class HashTest extends CakeTestCase { array('Item' => array('name' => 'Baz')), array('Item' => array('name' => 'bat')), ); - $sorted = Hash::sort($toSort, '{n}.Item.name', 'asc', 'string', true); + $sorted = Hash::sort($toSort, '{n}.Item.name', 'asc', ['type' => 'string', 'ignoreCase' => true]); $expected = array( array('Item' => array('name' => 'Baby')), array('Item' => array('name' => 'bar')), @@ -1458,7 +1458,7 @@ class HashTest extends CakeTestCase { array('Item' => array('name' => 'Baz')), array('Item' => array('name' => 'bat')), ); - $sorted = Hash::sort($toSort, '{n}.Item.name', 'asc', 'regular', true); + $sorted = Hash::sort($toSort, '{n}.Item.name', 'asc', ['type' => 'regular', 'ignoreCase' => true]); $expected = array( array('Item' => array('name' => 'Baby')), array('Item' => array('name' => 'bar')), @@ -1468,6 +1468,17 @@ class HashTest extends CakeTestCase { $this->assertEquals($expected, $sorted); } +/** + * Tests that sort() throws an InvalidArgumentException when providing an invalid input. + * + * @expectedException InvalidArgumentException + * @return void + */ + public function testSortInvalidType() { + $toSort = ['a', 'b', 'c']; + Hash::sort($toSort, '{n}', 'asc', ['regular'], true); + } + /** * Test insert() * diff --git a/lib/Cake/Utility/Hash.php b/lib/Cake/Utility/Hash.php index c14fe5c5b..6cf14a01e 100644 --- a/lib/Cake/Utility/Hash.php +++ b/lib/Cake/Utility/Hash.php @@ -838,16 +838,18 @@ class Hash { * - `string` Compare values as strings * - `natural` Compare items as strings using "natural ordering" in a human friendly way. * Will sort foo10 below foo2 as an example. Requires PHP 5.4 or greater or it will fallback to 'regular' + * To do case insensitive sorting, pass the type as an array as follows: + * ['type' => 'regular', 'ignoreCase' => true] * * @param array $data An array of data to sort * @param string $path A Set-compatible path to the array value * @param string $dir See directions above. Defaults to 'asc'. - * @param string $type See direction types above. Defaults to 'regular'. - * @param string $ignoreCase Case insensitive sorting. Defaults to false. + * @param mixed $type See direction types above. Defaults to 'regular'. * @return array Sorted array of data * @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::sort + * @throws InvalidArgumentException */ - public static function sort(array $data, $path, $dir = 'asc', $type = 'regular', $ignoreCase = false) { + public static function sort(array $data, $path, $dir = 'asc', $type = 'regular') { if (empty($data)) { return array(); } @@ -870,15 +872,28 @@ class Hash { $values = static::extract($result, '{n}.value'); $dir = strtolower($dir); - $type = strtolower($type); + $ignoreCase = false; + + // $type can be overloaded for case insensitive sort + if (is_array($type)) { - // Natural and case insensitive sort is only supported from >= 5.4.0 - if (version_compare(PHP_VERSION, '5.4.0', '<')) { - if ($type === 'natural' || $type === 'natural_ignore_case' || $type === 'regular_ignore_case') { - $type = 'regular'; - } elseif ($type == 'string_ignore_case') { - $type = 'string'; + if (!empty($type['ignoreCase'])) { + $ignoreCase = $type['ignoreCase']; } + + if (!empty($type['ignoreCase'])) { + $type = $type['type']; + } else { + throw new InvalidArgumentException(__d('cake_dev', + 'Invalid parameter $type. It requires type key to be specified.' + )); + } + } else { + $type = strtolower($type); + } + + if (version_compare(PHP_VERSION, '5.4.0', '<')) { + $type = 'regular'; } if ($dir === 'asc') { From b89d8d5efa3e22f79480fb2c08105d40661b7d4d Mon Sep 17 00:00:00 2001 From: Adrian Gunawan Date: Fri, 14 Aug 2015 14:15:00 +1000 Subject: [PATCH 3/8] Use array() instead of the short notation [] --- lib/Cake/Test/Case/Utility/HashTest.php | 8 ++++---- lib/Cake/Utility/Hash.php | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/Cake/Test/Case/Utility/HashTest.php b/lib/Cake/Test/Case/Utility/HashTest.php index 64858bfbb..03695df30 100644 --- a/lib/Cake/Test/Case/Utility/HashTest.php +++ b/lib/Cake/Test/Case/Utility/HashTest.php @@ -1317,7 +1317,7 @@ class HashTest extends CakeTestCase { array('Item' => array('image' => 'Img10.jpg')), array('Item' => array('image' => 'img2.jpg')), ); - $result = Hash::sort($items, '{n}.Item.image', 'desc', ['type' => 'natural', 'ignoreCase' => true]); + $result = Hash::sort($items, '{n}.Item.image', 'desc', array('type' => 'natural', 'ignoreCase' => true)); $expected = array( array('Item' => array('image' => 'img99.jpg')), array('Item' => array('image' => 'Img12.jpg')), @@ -1327,7 +1327,7 @@ class HashTest extends CakeTestCase { ); $this->assertEquals($expected, $result); - $result = Hash::sort($items, '{n}.Item.image', 'asc', ['type' => 'natural', 'ignoreCase' => true]); + $result = Hash::sort($items, '{n}.Item.image', 'asc', array('type' => 'natural', 'ignoreCase' => true)); $expected = array( array('Item' => array('image' => 'img1.jpg')), array('Item' => array('image' => 'img2.jpg')), @@ -1436,7 +1436,7 @@ class HashTest extends CakeTestCase { array('Item' => array('name' => 'Baz')), array('Item' => array('name' => 'bat')), ); - $sorted = Hash::sort($toSort, '{n}.Item.name', 'asc', ['type' => 'string', 'ignoreCase' => true]); + $sorted = Hash::sort($toSort, '{n}.Item.name', 'asc', array('type' => 'string', 'ignoreCase' => true)); $expected = array( array('Item' => array('name' => 'Baby')), array('Item' => array('name' => 'bar')), @@ -1458,7 +1458,7 @@ class HashTest extends CakeTestCase { array('Item' => array('name' => 'Baz')), array('Item' => array('name' => 'bat')), ); - $sorted = Hash::sort($toSort, '{n}.Item.name', 'asc', ['type' => 'regular', 'ignoreCase' => true]); + $sorted = Hash::sort($toSort, '{n}.Item.name', 'asc', array('type' => 'regular', 'ignoreCase' => true)); $expected = array( array('Item' => array('name' => 'Baby')), array('Item' => array('name' => 'bar')), diff --git a/lib/Cake/Utility/Hash.php b/lib/Cake/Utility/Hash.php index 6cf14a01e..1c1899e48 100644 --- a/lib/Cake/Utility/Hash.php +++ b/lib/Cake/Utility/Hash.php @@ -839,7 +839,7 @@ class Hash { * - `natural` Compare items as strings using "natural ordering" in a human friendly way. * Will sort foo10 below foo2 as an example. Requires PHP 5.4 or greater or it will fallback to 'regular' * To do case insensitive sorting, pass the type as an array as follows: - * ['type' => 'regular', 'ignoreCase' => true] + * array('type' => 'regular', 'ignoreCase' => true) * * @param array $data An array of data to sort * @param string $path A Set-compatible path to the array value From bf6574c3b23e787f069f79080ae578c6013fd05b Mon Sep 17 00:00:00 2001 From: Adrian Gunawan Date: Fri, 14 Aug 2015 14:26:01 +1000 Subject: [PATCH 4/8] Use array() instead of the short notation [] --- lib/Cake/Test/Case/Utility/HashTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Cake/Test/Case/Utility/HashTest.php b/lib/Cake/Test/Case/Utility/HashTest.php index 03695df30..f6216ff07 100644 --- a/lib/Cake/Test/Case/Utility/HashTest.php +++ b/lib/Cake/Test/Case/Utility/HashTest.php @@ -1475,7 +1475,7 @@ class HashTest extends CakeTestCase { * @return void */ public function testSortInvalidType() { - $toSort = ['a', 'b', 'c']; + $toSort = array('a', 'b', 'c'); Hash::sort($toSort, '{n}', 'asc', ['regular'], true); } From 2eafcc0f728e26293be2704802ed18994628eeb2 Mon Sep 17 00:00:00 2001 From: Adrian Gunawan Date: Fri, 14 Aug 2015 14:55:44 +1000 Subject: [PATCH 5/8] Use array() instead of the short notation [] --- lib/Cake/Test/Case/Utility/HashTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Cake/Test/Case/Utility/HashTest.php b/lib/Cake/Test/Case/Utility/HashTest.php index f6216ff07..af14529e3 100644 --- a/lib/Cake/Test/Case/Utility/HashTest.php +++ b/lib/Cake/Test/Case/Utility/HashTest.php @@ -1476,7 +1476,7 @@ class HashTest extends CakeTestCase { */ public function testSortInvalidType() { $toSort = array('a', 'b', 'c'); - Hash::sort($toSort, '{n}', 'asc', ['regular'], true); + Hash::sort($toSort, '{n}', 'asc', array('regular'), true); } /** From 80f6a97d93c3b701456e3d5fb5c3e6304e980362 Mon Sep 17 00:00:00 2001 From: Adrian Gunawan Date: Fri, 14 Aug 2015 15:32:23 +1000 Subject: [PATCH 6/8] Check === 'natural' was inadvertently removed --- lib/Cake/Utility/Hash.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Cake/Utility/Hash.php b/lib/Cake/Utility/Hash.php index 1c1899e48..02d8d579c 100644 --- a/lib/Cake/Utility/Hash.php +++ b/lib/Cake/Utility/Hash.php @@ -892,7 +892,7 @@ class Hash { $type = strtolower($type); } - if (version_compare(PHP_VERSION, '5.4.0', '<')) { + if ($type === 'natural' && version_compare(PHP_VERSION, '5.4.0', '<')) { $type = 'regular'; } From 12e5719aadf0a8de95a512f8229b65cd80edbfd5 Mon Sep 17 00:00:00 2001 From: Adrian Gunawan Date: Fri, 14 Aug 2015 17:05:59 +1000 Subject: [PATCH 7/8] Remove whitespace at end of line --- lib/Cake/Utility/Hash.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Cake/Utility/Hash.php b/lib/Cake/Utility/Hash.php index 02d8d579c..3df90bcc0 100644 --- a/lib/Cake/Utility/Hash.php +++ b/lib/Cake/Utility/Hash.php @@ -1127,4 +1127,4 @@ class Hash { return array_values($return); } -} +} \ No newline at end of file From a9ef1f8aeaa864655e42a80bbfac093a5a01cff9 Mon Sep 17 00:00:00 2001 From: mark_story Date: Tue, 25 Aug 2015 21:39:02 -0400 Subject: [PATCH 8/8] Simplify branching and add default options. Use fewer conditionals by merging defaults and avoid exceptions by setting defaults as well. Refs #7217 --- lib/Cake/Test/Case/Utility/HashTest.php | 11 --------- lib/Cake/Utility/Hash.php | 31 +++++++++++-------------- 2 files changed, 14 insertions(+), 28 deletions(-) diff --git a/lib/Cake/Test/Case/Utility/HashTest.php b/lib/Cake/Test/Case/Utility/HashTest.php index af14529e3..cb97f60e2 100644 --- a/lib/Cake/Test/Case/Utility/HashTest.php +++ b/lib/Cake/Test/Case/Utility/HashTest.php @@ -1468,17 +1468,6 @@ class HashTest extends CakeTestCase { $this->assertEquals($expected, $sorted); } -/** - * Tests that sort() throws an InvalidArgumentException when providing an invalid input. - * - * @expectedException InvalidArgumentException - * @return void - */ - public function testSortInvalidType() { - $toSort = array('a', 'b', 'c'); - Hash::sort($toSort, '{n}', 'asc', array('regular'), true); - } - /** * Test insert() * diff --git a/lib/Cake/Utility/Hash.php b/lib/Cake/Utility/Hash.php index 3df90bcc0..a5ccf0ab2 100644 --- a/lib/Cake/Utility/Hash.php +++ b/lib/Cake/Utility/Hash.php @@ -838,16 +838,22 @@ class Hash { * - `string` Compare values as strings * - `natural` Compare items as strings using "natural ordering" in a human friendly way. * Will sort foo10 below foo2 as an example. Requires PHP 5.4 or greater or it will fallback to 'regular' + * * To do case insensitive sorting, pass the type as an array as follows: - * array('type' => 'regular', 'ignoreCase' => true) + * + * ``` + * array('type' => 'regular', 'ignoreCase' => true) + * ``` + * + * When using the array form, `type` defaults to 'regular'. The `ignoreCase` option + * defaults to `false`. * * @param array $data An array of data to sort * @param string $path A Set-compatible path to the array value * @param string $dir See directions above. Defaults to 'asc'. - * @param mixed $type See direction types above. Defaults to 'regular'. + * @param array|string $type See direction types above. Defaults to 'regular'. * @return array Sorted array of data * @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::sort - * @throws InvalidArgumentException */ public static function sort(array $data, $path, $dir = 'asc', $type = 'regular') { if (empty($data)) { @@ -873,21 +879,12 @@ class Hash { $dir = strtolower($dir); $ignoreCase = false; - + // $type can be overloaded for case insensitive sort if (is_array($type)) { - - if (!empty($type['ignoreCase'])) { - $ignoreCase = $type['ignoreCase']; - } - - if (!empty($type['ignoreCase'])) { - $type = $type['type']; - } else { - throw new InvalidArgumentException(__d('cake_dev', - 'Invalid parameter $type. It requires type key to be specified.' - )); - } + $type += array('ignoreCase' => false, 'type' => 'regular'); + $ignoreCase = $type['ignoreCase']; + $type = $type['type']; } else { $type = strtolower($type); } @@ -1127,4 +1124,4 @@ class Hash { return array_values($return); } -} \ No newline at end of file +}