From f6fe1427c286fa5fc58b83cd10d84bf5930889e2 Mon Sep 17 00:00:00 2001 From: Daniel Hensby Date: Fri, 28 Aug 2015 15:56:54 +0100 Subject: [PATCH] API Making ArrayList (and others) more consistent with DataList --- model/ArrayList.php | 122 ++++++++++++++++++++++++---------- model/ListDecorator.php | 46 +++++++++++++ model/UnsavedRelationList.php | 2 +- tests/model/ArrayListTest.php | 81 ++++++++++++++++++++++ tests/model/DataListTest.php | 10 +++ 5 files changed, 224 insertions(+), 37 deletions(-) diff --git a/model/ArrayList.php b/model/ArrayList.php index c9e428546..f17d5b462 100644 --- a/model/ArrayList.php +++ b/model/ArrayList.php @@ -56,7 +56,7 @@ class ArrayList extends ViewableData implements SS_List, SS_Filterable, SS_Sorta * @return bool */ public function exists() { - return (bool) count($this); + return !empty($this->items); } /** @@ -477,6 +477,79 @@ class ArrayList extends ViewableData implements SS_List, SS_Filterable, SS_Sorta * // aziz with the age 21 or 43 and bob with the Age 21 or 43 */ public function filter() { + + $keepUs = call_user_func_array(array($this, 'normaliseFilterArgs'), func_get_args()); + + $itemsToKeep = array(); + foreach($this->items as $item){ + $keepItem = true; + foreach ($keepUs as $column => $value) { + if ((is_array($value) && !in_array($this->extractValue($item, $column), $value)) + || (!is_array($value) && $this->extractValue($item, $column) != $value) + ) { + $keepItem = false; + break; + } + } + if($keepItem) { + $itemsToKeep[] = $item; + } + } + + $list = clone $this; + $list->items = $itemsToKeep; + return $list; + } + + /** + * Return a copy of this list which contains items matching any of these charactaristics. + * + * @example // only bob in the list + * $list = $list->filterAny('Name', 'bob'); + * @example // azis or bob in the list + * $list = $list->filterAny('Name', array('aziz', 'bob'); + * @example // bob or anyone aged 21 in the list + * $list = $list->filterAny(array('Name'=>'bob, 'Age'=>21)); + * @example // bob or anyone aged 21 or 43 in the list + * $list = $list->filterAny(array('Name'=>'bob, 'Age'=>array(21, 43))); + * @example // all bobs, phils or anyone aged 21 or 43 in the list + * $list = $list->filterAny(array('Name'=>array('bob','phil'), 'Age'=>array(21, 43))); + * + * @param string|array See {@link filter()} + * @return DataList + */ + public function filterAny() { + $keepUs = call_user_func_array(array($this, 'normaliseFilterArgs'), func_get_args()); + + $itemsToKeep = array(); + + foreach ($this->items as $item) { + foreach ($keepUs as $column => $value) { + $extractedValue = $this->extractValue($item, $column); + $matches = is_array($value) ? in_array($extractedValue, $value) : $extractedValue == $value; + if ($matches) { + $itemsToKeep[] = $item; + break; + } + } + } + + $list = clone $this; + $list->items = array_unique($itemsToKeep, SORT_REGULAR); + return $list; + + } + + /** + * Take the "standard" arguments that the filter/exclude functions take and return a single array with + * 'colum' => 'value' + * + * @param $column array|string The column name to filter OR an assosicative array of column => value + * @param $value array|string|null The values to filter the $column against + * + * @return array The normalised keyed array + */ + protected function normaliseFilterArgs($column, $value = null) { if(count(func_get_args())>2){ throw new InvalidArgumentException('filter takes one array or two arguments'); } @@ -496,24 +569,18 @@ class ArrayList extends ViewableData implements SS_List, SS_Filterable, SS_Sorta } } - $itemsToKeep = array(); - foreach($this->items as $item){ - $keepItem = true; - foreach($keepUs as $column => $value ) { - if(is_array($value) && !in_array($this->extractValue($item, $column), $value)) { - $keepItem = false; - } elseif(!is_array($value) && $this->extractValue($item, $column) != $value) { - $keepItem = false; - } - } - if($keepItem) { - $itemsToKeep[] = $item; - } - } + return $keepUs; + } - $list = clone $this; - $list->items = $itemsToKeep; - return $list; + /** + * Filter this list to only contain the given Primary IDs + * + * @param array $ids Array of integers, will be automatically cast/escaped. + * @return ArrayList + */ + public function byIDs($ids) { + $ids = array_map('intval', $ids); // sanitize + return $this->filter('ID', $ids); } public function byID($id) { @@ -563,25 +630,8 @@ class ArrayList extends ViewableData implements SS_List, SS_Filterable, SS_Sorta * // bob age 21 or 43, phil age 21 or 43 would be excluded */ public function exclude() { - if(count(func_get_args())>2){ - throw new InvalidArgumentException('exclude() takes one array or two arguments'); - } - - if(count(func_get_args()) == 1 && !is_array(func_get_arg(0))){ - throw new InvalidArgumentException('exclude() takes one array or two arguments'); - } - - $removeUs = array(); - if(count(func_get_args())==2){ - $removeUs[func_get_arg(0)] = func_get_arg(1); - } - - if(count(func_get_args())==1 && is_array(func_get_arg(0))){ - foreach(func_get_arg(0) as $column => $excludeValue) { - $removeUs[$column] = $excludeValue; - } - } + $removeUs = call_user_func_array(array($this, 'normaliseFilterArgs'), func_get_args()); $hitsRequiredToRemove = count($removeUs); $matches = array(); diff --git a/model/ListDecorator.php b/model/ListDecorator.php index 83bf7ad35..5ec1d8e88 100644 --- a/model/ListDecorator.php +++ b/model/ListDecorator.php @@ -147,6 +147,32 @@ abstract class SS_ListDecorator extends ViewableData implements SS_List, SS_Sort return call_user_func_array(array($this->list, 'filter'), $args); } + /** + * Return a copy of this list which contains items matching any of these charactaristics. + * + * @example // only bob in the list + * $list = $list->filterAny('Name', 'bob'); + * // SQL: WHERE "Name" = 'bob' + * @example // azis or bob in the list + * $list = $list->filterAny('Name', array('aziz', 'bob'); + * // SQL: WHERE ("Name" IN ('aziz','bob')) + * @example // bob or anyone aged 21 in the list + * $list = $list->filterAny(array('Name'=>'bob, 'Age'=>21)); + * // SQL: WHERE ("Name" = 'bob' OR "Age" = '21') + * @example // bob or anyone aged 21 or 43 in the list + * $list = $list->filterAny(array('Name'=>'bob, 'Age'=>array(21, 43))); + * // SQL: WHERE ("Name" = 'bob' OR ("Age" IN ('21', '43')) + * @example // all bobs, phils or anyone aged 21 or 43 in the list + * $list = $list->filterAny(array('Name'=>array('bob','phil'), 'Age'=>array(21, 43))); + * // SQL: WHERE (("Name" IN ('bob', 'phil')) OR ("Age" IN ('21', '43')) + * + * @param string|array See {@link filter()} + * @return DataList + */ + public function filterAny() { + return call_user_func_array(array($this->list, __FUNCTION__), func_get_args()); + } + /** * Note that, in the current implementation, the filtered list will be an ArrayList, but this may change in a * future implementation. @@ -174,6 +200,26 @@ abstract class SS_ListDecorator extends ViewableData implements SS_List, SS_Sort return $this->list->limit($limit, $offset); } + /** + * Return the first item with the given ID + * + * @param int $id + * @return mixed + */ + public function byID($id) { + return $this->list->byID($id); + } + + /** + * Filter this list to only contain the given Primary IDs + * + * @param array $ids Array of integers + * @return SS_List + */ + public function byIDs($ids) { + return $this->list->byIDs($ids); + } + /** * Exclude the list to not contain items with these charactaristics * diff --git a/model/UnsavedRelationList.php b/model/UnsavedRelationList.php index 05db48c92..e667a16c5 100644 --- a/model/UnsavedRelationList.php +++ b/model/UnsavedRelationList.php @@ -299,7 +299,7 @@ class UnsavedRelationList extends ArrayList { throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList."); } - public function byIDs() { + public function byIDs($ids) { throw new LogicException(__FUNCTION__ . " can't be called on an UnsavedRelationList."); } diff --git a/tests/model/ArrayListTest.php b/tests/model/ArrayListTest.php index 95af4d3f1..9178ff7b5 100644 --- a/tests/model/ArrayListTest.php +++ b/tests/model/ArrayListTest.php @@ -541,6 +541,72 @@ class ArrayListTest extends SapphireTest { $this->assertEquals($expected, $list->toArray(), 'List should only contain Steve and Steve and Clair'); } + public function testFilterAny() { + + $list = new ArrayList(array( + $steve = array('Name' => 'Steve', 'ID' => 1, 'Age' => 21), + $bob = array('Name' => 'Bob', 'ID' => 2, 'Age' => 18), + $clair = array('Name' => 'Clair', 'ID' => 3, 'Age' => 21), + $phil = array('Name' => 'Phil', 'ID' => 4, 'Age' => 21), + $oscar = array('Name' => 'Oscar', 'ID' => 5, 'Age' => 52), + $mike = array('Name' => 'Mike', 'ID' => 6, 'Age' => 43), + )); + + // only bob in the list + //$list = $list->filterAny('Name', 'bob'); + $filteredList = $list->filterAny('Name', 'Bob')->toArray(); + $this->assertCount(1, $filteredList); + $this->assertContains($bob, $filteredList); + + // azis or bob in the list + //$list = $list->filterAny('Name', array('aziz', 'bob'); + $filteredList = $list->filterAny('Name', array('Aziz', 'Bob'))->toArray(); + $this->assertCount(1, $filteredList); + $this->assertContains($bob, $filteredList); + + $filteredList = $list->filterAny('Name', array('Steve', 'Bob'))->toArray(); + $this->assertCount(2, $filteredList); + $this->assertContains($steve, $filteredList); + $this->assertContains($bob, $filteredList); + + // bob or anyone aged 21 in the list + //$list = $list->filterAny(array('Name'=>'bob, 'Age'=>21)); + $filteredList = $list->filterAny(array('Name' => 'Bob', 'Age' => 21))->toArray(); + $this->assertCount(4, $filteredList); + $this->assertContains($bob, $filteredList); + $this->assertContains($steve, $filteredList); + $this->assertContains($clair, $filteredList); + $this->assertContains($phil, $filteredList); + + // bob or anyone aged 21 or 43 in the list + // $list = $list->filterAny(array('Name'=>'bob, 'Age'=>array(21, 43))); + $filteredList = $list->filterAny(array('Name' => 'Bob', 'Age' => array(21, 43)))->toArray(); + $this->assertCount(5, $filteredList); + $this->assertContains($bob, $filteredList); + $this->assertContains($steve, $filteredList); + $this->assertContains($clair, $filteredList); + $this->assertContains($mike, $filteredList); + $this->assertContains($phil, $filteredList); + + // all bobs, phils or anyone aged 21 or 43 in the list + //$list = $list->filterAny(array('Name'=>array('bob','phil'), 'Age'=>array(21, 43))); + $filteredList = $list->filterAny(array('Name' => array('Bob', 'Phil'), 'Age' => array(21, 43)))->toArray(); + $this->assertCount(5, $filteredList); + $this->assertContains($bob, $filteredList); + $this->assertContains($steve, $filteredList); + $this->assertContains($clair, $filteredList); + $this->assertContains($mike, $filteredList); + $this->assertContains($phil, $filteredList); + + $filteredList = $list->filterAny(array('Name' => array('Bob', 'Nobody'), 'Age' => array(21, 43)))->toArray(); + $this->assertCount(5, $filteredList); + $this->assertContains($bob, $filteredList); + $this->assertContains($steve, $filteredList); + $this->assertContains($clair, $filteredList); + $this->assertContains($mike, $filteredList); + $this->assertContains($phil, $filteredList); + } + /** * $list = $list->filterByCallback(function($item, $list) { return $item->Age == 21; }) */ @@ -763,6 +829,21 @@ class ArrayListTest extends SapphireTest { $this->assertNull($element); } + public function testByIDs() { + $list = new ArrayList(array( + array('ID' => 1, 'Name' => 'Steve'), + array('ID' => 2, 'Name' => 'Bob'), + array('ID' => 3, 'Name' => 'John') + )); + $knownIDs = $list->column('ID'); + $removedID = array_pop($knownIDs); + $filteredItems = $list->byIDs($knownIDs); + foreach ($filteredItems as $item) { + $this->assertContains($item->ID, $knownIDs); + $this->assertNotEquals($removedID, $item->ID); + } + } + public function testByIDEmpty() { $list = new ArrayList(); diff --git a/tests/model/DataListTest.php b/tests/model/DataListTest.php index dc355aa8e..25822a336 100755 --- a/tests/model/DataListTest.php +++ b/tests/model/DataListTest.php @@ -394,6 +394,16 @@ class DataListTest extends SapphireTest { $this->assertEquals('Team 2', $team->Title); } + public function testByIDs() { + $knownIDs = $this->allFixtureIDs('DataObjectTest_Player'); + $removedID = array_pop($knownIDs); + $filteredPlayers = DataObjectTest_Player::get()->byIDs($knownIDs); + foreach ($filteredPlayers as $player) { + $this->assertContains($player->ID, $knownIDs); + $this->assertNotEquals($removedID, $player->ID); + } + } + /** * Test DataList->removeByID() */