added filterByCallback() to interface SS_Filterable and all implementing classes

This commit is contained in:
Zauberfisch 2013-10-30 13:08:55 +00:00
parent ba1e688729
commit d9b74874fc
7 changed files with 130 additions and 11 deletions

View File

@ -211,6 +211,25 @@ You can also combine both conjunctive ("AND") and disjunctive ("OR") statements.
)); ));
// WHERE ("LastName" = 'Minnée' AND ("FirstName" = 'Sam' OR "Age" = '17')) // WHERE ("LastName" = 'Minnée' AND ("FirstName" = 'Sam' OR "Age" = '17'))
### Filter with PHP / filterByCallback
It is also possible to filter by a PHP callback, however this will force the
data model to fetch all records and loop them in PHP, thus `filter()` or `filterAny()`
are to be preferred over `filterByCallback()`.
Please note that because `filterByCallback()` has to run in PHP, it will always return
an `ArrayList` (even if called on a `DataList`, this however might change in future).
The first parameter to the callback is the item, the second parameter is the list itself.
The callback will run once for each record, if the callback returns true, this record
will be added to the list of returned items.
The below example will get all Members that have an expired or not encrypted password.
:::php
$membersWithBadPassword = Member::get()->filterByCallback(function($item, $list) {
if ($item->isPasswordExpired() || $item->PasswordEncryption = 'none') {
return true;
}
});
### Exclude ### Exclude
The `exclude()` method is the opposite to the filter in that it removes entries The `exclude()` method is the opposite to the filter in that it removes entries

View File

@ -476,6 +476,27 @@ class ArrayList extends ViewableData implements SS_List, SS_Filterable, SS_Sorta
return $firstElement; return $firstElement;
} }
/**
* @see SS_Filterable::filterByCallback()
*
* @example $list = $list->filterByCallback(function($item, $list) { return $item->Age == 9; })
* @param callable $callback
* @return ArrayList
*/
public function filterByCallback($callback) {
if(!is_callable($callback)) {
throw new LogicException(sprintf(
"SS_Filterable::filterByCallback() passed callback must be callable, '%s' given",
gettype($callback)
));
}
$output = ArrayList::create();
foreach($this as $item) {
if(call_user_func($callback, $item, $this)) $output->push($item);
}
return $output;
}
/** /**
* Exclude the list to not contain items with these charactaristics * Exclude the list to not contain items with these charactaristics
* *

View File

@ -406,21 +406,24 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
} }
/** /**
* Filter this DataList by a callback function.
* The function will be passed each record of the DataList in turn, and must return true for the record to be
* included. Returns the filtered list.
*
* Note that, in the current implementation, the filtered list will be an ArrayList, but this may change in a * Note that, in the current implementation, the filtered list will be an ArrayList, but this may change in a
* future implementation. * future implementation.
* @see SS_Filterable::filterByCallback()
*
* @example $list = $list->filterByCallback(function($item, $list) { return $item->Age == 9; })
* @param callable $callback
* @return ArrayList (this may change in future implementations)
*/ */
public function filterByCallback($callback) { public function filterByCallback($callback) {
if(!is_callable($callback)) { if(!is_callable($callback)) {
throw new LogicException("DataList::filterByCallback() must be passed something callable."); throw new LogicException(sprintf(
"SS_Filterable::filterByCallback() passed callback must be callable, '%s' given",
gettype($callback)
));
} }
$output = ArrayList::create();
$output = new ArrayList;
foreach($this as $item) { foreach($this as $item) {
if($callback($item)) $output->push($item); if(call_user_func($callback, $item, $this)) $output->push($item);
} }
return $output; return $output;
} }

View File

@ -44,4 +44,13 @@ interface SS_Filterable {
*/ */
public function exclude(); public function exclude();
/**
* Return a new instance of this list that excludes any items with these charactaristics
* Filter this List by a callback function. The function will be passed each record of the List in turn,
* and must return true for the record to be included. Returns the filtered list.
*
* @example $list = $list->filterByCallback(function($item, $list) { return $item->Age == 9; })
* @return SS_Filterable
*/
public function filterByCallback($callback);
} }

View File

@ -147,6 +147,29 @@ abstract class SS_ListDecorator extends ViewableData implements SS_List, SS_Sort
return call_user_func_array(array($this->list, 'filter'), $args); return call_user_func_array(array($this->list, 'filter'), $args);
} }
/**
* Note that, in the current implementation, the filtered list will be an ArrayList, but this may change in a
* future implementation.
* @see SS_Filterable::filterByCallback()
*
* @example $list = $list->filterByCallback(function($item, $list) { return $item->Age == 9; })
* @param callable $callback
* @return ArrayList (this may change in future implementations)
*/
public function filterByCallback($callback) {
if(!is_callable($callback)) {
throw new LogicException(sprintf(
"SS_Filterable::filterByCallback() passed callback must be callable, '%s' given",
gettype($callback)
));
}
$output = ArrayList::create();
foreach($this->list as $item) {
if(call_user_func($callback, $item, $this->list)) $output->push($item);
}
return $output;
}
public function limit($limit, $offset = 0) { public function limit($limit, $offset = 0) {
return $this->list->limit($limit, $offset); return $this->list->limit($limit, $offset);
} }

View File

@ -439,6 +439,32 @@ class ArrayListTest extends SapphireTest {
$this->assertEquals($expected, $list->toArray(), 'List should only contain Steve and Steve and Clair'); $this->assertEquals($expected, $list->toArray(), 'List should only contain Steve and Steve and Clair');
} }
/**
* $list = $list->filterByCallback(function($item, $list) { return $item->Age == 21; })
*/
public function testFilterByCallback() {
$list = new ArrayList(array(
array('Name' => 'Steve', 'ID' => 1, 'Age' => 21),
array('Name' => 'Bob', 'ID' => 2, 'Age' => 18),
array('Name' => 'Clair', 'ID' => 2, 'Age' => 21),
array('Name' => 'Oscar', 'ID' => 2, 'Age' => 52),
array('Name' => 'Mike', 'ID' => 3, 'Age' => 43)
));
$list = $list->filterByCallback(function ($item, $list) {
return $item->Age == 21;
});
$expected = array(
new ArrayData(array('Name' => 'Steve', 'ID' => 1, 'Age' => 21)),
new ArrayData(array('Name' => 'Clair', 'ID' => 2, 'Age' => 21)),
);
$this->assertEquals(2, $list->count());
$this->assertEquals($expected, $list->toArray(), 'List should only contain Steve and Clair');
$this->assertTrue($list instanceof SS_Filterable, 'The List should be of type SS_Filterable');
}
/** /**
* $list->exclude('Name', 'bob'); // exclude bob from list * $list->exclude('Name', 'bob'); // exclude bob from list
*/ */

View File

@ -671,6 +671,24 @@ class DataListTest extends SapphireTest {
$this->assertEquals(0, $list->exclude('ID', $obj->ID)->count()); $this->assertEquals(0, $list->exclude('ID', $obj->ID)->count());
} }
/**
* $list = $list->filterByCallback(function($item, $list) { return $item->Age == 21; })
*/
public function testFilterByCallback() {
$team1ID = $this->idFromFixture('DataObjectTest_Team', 'team1');
$list = DataObjectTest_TeamComment::get();
$list = $list->filterByCallback(function ($item, $list) use ($team1ID) {
return $item->TeamID == $team1ID;
});
$result = $list->column('Name');
$expected = array_intersect($result, array('Joe', 'Bob'));
$this->assertEquals(2, $list->count());
$this->assertEquals($expected, $result, 'List should only contain comments from Team 1 (Joe and Bob)');
$this->assertTrue($list instanceof SS_Filterable, 'The List should be of type SS_Filterable');
}
/** /**
* $list->exclude('Name', 'bob'); // exclude bob from list * $list->exclude('Name', 'bob'); // exclude bob from list
*/ */