NEW DataList->filterAny() #870

Merged
merged 1 commit into from Nov 2, 2012
View
24 docs/en/topics/datamodel.md
@@ -161,12 +161,30 @@ Then there is the most complex task when you want to find Sam and Sig that has e
'FirstName' => array('Sam', 'Sig'),
'Age' => array(17, 74)
));
+ // SQL: WHERE ("FirstName" IN ('Sam', 'Sig) AND "Age" IN ('17', '74))
-This would be equivalent to a SQL query of
+In case you want to match multiple criteria non-exclusively (with an "OR" disjunctive),
+use the `filterAny()` method instead:
- :::
- ... WHERE ("FirstName" IN ('Sam', 'Sig) AND "Age" IN ('17', '74));
+ :::php
+ $members = Member::get()->filterAny(array(
+ 'FirstName' => 'Sam',
+ 'Age' => 17,
+ ));
+ // SQL: WHERE ("FirstName" = 'Sam' OR "Age" = '17')
+
+You can also combine both conjunctive ("AND") and disjunctive ("OR") statements.
+ :::php
+ $members = Member::get()
+ ->filter(array(
+ 'LastName' => 'Minnée'
+ ))
+ ->filterAny(array(
+ 'FirstName' => 'Sam',
+ 'Age' => 17,
+ ));
+ // SQL: WHERE ("LastName" = 'Minnée' AND ("FirstName" = 'Sam' OR "Age" = '17'))
### Exclude
View
62 model/DataList.php
@@ -386,6 +386,68 @@ public function addFilter($filterArray) {
}
/**
+ * Return a copy of this list which does not contain items matching any of these charactaristics.
+ *
+ * @example // filter bob from list
+ * $list = $list->filterAny('Name', 'bob');
+ * // SQL: WHERE "Name" = 'bob'
@chillu
SilverStripe Ltd. member
chillu added a line comment Oct 12, 2012

Putting SQL in the docs slightly defeats the purpose of the abstraction layer, but its a much more distinct way of actually describing the feature to people who know SQL, so worth the tradeoff in my opinion.

@wilr
SilverStripe Ltd. member
wilr added a line comment Oct 17, 2012

Agreeed, we use SQL explanations elsewhere so keeps it consistent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ * @example // filter aziz and bob from list
+ * $list = $list->filterAny('Name', array('aziz', 'bob');
+ * // SQL: WHERE ("Name" IN ('aziz','bob'))
+ * @example // filter by bob or anybody aged 21
+ * $list = $list->filterAny(array('Name'=>'bob, 'Age'=>21));
+ * // SQL: WHERE ("Name" = 'bob' OR "Age" = '21')
+ * @example // filter by bob or anybody aged 21 or 43
+ * $list = $list->filterAny(array('Name'=>'bob, 'Age'=>array(21, 43)));
+ * // SQL: WHERE ("Name" = 'bob' OR ("Age" IN ('21', '43'))
+ * @example // bob age 21 or 43, phil age 21 or 43 would be excluded
+ * $list = $list->filterAny(array('Name'=>array('bob','phil'), 'Age'=>array(21, 43)));
+ * // SQL: WHERE (("Name" IN ('bob', 'phil')) OR ("Age" IN ('21', '43'))
+ *
+ * @todo extract the sql from this method into a SQLGenerator class
+ *
+ * @param string|array See {@link filter()}
+ * @return DataList
+ */
+ public function filterAny() {
+ $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('Incorrect number of arguments passed to exclude()');
+ }
+
+ return $this->alterDataQuery(function($query, $list) use ($whereArguments) {
+ $subquery = $query->disjunctiveGroup();
+
+ foreach($whereArguments as $field => $value) {
+ $fieldArgs = explode(':', $field);
+ $field = array_shift($fieldArgs);
+ $filterType = array_shift($fieldArgs);
+ $modifiers = $fieldArgs;
+
+ // This is here since PHP 5.3 can't call protected/private methods in a closure.
+ $t = singleton($list->dataClass())->dbObject($field);
+ if($filterType) {
+ $className = "{$filterType}Filter";
+ } else {
+ $className = 'ExactMatchFilter';
+ }
+ if(!class_exists($className)){
+ $className = 'ExactMatchFilter';
+ array_unshift($modifiers, $filterType);
+ }
+ $t = new $className($field, $value, $modifiers);
+ $t->apply($subquery);
+ }
+ });
+ }
+
+ /**
* 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.
View
85 tests/model/DataListTest.php
@@ -457,6 +457,91 @@ public function testFilterWithModifiers() {
$this->assertEquals(2, $gtList->count());
}
+ public function testFilterAny() {
+ $list = DataObjectTest_TeamComment::get();
+ $list = $list->filterAny('Name', 'Bob');
+ $this->assertEquals(1, $list->count());
+ }
+
+ public function testFilterAnyMultipleArray() {
+ $list = DataObjectTest_TeamComment::get();
+ $list = $list->filterAny(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 testFilterAnyOnFilter() {
+ $list = DataObjectTest_TeamComment::get();
+ $list = $list->filter(array(
+ 'TeamID'=>$this->idFromFixture('DataObjectTest_Team', 'team1')
+ ));
+ $list = $list->filterAny(array(
+ 'Name'=>array('Phil', 'Joe'),
+ 'Comment'=>'This is a team comment by Bob'
+ ));
+ $list = $list->sort('Name');
+ $this->assertEquals(2, $list->count());
+ $this->assertEquals(
+ 'Bob',
+ $list->offsetGet(0)->Name,
+ 'Results should include comments from Bob, matched by comment and team'
+ );
+ $this->assertEquals(
+ 'Joe',
+ $list->offsetGet(1)->Name,
+ 'Results should include comments by Joe, matched by name and team (not by comment)'
+ );
+
+ $list = DataObjectTest_TeamComment::get();
+ $list = $list->filter(array(
+ 'TeamID'=>$this->idFromFixture('DataObjectTest_Team', 'team1')
+ ));
+ $list = $list->filterAny(array(
+ 'Name'=>array('Phil', 'Joe'),
+ 'Comment'=>'This is a team comment by Bob'
+ ));
+ $list = $list->sort('Name');
+ $list = $list->filter(array('Name' => 'Bob'));
+ $this->assertEquals(1, $list->count());
+ $this->assertEquals(
+ 'Bob',
+ $list->offsetGet(0)->Name,
+ 'Results should include comments from Bob, matched by name and team'
+ );
+ }
+
+ public function testFilterAnyMultipleWithArrayFilter() {
+ $list = DataObjectTest_TeamComment::get();
+ $list = $list->filterAny(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');
+ }
+
+ public function testFilterAnyArrayInArray() {
+ $list = DataObjectTest_TeamComment::get();
+ $list = $list->filterAny(array(
+ 'Name'=>array('Bob','Phil'),
+ 'TeamID'=>array($this->idFromFixture('DataObjectTest_Team', 'team1'))))
+ ->sort('Name');
+ $this->assertEquals(3, $list->count());
+ $this->assertEquals(
+ 'Bob',
+ $list->offsetGet(0)->Name,
+ 'Results should include comments from Bob, matched by name and team'
+ );
+ $this->assertEquals(
+ 'Joe',
+ $list->offsetGet(1)->Name,
+ 'Results should include comments by Joe, matched by team (not by name)'
+ );
+ $this->assertEquals(
+ 'Phil',
+ $list->offsetGet(2)->Name,
+ 'Results should include comments from Phil, matched by name (even if he\'s not in Team1)'
+ );
+ }
+
public function testFilterAndExcludeById() {
$id = $this->idFromFixture('DataObjectTest_SubTeam', 'subteam1');
$list = DataObjectTest_SubTeam::get()->filter('ID', $id);