From aafdb8e01c47cf5aeccc2b5b7139e6fc7560a06b Mon Sep 17 00:00:00 2001 From: Stig Lindqvist Date: Fri, 9 Dec 2011 10:08:46 +1300 Subject: [PATCH] API CHANGE All SS_List implementators supports filter, exclude and sort methods --- model/ArrayList.php | 178 +++++++++++++++++-- model/DataList.php | 173 ++++++++++++++++-- model/DataQuery.php | 0 model/List.php | 33 +++- model/ListDecorator.php | 43 ++++- model/SQLQuery.php | 0 tests/model/ArrayListTest.php | 323 +++++++++++++++++++++++++++++++++- tests/model/DataListTest.php | 281 ++++++++++++++++++++++++++++- 8 files changed, 984 insertions(+), 47 deletions(-) mode change 100755 => 100644 model/DataQuery.php mode change 100755 => 100644 model/SQLQuery.php mode change 100644 => 100755 tests/model/DataListTest.php diff --git a/model/ArrayList.php b/model/ArrayList.php index 092d737b5..fc783a4f9 100644 --- a/model/ArrayList.php +++ b/model/ArrayList.php @@ -291,33 +291,175 @@ class ArrayList extends ViewableData implements SS_List { * Sorts this list by one or more fields. You can either pass in a single * field name and direction, or a map of field names to sort directions. * - * @param string|array $by - * @param string $sortDirection + * @return DataList * @see SS_List::sort() - * @link http://php.net/manual/en/function.array-multisort.php + * @example $list->sort('Name'); // default ASC sorting + * @example $list->sort('Name DESC'); // DESC sorting * @example $list->sort('Name', 'ASC'); - * @example $list->sort(array('Name'=>'ASC,'Age'=>'DESC'); + * @example $list->sort(array('Name'=>'ASC,'Age'=>'DESC')); */ - public function sort($by, $sortDirection = 'ASC') { - $sorts = array(); - - if(!is_array($by)) { - $by = array($by => $sortDirection); + public function sort() { + $args = func_get_args(); + + if(count($args)==0){ + return $this; } + if(count($args)>2){ + throw new InvalidArgumentException('This method takes zero, one or two arguments'); + } + + // One argument and it's a string + if(count($args)==1 && is_string($args[0])){ + $column = $args[0]; + if(strpos($column, ' ') !== false) throw new InvalidArgumentException("You can't pass SQL fragments to sort()"); + $columnsToSort[$column] = SORT_ASC; - foreach ($by as $field => $sortDirection) { - $sortDirection = strtoupper($sortDirection) == 'DESC' ? SORT_DESC : SORT_ASC; - $values = array(); - foreach($this->items as $item) { - $values[] = $this->extractValue($item, $field); + } else if(count($args)==2){ + $columnsToSort[$args[0]]=(strtolower($args[1])=='desc')?SORT_DESC:SORT_ASC; + + } else if(is_array($args[0])) { + foreach($args[0] as $column => $sort_order){ + $columnsToSort[$column] = (strtolower($sort_order)=='desc')?SORT_DESC:SORT_ASC; } - $sorts[] = &$values; - $sorts[] = &$sortDirection; + } else { + throw new InvalidArgumentException("Bad arguments passed to sort()"); } - $sorts[] = &$this->items; - call_user_func_array('array_multisort', $sorts); + + // This the main sorting algorithm that supports infinite sorting params + $multisortArgs = array(); + $values = array(); + foreach($columnsToSort as $column => $direction ) { + // The reason these are added to columns is of the references, otherwise when the foreach + // is done, all $values and $direction look the same + $values[$column] = array(); + $sortDirection[$column] = $direction; + // We need to subtract every value into a temporary array for sorting + foreach($this->items as $index => $item) { + $values[$column][] = $this->extractValue($item, $column); + } + // PHP 5.3 requires below arguments to be reference when using array_multisort together + // with call_user_func_array + // First argument is the 'value' array to be sorted + $multisortArgs[] = &$values[$column]; + // First argument is the direction to be sorted, + $multisortArgs[] = &$sortDirection[$column]; + } + // As the last argument we pass in a reference to the items that all the sorting will be + // applied upon + $multisortArgs[] = &$this->items; + call_user_func_array('array_multisort', $multisortArgs); + return $this; } + /** + * Filter the list to include items with these charactaristics + * + * @return ArrayList + * @see SS_List::filter() + * @example $list->filter('Name', 'bob'); // only bob in the list + * @example $list->filter('Name', array('aziz', 'bob'); // aziz and bob in list + * @example $list->filter(array('Name'=>'bob, 'Age'=>21)); // bob with the Age 21 in list + * @example $list->filter(array('Name'=>'bob, 'Age'=>array(21, 43))); // bob with the Age 21 or 43 + * @example $list->filter(array('Name'=>array('aziz','bob'), 'Age'=>array(21, 43))); // aziz with the age 21 or 43 and bob with the Age 21 or 43 + */ + public function filter() { + if(count(func_get_args())>2){ + throw new InvalidArgumentException('filter takes one array or two arguments'); + } + + if(count(func_get_args()) == 1 && !is_array(func_get_arg(0))){ + throw new InvalidArgumentException('filter takes one array or two arguments'); + } + + $keepUs = array(); + if(count(func_get_args())==2){ + $keepUs[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 => $value) { + $keepUs[$column] = $value; + } + } + + $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; + } + } + + $this->items = $itemsToKeep; + return $this; + } + + /** + * Exclude the list to not contain items with these charactaristics + * + * @return ArrayList + * @see SS_List::exclude() + * @example $list->exclude('Name', 'bob'); // exclude bob from list + * @example $list->exclude('Name', array('aziz', 'bob'); // exclude aziz and bob from list + * @example $list->exclude(array('Name'=>'bob, 'Age'=>21)); // exclude bob that has Age 21 + * @example $list->exclude(array('Name'=>'bob, 'Age'=>array(21, 43))); // exclude bob with Age 21 or 43 + * @example $list->exclude(array('Name'=>array('bob','phil'), 'Age'=>array(21, 43))); // 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; + } + } + + $itemsToKeep = array(); + + $hitsRequiredToRemove = count($removeUs); + $matches = array(); + foreach($removeUs as $column => $excludeValue) { + foreach($this->items as $key => $item){ + if(!is_array($excludeValue) && $this->extractValue($item, $column) == $excludeValue) { + $matches[$key]=isset($matches[$key])?$matches[$key]+1:1; + } elseif(is_array($excludeValue) && in_array($this->extractValue($item, $column), $excludeValue)) { + $matches[$key]=isset($matches[$key])?$matches[$key]+1:1; + } + } + } + + $keysToRemove = array_keys($matches,$hitsRequiredToRemove); + foreach($keysToRemove as $itemToRemoveIdx){ + $this->remove($this->items[$itemToRemoveIdx]); + } + return; + + return $this; + } + + protected function shouldExclude($item, $args) { + + } + + /** * Returns whether an item with $key exists * diff --git a/model/DataList.php b/model/DataList.php index 85589fbb9..09715f921 100644 --- a/model/DataList.php +++ b/model/DataList.php @@ -105,19 +105,6 @@ class DataList extends ViewableData implements SS_List { return $this; } - /** - * Set the sort order of this data list - * - * @param string $sort - * @param string $direction - * @return DataList - */ - public function sort($sort, $direction = "ASC") { - if($direction && strtoupper($direction) != 'ASC') $sort = "$sort $direction"; - $this->dataQuery->sort($sort); - return $this; - } - /** * Returns true if this DataList can be sorted by the given field. * @@ -150,7 +137,165 @@ class DataList extends ViewableData implements SS_List { $this->dataQuery->limit($limit); return $this; } + + /** + * Set the sort order of this data list + * + * @return DataList + * @see SS_List::sort() + * @example $list->sort('Name'); // default ASC sorting + * @example $list->sort('Name DESC'); // DESC sorting + * @example $list->sort('Name', 'ASC'); + * @example $list->sort(array('Name'=>'ASC,'Age'=>'DESC')); + */ + public function sort() { + if(count(func_get_args())==0){ + return $this; + } + if(count(func_get_args())>2){ + throw new InvalidArgumentException('This method takes zero, one or two arguments'); + } + // sort('Name','Desc') + if(count(func_get_args())==2){ + if(!in_array(strtolower(func_get_arg(1)),array('desc','asc'))){ + user_error('Second argument to sort must be either ASC or DESC'); + } + $this->dataQuery->sort(func_get_arg(0).' '.func_get_arg(1)); + return $this; + } + + // sort('Name') - default to ASC sorting if not specified + if(is_string(func_get_arg(0)) && func_get_arg(0)){ + // sort('Name ASC') + if(stristr(func_get_arg(0), ' asc') || stristr(func_get_arg(0), ' desc')){ + $this->dataQuery->sort(func_get_arg(0)); + } else { + $this->dataQuery->sort(func_get_arg(0).' ASC'); + } + + return $this; + } + + // sort(array('Name'=>'desc')); + $argumentArray = func_get_arg(0); + if(is_array($argumentArray)){ + $sort = array(); + foreach($argumentArray as $column => $direction) { + $sort []= '"'.$column.'" '.$direction; + } + $this->dataQuery->sort(implode(',', $sort)); + return $this; + } + + return $this; + } + + /** + * Filter the list to include items with these charactaristics + * + * @return DataList + * @see SS_List::filter() + * @example $list->filter('Name', 'bob'); // only bob in the list + * @example $list->filter('Name', array('aziz', 'bob'); // aziz and bob in list + * @example $list->filter(array('Name'=>'bob, 'Age'=>21)); // bob with the age 21 + * @example $list->filter(array('Name'=>'bob, 'Age'=>array(21, 43))); // bob with the Age 21 or 43 + * @example $list->filter(array('Name'=>array('aziz','bob'), 'Age'=>array(21, 43))); // aziz with the age 21 or 43 and bob with the Age 21 or 43 + * + * @todo extract the sql from $customQuery into a SQLGenerator class + */ + public function filter() { + $numberFuncArgs = count(func_get_args()); + $whereArguments = array(); + if($numberFuncArgs == 1 && is_array(func_get_arg(0))){ + $whereArguments = func_get_arg(0); + } elseif($numberFuncArgs == 2) { + $whereArguments[func_get_arg(0)] = func_get_arg(1); + } else { + throw new InvalidArgumentException('Arguments passed to filter() is wrong'); + } + + $SQL_Statements = array(); + foreach($whereArguments as $field => $value) { + if(is_array($value)) { + $customQuery = 'IN (\''.implode('\',\'',Convert::raw2sql($value)).'\')'; + } else { + $customQuery = '= \''.Convert::raw2sql($value).'\''; + } + + if(stristr($field,':')) { + $fieldArgs = explode(':',$field); + $field = array_shift($fieldArgs); + foreach($fieldArgs as $fieldArg){ + $comparisor = $this->applyFilterContext($field, $fieldArg, $value); + } + } else { + $SQL_Statements[] = '"'.Convert::raw2sql($field).'" '.$customQuery; + } + } + if(count($SQL_Statements)) { + foreach($SQL_Statements as $SQL_Statement){ + $this->dataQuery->where($SQL_Statement); + } + } + return $this; + } + + /** + * Translates the comparisator to the sql query + * + * @param string $field - the fieldname in the db + * @param string $comparisators - example StartsWith, relates to a filtercontext + * @param string $value - the value that the filtercontext will use for matching + * @todo Deprecated SearchContexts and pull their functionality into the core of the ORM + */ + private function applyFilterContext($field, $comparisators, $value) { + $t = singleton($this->dataClass())->dbObject($field); + $className = "{$comparisators}Filter"; + if(!class_exists($className)){ + throw new InvalidArgumentException('There are no '.$comparisators.' comparisator'); + } + $t = new $className($field,$value); + $t->apply($this->dataQuery()); + } + + /** + * Exclude the list to not contain items with these charactaristics + * + * @return DataList + * @see SS_List::exclude() + * @example $list->exclude('Name', 'bob'); // exclude bob from list + * @example $list->exclude('Name', array('aziz', 'bob'); // exclude aziz and bob from list + * @example $list->exclude(array('Name'=>'bob, 'Age'=>21)); // exclude bob that has Age 21 + * @example $list->exclude(array('Name'=>'bob, 'Age'=>array(21, 43))); // exclude bob with Age 21 or 43 + * @example $list->exclude(array('Name'=>array('bob','phil'), 'Age'=>array(21, 43))); // bob age 21 or 43, phil age 21 or 43 would be excluded + * + * @todo extract the sql from this method into a SQLGenerator class + */ + public function exclude(){ + $numberFuncArgs = count(func_get_args()); + $whereArguments = array(); + + if($numberFuncArgs == 1 && is_array(func_get_arg(0))){ + $whereArguments = func_get_arg(0); + } elseif($numberFuncArgs == 2) { + $whereArguments[func_get_arg(0)] = func_get_arg(1); + } else { + throw new InvalidArgumentException('Arguments passed to exclude() is wrong'); + } + + $SQL_Statements = array(); + foreach($whereArguments as $fieldName => $value) { + if(is_array($value)){ + $SQL_Statements[] = ('"'.$fieldName.'" NOT IN (\''.implode('\',\'', Convert::raw2sql($value)).'\')'); + } else { + $SQL_Statements[] = ('"'.$fieldName.'" != \''.Convert::raw2sql($value).'\''); + } + } + $this->dataQuery->where(implode(' OR ', $SQL_Statements)); + return $this; + } + /** * Add an inner join clause to this data list's query. * @@ -647,6 +792,6 @@ class DataList extends ViewableData implements SS_List { */ public function offsetUnset($key) { user_error("Can't alter items in a DataList using array-access", E_USER_ERROR); - } + } } diff --git a/model/DataQuery.php b/model/DataQuery.php old mode 100755 new mode 100644 diff --git a/model/List.php b/model/List.php index ed2778fd2..b03688d6d 100644 --- a/model/List.php +++ b/model/List.php @@ -96,11 +96,36 @@ interface SS_List extends ArrayAccess, Countable, IteratorAggregate { public function canSortBy($by); /** - * Sorts the list in place by a field on the items and direction. + * Sorts this list by one or more fields. You can either pass in a single + * field name and direction, or a map of field names to sort directions. * - * @param string $by The field name to sort by. - * @param string $dir Either "ASC" or "DIR". + * @example $list->sort('Name'); // default ASC sorting + * @example $list->sort('Name DESC'); // DESC sorting + * @example $list->sort('Name', 'ASC'); + * @example $list->sort(array('Name'=>'ASC,'Age'=>'DESC')); */ - public function sort($by, $dir = 'ASC'); + public function sort(); + + /** + * Filter the list to include items with these charactaristics + * + * @example $list->filter('Name', 'bob'); // only bob in the list + * @example $list->filter('Name', array('aziz', 'bob'); // aziz and bob in list + * @example $list->filter(array('Name'=>'bob, 'Age'=>21)); // bob with the age 21 + * @example $list->filter(array('Name'=>'bob, 'Age'=>array(21, 43))); // bob with the Age 21 or 43 + * @example $list->filter(array('Name'=>array('aziz','bob'), 'Age'=>array(21, 43))); // aziz with the age 21 or 43 and bob with the Age 21 or 43 + */ + public function filter(); + + /** + * Exclude the list to not contain items with these charactaristics + * + * @example $list->exclude('Name', 'bob'); // exclude bob from list + * @example $list->exclude('Name', array('aziz', 'bob'); // exclude aziz and bob from list + * @example $list->exclude(array('Name'=>'bob, 'Age'=>21)); // exclude bob that has Age 21 + * @example $list->exclude(array('Name'=>'bob, 'Age'=>array(21, 43))); // exclude bob with Age 21 or 43 + * @example $list->exclude(array('Name'=>array('bob','phil'), 'Age'=>array(21, 43))); // bob age 21 or 43, phil age 21 or 43 would be excluded + */ + public function exclude(); } \ No newline at end of file diff --git a/model/ListDecorator.php b/model/ListDecorator.php index ccea22054..9eaf0840e 100644 --- a/model/ListDecorator.php +++ b/model/ListDecorator.php @@ -108,9 +108,46 @@ abstract class SS_ListDecorator extends ViewableData implements SS_List { public function canSortBy($by) { return $this->list->canSortBy($by); } - - public function sort($fieldname, $direction = "ASC") { - $this->list->sort($fieldname, $direction); + + /** + * Sorts this list by one or more fields. You can either pass in a single + * field name and direction, or a map of field names to sort directions. + * + * @example $list->sort('Name'); // default ASC sorting + * @example $list->sort('Name DESC'); // DESC sorting + * @example $list->sort('Name', 'ASC'); + * @example $list->sort(array('Name'=>'ASC,'Age'=>'DESC')); + */ + public function sort() { + $args = func_get_args(); + return $this->list->sort($args); + } + + /** + * Filter the list to include items with these charactaristics + * + * @example $list->filter('Name', 'bob'); // only bob in list + * @example $list->filter('Name', array('aziz', 'bob'); // aziz and bob in list + * @example $list->filter(array('Name'=>'bob, 'Age'=>21)); // bob or someone with Age 21 + * @example $list->filter(array('Name'=>'bob, 'Age'=>array(21, 43))); // bob or anyone with Age 21 or 43 + */ + public function filter(){ + $args = func_get_args(); + return $this->list->filter(func_get_args($args)); + } + + /** + * Exclude the list to not contain items with these charactaristics + * + * @example $list->exclude('Name', 'bob'); // exclude bob from list + * @example $list->exclude('Name', array('aziz', 'bob'); // exclude aziz and bob from list + * @example $list->exclude(array('Name'=>'bob, 'Age'=>21)); // exclude bob or someone with Age 21 + * @example $list->exclude(array('Name'=>'bob, 'Age'=>array(21, 43))); // exclude bob or anyone with Age 21 or 43 + */ + public function exclude(){ + $args = func_get_args(); + return $this->list->exclude(func_get_args($args)); + } public function debug() { diff --git a/model/SQLQuery.php b/model/SQLQuery.php old mode 100755 new mode 100644 diff --git a/tests/model/ArrayListTest.php b/tests/model/ArrayListTest.php index b7f44c7f2..bf84fff9f 100644 --- a/tests/model/ArrayListTest.php +++ b/tests/model/ArrayListTest.php @@ -214,7 +214,7 @@ class ArrayListTest extends SapphireTest { )); } - public function testSort() { + public function testSortSimpleDefualtIsSortedASC() { $list = new ArrayList(array( array('Name' => 'Steve'), (object) array('Name' => 'Bob'), @@ -227,6 +227,28 @@ class ArrayListTest extends SapphireTest { array('Name' => 'John'), array('Name' => 'Steve') )); + } + + public function testSortSimpleASCOrder() { + $list = new ArrayList(array( + array('Name' => 'Steve'), + (object) array('Name' => 'Bob'), + array('Name' => 'John') + )); + $list->sort('Name','asc'); + $this->assertEquals($list->toArray(), array( + (object) array('Name' => 'Bob'), + array('Name' => 'John'), + array('Name' => 'Steve') + )); + } + + public function testSortSimpleDESCOrder() { + $list = new ArrayList(array( + array('Name' => 'Steve'), + (object) array('Name' => 'Bob'), + array('Name' => 'John') + )); $list->sort('Name', 'DESC'); $this->assertEquals($list->toArray(), array( @@ -236,7 +258,7 @@ class ArrayListTest extends SapphireTest { )); } - public function testMultiSort() { + public function testSimpleMultiSort() { $list = new ArrayList(array( (object) array('Name'=>'Object1', 'F1'=>1, 'F2'=>2, 'F3'=>3), (object) array('Name'=>'Object2', 'F1'=>2, 'F2'=>1, 'F3'=>4), @@ -245,17 +267,304 @@ class ArrayListTest extends SapphireTest { $list->sort('F3', 'ASC'); $this->assertEquals($list->first()->Name, 'Object3', 'Object3 should be first in the list'); + $this->assertEquals($list->last()->Name, 'Object2', 'Object2 should be last in the list'); $list->sort('F3', 'DESC'); $this->assertEquals($list->first()->Name, 'Object2', 'Object2 should be first in the list'); - - $list->sort(array('F2'=>'ASC', 'F1'=>'ASC')); $this->assertEquals($list->last()->Name, 'Object3', 'Object3 should be last in the list'); - - $list->sort(array('F2'=>'ASC', 'F1'=>'DESC')); - $this->assertEquals($list->last()->Name, 'Object1', 'Object1 should be last in the list'); + } + + public function testMultiSort() { + $list = new ArrayList(array( + (object) array('ID'=>3, 'Name'=>'Bert', 'Importance'=>1), + (object) array('ID'=>1, 'Name'=>'Aron', 'Importance'=>2), + (object) array('ID'=>2, 'Name'=>'Aron', 'Importance'=>1), + )); + + $list->sort(array('Name'=>'ASC', 'Importance'=>'ASC')); + $this->assertEquals($list->first()->ID, 2, 'Aron.2 should be first in the list'); + $this->assertEquals($list->last()->ID, 3, 'Bert.3 should be last in the list'); + + $list->sort(array('Name'=>'ASC', 'Importance'=>'DESC')); + $this->assertEquals($list->first()->ID, 1, 'Aron.2 should be first in the list'); + $this->assertEquals($list->last()->ID, 3, 'Bert.3 should be last in the list'); + } + + /** + * $list->filter('Name', 'bob'); // only bob in the list + */ + public function testSimpleFilter() { + $list = new ArrayList(array( + array('Name' => 'Steve'), + (object) array('Name' => 'Bob'), + array('Name' => 'John') + )); + $list->filter('Name','Bob'); + $this->assertEquals(array((object)array('Name'=>'Bob')), $list->toArray(), 'List should only contain Bob'); + } + + /** + * $list->filter('Name', array('Steve', 'John'); // Steve and John in list + */ + public function testSimpleFilterWithMultiple() { + $list = new ArrayList(array( + array('Name' => 'Steve'), + (object) array('Name' => 'Bob'), + array('Name' => 'John') + )); + + $expected = array( + array('Name' => 'Steve'), + array('Name' => 'John') + ); + $list->filter('Name',array('Steve','John')); + $this->assertEquals($expected, $list->toArray(), 'List should only contain Steve and John'); + } + + /** + * $list->filter('Name', array('Steve', 'John'); // negative version + */ + public function testSimpleFilterWithMultipleNoMatch() { + $list = new ArrayList(array( + array('Name' => 'Steve', 'ID' => 1), + (object) array('Name' => 'Steve', 'ID' => 2), + array('Name' => 'John', 'ID' => 2) + )); + $list->filter(array('Name'=>'Clair')); + $this->assertEquals(array(), $list->toArray(), 'List should be empty'); + } + + /** + * $list->filter(array('Name'=>'bob, 'Age'=>21)); // bob with the Age 21 in list + */ + public function testMultipleFilter() { + $list = new ArrayList(array( + array('Name' => 'Steve', 'ID' => 1), + (object) array('Name' => 'Steve', 'ID' => 2), + array('Name' => 'John', 'ID' => 2) + )); + $list->filter(array('Name'=>'Steve', 'ID'=>2)); + $this->assertEquals(array((object)array('Name'=>'Steve', 'ID'=>2)), $list->toArray(), 'List should only contain object Steve'); + } + + /** + * $list->filter(array('Name'=>'bob, 'Age'=>21)); // negative version + */ + public function testMultipleFilterNoMatch() { + $list = new ArrayList(array( + array('Name' => 'Steve', 'ID' => 1), + (object) array('Name' => 'Steve', 'ID' => 2), + array('Name' => 'John', 'ID' => 2) + )); + $list->filter(array('Name'=>'Steve', 'ID'=>4)); + $this->assertEquals(array(), $list->toArray(), 'List should be empty'); + } + + /** + * $list->filter(array('Name'=>'Steve', 'Age'=>array(21, 43))); // Steve with the Age 21 or 43 + */ + public function testMultipleWithArrayFilter() { + $list = new ArrayList(array( + array('Name' => 'Steve', 'ID' => 1, 'Age'=>21), + array('Name' => 'Steve', 'ID' => 2, 'Age'=>18), + array('Name' => 'Clair', 'ID' => 2, 'Age'=>21), + array('Name' => 'Steve', 'ID' => 3, 'Age'=>43) + )); + + $list->filter(array('Name'=>'Steve','Age'=>array(21, 43))); + + $expected = array( + array('Name' => 'Steve', 'ID' => 1, 'Age'=>21), + array('Name' => 'Steve', 'ID' => 3, 'Age'=>43) + ); + $this->assertEquals(2, $list->count()); + $this->assertEquals($expected, $list->toArray(), 'List should only contain Steve and Steve'); + } + + /** + * $list->filter(array('Name'=>array('aziz','bob'), 'Age'=>array(21, 43))); + */ + public function testMultipleWithArrayFilterAdvanced() { + $list = new ArrayList(array( + array('Name' => 'Steve', 'ID' => 1, 'Age'=>21), + array('Name' => 'Steve', 'ID' => 2, 'Age'=>18), + array('Name' => 'Clair', 'ID' => 2, 'Age'=>21), + array('Name' => 'Clair', 'ID' => 2, 'Age'=>52), + array('Name' => 'Steve', 'ID' => 3, 'Age'=>43) + )); + + $list->filter(array('Name'=>array('Steve','Clair'),'Age'=>array(21, 43))); + + $expected = array( + array('Name' => 'Steve', 'ID' => 1, 'Age'=>21), + array('Name' => 'Clair', 'ID' => 2, 'Age'=>21), + array('Name' => 'Steve', 'ID' => 3, 'Age'=>43) + ); + + $this->assertEquals(3, $list->count()); + $this->assertEquals($expected, $list->toArray(), 'List should only contain Steve and Steve and Clair'); + } + + /** + * $list->exclude('Name', 'bob'); // exclude bob from list + */ + public function testSimpleExclude() { + $list = new ArrayList(array( + 0=>array('Name' => 'Steve'), + 1=>array('Name' => 'Bob'), + 2=>array('Name' => 'John') + )); + + $list->exclude('Name', 'Bob'); + $expected = array( + 0=>array('Name' => 'Steve'), + 2=>array('Name' => 'John') + ); + $this->assertEquals(2, $list->count()); + $this->assertEquals($expected, $list->toArray(), 'List should not contain Bob'); + } + + /** + * $list->exclude('Name', 'bob'); // No exclusion version + */ + public function testSimpleExcludeNoMatch() { + $list = new ArrayList(array( + array('Name' => 'Steve'), + array('Name' => 'Bob'), + array('Name' => 'John') + )); + + $list->exclude('Name', 'Clair'); + $expected = array( + array('Name' => 'Steve'), + array('Name' => 'Bob'), + array('Name' => 'John') + ); + $this->assertEquals($expected, $list->toArray(), 'List should be unchanged'); + } + + /** + * $list->exclude('Name', array('Steve','John')); + */ + public function testSimpleExcludeWithArray() { + $list = new ArrayList(array( + 0=>array('Name' => 'Steve'), + 1=>array('Name' => 'Bob'), + 2=>array('Name' => 'John') + )); + $list->exclude('Name', array('Steve','John')); + $expected = array(1=>array('Name' => 'Bob')); + $this->assertEquals(1, $list->count()); + $this->assertEquals($expected, $list->toArray(), 'List should only contain Bob'); + } + + /** + * $list->exclude(array('Name'=>'bob, 'Age'=>21)); // exclude all Bob that has Age 21 + */ + public function testExcludeWithTwoArrays() { + $list = new ArrayList(array( + 0=>array('Name' => 'Bob' , 'Age' => 21), + 1=>array('Name' => 'Bob' , 'Age' => 32), + 2=>array('Name' => 'John', 'Age' => 21) + )); + + $list->exclude(array('Name' => 'Bob', 'Age' => 21)); + + $expected = array( + 1=>array('Name' => 'Bob', 'Age' => 32), + 2=>array('Name' => 'John', 'Age' => 21) + ); + + $this->assertEquals(2, $list->count()); + $this->assertEquals($expected, $list->toArray(), 'List should only contain John and Bob'); } + /** + * $list->exclude(array('Name'=>array('bob','phil'), 'Age'=>array(10, 16))); + */ + public function testMultipleExclude() { + $list = new ArrayList(array( + 0 => array('Name' => 'bob', 'Age' => 10), + 1 => array('Name' => 'phil', 'Age' => 11), + 2 => array('Name' => 'bob', 'Age' => 12), + 3 => array('Name' => 'phil', 'Age' => 12), + 4 => array('Name' => 'bob', 'Age' => 14), + 5 => array('Name' => 'phil', 'Age' => 14), + 6 => array('Name' => 'bob', 'Age' => 16), + 7 => array('Name' => 'phil', 'Age' => 16) + )); + + $list->exclude(array('Name'=>array('bob','phil'),'Age'=>array(10, 16))); + $expected = array( + 1 => array('Name' => 'phil', 'Age' => 11), + 2 => array('Name' => 'bob', 'Age' => 12), + 3 => array('Name' => 'phil', 'Age' => 12), + 4 => array('Name' => 'bob', 'Age' => 14), + 5 => array('Name' => 'phil', 'Age' => 14), + ); + $this->assertEquals($expected, $list->toArray()); + } + + /** + * $list->exclude(array('Name'=>array('bob','phil'), 'Age'=>array(10, 16), 'Bananas'=>true)); + */ + public function testMultipleExcludeNoMatch() { + $list = new ArrayList(array( + 0 => array('Name' => 'bob', 'Age' => 10), + 1 => array('Name' => 'phil', 'Age' => 11), + 2 => array('Name' => 'bob', 'Age' => 12), + 3 => array('Name' => 'phil', 'Age' => 12), + 4 => array('Name' => 'bob', 'Age' => 14), + 5 => array('Name' => 'phil', 'Age' => 14), + 6 => array('Name' => 'bob', 'Age' => 16), + 7 => array('Name' => 'phil', 'Age' => 16) + )); + + $list->exclude(array('Name'=>array('bob','phil'),'Age'=>array(10, 16),'Bananas'=>true)); + $expected = array( + 0 => array('Name' => 'bob', 'Age' => 10), + 1 => array('Name' => 'phil', 'Age' => 11), + 2 => array('Name' => 'bob', 'Age' => 12), + 3 => array('Name' => 'phil', 'Age' => 12), + 4 => array('Name' => 'bob', 'Age' => 14), + 5 => array('Name' => 'phil', 'Age' => 14), + 6 => array('Name' => 'bob', 'Age' => 16), + 7 => array('Name' => 'phil', 'Age' => 16) + ); + $this->assertEquals($expected, $list->toArray()); + } + + /** + * $list->exclude(array('Name'=>array('bob','phil'), 'Age'=>array(10, 16), 'HasBananas'=>true)); + */ + public function testMultipleExcludeThreeArguments() { + $list = new ArrayList(array( + 0 => array('Name' => 'bob', 'Age' => 10, 'HasBananas'=>false), + 1 => array('Name' => 'phil','Age' => 11, 'HasBananas'=>true), + 2 => array('Name' => 'bob', 'Age' => 12, 'HasBananas'=>true), + 3 => array('Name' => 'phil','Age' => 12, 'HasBananas'=>true), + 4 => array('Name' => 'bob', 'Age' => 14, 'HasBananas'=>false), + 4 => array('Name' => 'ann', 'Age' => 14, 'HasBananas'=>true), + 5 => array('Name' => 'phil','Age' => 14, 'HasBananas'=>false), + 6 => array('Name' => 'bob', 'Age' => 16, 'HasBananas'=>false), + 7 => array('Name' => 'phil','Age' => 16, 'HasBananas'=>true), + 8 => array('Name' => 'clair','Age' => 16, 'HasBananas'=>true) + )); + + $list->exclude(array('Name'=>array('bob','phil'),'Age'=>array(10, 16),'HasBananas'=>true)); + $expected = array( + 0 => array('Name' => 'bob', 'Age' => 10, 'HasBananas'=>false), + 1 => array('Name' => 'phil','Age' => 11, 'HasBananas'=>true), + 2 => array('Name' => 'bob', 'Age' => 12, 'HasBananas'=>true), + 3 => array('Name' => 'phil','Age' => 12, 'HasBananas'=>true), + 4 => array('Name' => 'bob', 'Age' => 14, 'HasBananas'=>false), + 4 => array('Name' => 'ann', 'Age' => 14, 'HasBananas'=>true), + 5 => array('Name' => 'phil','Age' => 14, 'HasBananas'=>false), + 6 => array('Name' => 'bob', 'Age' => 16, 'HasBananas'=>false), + 8 => array('Name' => 'clair','Age' => 16, 'HasBananas'=>true) + ); + $this->assertEquals($expected, $list->toArray()); + } } /** diff --git a/tests/model/DataListTest.php b/tests/model/DataListTest.php old mode 100644 new mode 100755 index 997886d7a..beea23d05 --- a/tests/model/DataListTest.php +++ b/tests/model/DataListTest.php @@ -200,4 +200,283 @@ class DataListTest extends SapphireTest { $record = $list->find('ID', $this->idFromFixture('DataObjectTest_Team', 'team2')); $this->assertEquals('Team 2', $record->Title); } -} \ No newline at end of file + + public function testSimpleSort() { + $list = DataList::create("DataObjectTest_TeamComment"); + $list->sort('Name'); + $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob'); + $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil'); + } + + public function testSimpleSortOneArgumentASC() { + $list = DataList::create("DataObjectTest_TeamComment"); + $list->sort('Name ASC'); + $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob'); + $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil'); + } + + public function testSimpleSortOneArgumentDESC() { + $list = DataList::create("DataObjectTest_TeamComment"); + $list->sort('Name DESC'); + $this->assertEquals('Phil', $list->first()->Name, 'Last comment should be from Phil'); + $this->assertEquals('Bob', $list->last()->Name, 'First comment should be from Bob'); + } + + public function testSortOneArgumentMultipleColumns() { + $list = DataList::create("DataObjectTest_TeamComment"); + $list->sort('TeamID ASC, Name DESC'); + $this->assertEquals('Joe', $list->first()->Name, 'First comment should be from Bob'); + $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil'); + } + + public function testSimpleSortASC() { + $list = DataList::create("DataObjectTest_TeamComment"); + $list->sort('Name', 'asc'); + $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob'); + $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil'); + } + + public function testSimpleSortDESC() { + $list = DataList::create("DataObjectTest_TeamComment"); + $list->sort('Name', 'desc'); + $this->assertEquals('Phil', $list->first()->Name, 'Last comment should be from Phil'); + $this->assertEquals('Bob', $list->last()->Name, 'First comment should be from Bob'); + } + + public function testSortWithArraySyntaxSortASC() { + $list = DataList::create("DataObjectTest_TeamComment"); + $list->sort(array('Name'=>'asc')); + $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob'); + $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil'); + } + + public function testSortWithArraySyntaxSortDESC() { + $list = DataList::create("DataObjectTest_TeamComment"); + $list->sort(array('Name'=>'desc')); + $this->assertEquals('Phil', $list->first()->Name, 'Last comment should be from Phil'); + $this->assertEquals('Bob', $list->last()->Name, 'First comment should be from Bob'); + } + + public function testSortWithMultipleArraySyntaxSort() { + $list = DataList::create("DataObjectTest_TeamComment"); + $list->sort(array('TeamID'=>'asc','Name'=>'desc')); + $this->assertEquals('Joe', $list->first()->Name, 'First comment should be from Bob'); + $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil'); + } + + /** + * $list->filter('Name', 'bob'); // only bob in the list + */ + public function testSimpleFilter() { + $list = DataList::create("DataObjectTest_Team"); + $list->filter('Title','Team 2'); + $this->assertEquals(1, $list->count()); + $this->assertEquals('Team 2', $list->first()->Title, 'List should only contain Team 2'); + $this->assertEquals('Team 2', $list->last()->Title, 'Last should only contain Team 2'); + } + + public function testSimpleFilterEndsWith() { + $list = DataList::create("DataObjectTest_TeamComment"); + $list->filter('Name:EndsWith', 'b'); + $this->assertEquals(1, $list->count()); + $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob'); + } + + public function testSimpleFilterExactMatchFilter() { + $list = DataList::create("DataObjectTest_TeamComment"); + $list->filter('Name:ExactMatch', 'Bob'); + $this->assertEquals(1, $list->count()); + $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob'); + } + + public function testSimpleFilterGreaterThanFilter() { + $list = DataList::create("DataObjectTest_TeamComment"); + $list->filter('TeamID:GreaterThan', 1); + $this->assertEquals(1, $list->count()); + $this->assertEquals('Phil', $list->first()->Name, 'First comment should be from Bob'); + } + + public function testSimpleFilterLessThanFilter() { + $list = DataList::create("DataObjectTest_TeamComment"); + $list = $list->filter('TeamID:LessThan', $this->idFromFixture('DataObjectTest_TeamComment', 'comment2'))->sort('Name'); + $this->assertEquals(2, $list->count()); + $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob'); + $this->assertEquals('Joe', $list->Last()->Name, 'Last comment should be from Joe'); + } + + public function testSimpleNegationFilter() { + $list = DataList::create("DataObjectTest_TeamComment"); + $list->filter('TeamID:Negation', 1); + $this->assertEquals(1, $list->count()); + $this->assertEquals('Phil', $list->first()->Name, 'First comment should be from Bob'); + } + + public function testSimplePartialMatchFilter() { + $list = DataList::create("DataObjectTest_TeamComment"); + $list->filter('Name:PartialMatch', 'o')->sort('Name'); + $this->assertEquals(2, $list->count()); + $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob'); + $this->assertEquals('Joe', $list->last()->Name, 'First comment should be from Joe'); + } + + public function testSimpleFilterStartsWith() { + $list = DataList::create("DataObjectTest_TeamComment"); + $list->filter('Name:StartsWith', 'B'); + $this->assertEquals(1, $list->count()); + $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob'); + } + + public function testSimpleFilterSubstring() { + $list = DataList::create("DataObjectTest_TeamComment"); + $list->filter('Comment:SubString', 'team comment'); + $this->assertEquals(2, $list->count()); + $this->assertEquals('Joe', $list->first()->Name, 'First comment should be from Bob'); + $this->assertEquals('Bob', $list->last()->Name, 'First comment should be from Bob'); + } + + public function testSimpleFilterWithNonExistingComparisator() { + $this->setExpectedException('InvalidArgumentException'); + $list = DataList::create("DataObjectTest_TeamComment"); + $list->filter('Comment:Bogus', 'team comment'); + } + + /** + * $list->filter('Name', array('aziz', 'bob'); // aziz and bob in list + */ + public function testSimpleFilterWithMultiple() { + $list = DataList::create("DataObjectTest_TeamComment"); + $list->filter('Name', array('Bob','Phil')); + $this->assertEquals(2, $list->count()); + $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob'); + $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil'); + } + + public function testMultipleFilterWithNoMatch() { + $list = DataList::create("DataObjectTest_TeamComment"); + $list->filter(array('Name'=>'Bob', 'Comment'=>'Phil is a unique guy, and comments on team2')); + $this->assertEquals(0, $list->count()); + } + + /** + * $list->filter(array('Name'=>'bob, 'Age'=>21)); // bob with the age 21 + */ + public function testFilterMultipleArray() { + $list = DataList::create("DataObjectTest_TeamComment"); + $list->filter(array('Name'=>'Bob', 'Comment'=>'This is a team comment by Bob')); + $this->assertEquals(1, $list->count()); + $this->assertEquals('Bob', $list->first()->Name, 'Only comment should be from Bob'); + } + + public function testFilterMultipleWithTwoMatches() { + $list = DataList::create("DataObjectTest_TeamComment"); + $list->filter(array('TeamID'=>1)); + $this->assertEquals(2, $list->count()); + } + + public function testFilterMultipleWithArrayFilter() { + $list = DataList::create("DataObjectTest_TeamComment"); + $list->filter(array('Name'=>array('Bob','Phil'))); + $this->assertEquals(2, $list->count(), 'There should be two comments'); + $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob'); + $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil'); + } + + /** + * $list->filter(array('Name'=>array('aziz','bob'), 'Age'=>array(21, 43))); + */ + public function testFilterArrayInArray() { + $list = DataList::create("DataObjectTest_TeamComment"); + $list->filter(array('Name'=>array('Bob','Phil'), 'TeamID'=>array(1))); + $this->assertEquals(1, $list->count(), 'There should be one comments'); + $this->assertEquals('Bob', $list->first()->Name, 'Only comment should be from Bob'); + } + + /** + * $list->exclude('Name', 'bob'); // exclude bob from list + */ + public function testSimpleExclude() { + $list = DataList::create("DataObjectTest_TeamComment"); + $list->exclude('Name', 'Bob'); + $this->assertEquals(2, $list->count()); + $this->assertEquals('Joe', $list->first()->Name, 'First comment should be from Joe'); + $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil'); + } +// + /** + * $list->exclude('Name', array('aziz', 'bob'); // exclude aziz and bob from list + */ + public function testSimpleExcludeWithMultiple() { + $list = DataList::create("DataObjectTest_TeamComment"); + $list->exclude('Name', array('Joe','Phil')); + $this->assertEquals(1, $list->count()); + $this->assertEquals('Bob', $list->first()->Name, 'First comment should be from Bob'); + } + + /** + * $list->exclude(array('Name'=>'bob, 'Age'=>21)); // negative version + */ + public function testMultipleExcludeWithMiss() { + $list = DataList::create("DataObjectTest_TeamComment"); + $list->exclude(array('Name'=>'Bob', 'Comment'=>'Does not match any comments')); + $this->assertEquals(3, $list->count()); + } + + /** + * $list->exclude(array('Name'=>'bob, 'Age'=>21)); // exclude bob that has Age 21 + */ + public function testMultipleExclude() { + $list = DataList::create("DataObjectTest_TeamComment"); + $list->exclude(array('Name'=>'Bob', 'Comment'=>'This is a team comment by Bob')); + $this->assertEquals(2, $list->count()); + } + + /** + * $list->exclude(array('Name'=>'bob, 'Age'=>array(21, 43))); // exclude bob with Age 21 or 43 + */ + public function testMultipleExcludeWithMultipleThatCheersEitherTeam() { + $list = DataList::create("DataObjectTest_TeamComment"); + $list->exclude(array('Name'=>'Bob', 'TeamID'=>array(1,2))); + $this->assertEquals(2, $list->count()); + $this->assertEquals('Joe', $list->first()->Name, 'First comment should be from Phil'); + $this->assertEquals('Phil', $list->last()->Name, 'First comment should be from Phil'); + } + + /** + * $list->exclude(array('Name'=>'bob, 'Age'=>array(21, 43))); // negative version + */ + public function testMultipleExcludeWithMultipleThatCheersOnNonExistingTeam() { + $list = DataList::create("DataObjectTest_TeamComment"); + $list->exclude(array('Name'=>'Bob', 'TeamID'=>array(3))); + $this->assertEquals(3, $list->count()); + } + + /** + * $list->exclude(array('Name'=>array('bob','phil'), 'Age'=>array(21, 43))); //negative version + */ + public function testMultipleExcludeWithNoExclusion() { + $list = DataList::create("DataObjectTest_TeamComment"); + $list->exclude(array('Name'=>array('Bob','Joe'), 'Comment' => 'Phil is a unique guy, and comments on team2')); + $this->assertEquals(3, $list->count()); + } + + /** + * $list->exclude(array('Name'=>array('bob','phil'), 'Age'=>array(21, 43))); + */ + public function testMultipleExcludeWithTwoArray() { + $list = DataList::create("DataObjectTest_TeamComment"); + $list->exclude(array('Name'=>array('Bob','Joe'), 'TeamID' => array(1,2))); + $this->assertEquals(1, $list->count()); + $this->assertEquals('Phil', $list->last()->Name, 'Only comment should be from Phil'); + } + + /** + * $list->exclude(array('Name'=>array('bob','phil'), 'Age'=>array(21, 43))); + */ + public function testMultipleExcludeWithTwoArrayOneTeam() { + $list = DataList::create("DataObjectTest_TeamComment"); + $list->exclude(array('Name'=>array('Bob','Phil'), 'TeamID' => array(1))); + $this->assertEquals(2, $list->count()); + $this->assertEquals('Joe', $list->first()->Name, 'First comment should be from Joe'); + $this->assertEquals('Phil', $list->last()->Name, 'Last comment should be from Phil'); + } +}