Improved filtering capabilities

This commit is contained in:
Maurits van der Schee 2016-11-03 01:49:33 +01:00
commit 1f433ff42e
3 changed files with 59 additions and 37 deletions

View file

@ -168,6 +168,7 @@ GET http://localhost/api.php/categories?filter=name,sw,Inter
GET http://localhost/api.php/categories?filter=id,le,1
GET http://localhost/api.php/categories?filter=id,ngt,2
GET http://localhost/api.php/categories?filter=id,bt,1,1
GET http://localhost/api.php/categories?filter=categories.id,eq,1
```
Output:
@ -176,6 +177,8 @@ Output:
{"categories":{"columns":["id","name"],"records":[["1","Internet"]]}}
```
NB: You may specify table name before the field name, seperated with a dot.
### List + Filter + Satisfy
Multiple filters can be applied by using "filter[]" instead of "filter" as a parameter name. Then the parameter "satisfy" is used to indicate whether "all" (default) or "any" filter should be satisfied to lead to a match:
@ -183,6 +186,7 @@ Multiple filters can be applied by using "filter[]" instead of "filter" as a par
```
GET http://localhost/api.php/categories?filter[]=id,eq,1&filter[]=id,eq,3&satisfy=any
GET http://localhost/api.php/categories?filter[]=id,ge,1&filter[]=id,le,3&satisfy=all
GET http://localhost/api.php/categories?filter[]=id,ge,1&filter[]=id,le,3&satisfy=categories.all
GET http://localhost/api.php/categories?filter[]=id,ge,1&filter[]=id,le,3
```
@ -192,6 +196,8 @@ Output:
{"categories":{"columns":["id","name"],"records":[["1","Internet"],["3","Web development"]]}}
```
NB: You may specify "satisfy=categories.all,posts.any" if you want to mix "and" and "or" for different tables.
### List + Column selection
By default all columns are selected. With the "columns" parameter you can select specific columns. Multiple columns should be comma separated. An asterisk ("*") may be used as a wildcard to indicate "all columns":

76
api.php
View file

@ -946,12 +946,7 @@ class PHP_CRUD_API {
protected function applyRecordFilter($callback,$action,$database,$tables,&$filters) {
if (is_callable($callback,true)) foreach ($tables as $i=>$table) {
$f = $this->convertFilters($callback($action,$database,$table));
if ($f) {
if (!isset($filters[$table])) $filters[$table] = array();
if (!isset($filters[$table]['and'])) $filters[$table]['and'] = array();
$filters[$table]['and'] = array_merge($filters[$table]['and'],$f);
}
$this->addFilters($filters,$table,array($table=>'and'),$callback($action,$database,$table));
}
}
@ -960,10 +955,8 @@ class PHP_CRUD_API {
foreach ($keys as $field) {
$v = $callback($action,$database,$table,$field->name);
if ($v!==null) {
if (!isset($filters[$table])) $filters[$table] = array();
if (!isset($filters[$table]['and'])) $filters[$table]['and'] = array();
if (is_array($v)) $filters[$table]['and'][] = $this->convertFilter($field->name,'in',implode(',',$v));
else $filters[$table]['and'][] = $this->convertFilter($field->name,'eq',$v);
if (is_array($v)) $this->addFilter($filters,$table,'and',$field->name,'in',implode(',',$v));
else $this->addFilter($filters,$table,'and',$field->name,'eq',$v);
}
}
}
@ -1191,28 +1184,44 @@ class PHP_CRUD_API {
return false;
}
public function convertFilters($filters) {
$result = array();
if ($filters) {
for ($i=0;$i<count($filters);$i++) {
$parts = explode(',',$filters[$i],3);
public function addFilter(&$filters,$table,$and,$field,$comparator,$value) {
if (!isset($filters[$table])) $filters[$table] = array();
if (!isset($filters[$table][$and])) $filters[$table][$and] = array();
$filter = $this->convertFilter($field,$comparator,$value);
if ($filter) $filters[$table][$and][] = $filter;
}
public function addFilters(&$filters,$table,$satisfy,$filterStrings) {
if ($filterStrings) {
for ($i=0;$i<count($filterStrings);$i++) {
$parts = explode(',',$filterStrings[$i],3);
if (count($parts)>=2) {
$field = $parts[0];
if (strpos($parts[0],'.')) list($t,$f) = explode('.',$parts[0],2);
else list($t,$f) = array($table,$parts[0]);
$comparator = $parts[1];
$value = isset($parts[2])?$parts[2]:null;
$filter = $this->convertFilter($field,$comparator,$value);
if ($filter) $result[] = $filter;
$and = isset($satisfy[$t])?$satisfy[$t]:'and';
$this->addFilter($filters,$t,$and,$f,$comparator,$value);
}
}
}
return $result;
}
protected function processFiltersParameter($tables,$satisfy,$filters) {
$result = $this->convertFilters($filters);
if (!$result) return array();
$and = ($satisfy && strtolower($satisfy)=='any')?'or':'and';
return array($tables[0]=>array($and=>$result));
protected function processSatisfyParameter($tables,$satisfyString) {
$satisfy = array();
foreach (explode(',',$satisfyString) as $str) {
if (strpos($str,'.')) list($t,$s) = explode('.',$str,2);
else list($t,$s) = array($tables[0],$str);
$and = ($s && strtolower($s)=='any')?'or':'and';
$satisfy[$t] = $and;
}
return $satisfy;
}
protected function processFiltersParameter($tables,$satisfy,$filterStrings) {
$filters = array();
$this->addFilters($filters,$tables[0],$satisfy,$filterStrings);
return $filters;
}
protected function processPageParameter($page) {
@ -1231,9 +1240,7 @@ class PHP_CRUD_API {
$this->convertOutputs($sql,$params,$fields[$table]);
$sql .= ' FROM !';
$params[] = $table;
if (!isset($filters[$table])) $filters[$table] = array();
if (!isset($filters[$table]['and'])) $filters[$table]['and'] = array();
$filters[$table]['and'][] = $this->convertFilter($key[1],'eq',$key[0]);
$this->addFilter($filters,$table,'and',$key[1],'eq',$key[0]);
$this->addWhereFromFilters($filters[$table],$sql,$params);
$object = null;
if ($result = $this->db->query($sql,$params)) {
@ -1289,9 +1296,7 @@ class PHP_CRUD_API {
$params[] = $k;
$params[] = $v;
}
if (!isset($filters[$table])) $filters[$table] = array();
if (!isset($filters[$table]['and'])) $filters[$table]['and'] = array();
$filters[$table]['and'][] = $this->convertFilter($key[1],'eq',$key[0]);
$this->addFilter($filters,$table,'and',$key[1],'eq',$key[0]);
$this->addWhereFromFilters($filters[$table],$sql,$params);
$result = $this->db->query($sql,$params);
if (!$result) return null;
@ -1302,9 +1307,7 @@ class PHP_CRUD_API {
$table = $tables[0];
$sql = 'DELETE FROM !';
$params = array($table);
if (!isset($filters[$table])) $filters[$table] = array();
if (!isset($filters[$table]['and'])) $filters[$table]['and'] = array();
$filters[$table]['and'][] = $this->convertFilter($key[1],'eq',$key[0]);
$this->addFilter($filters,$table,'and',$key[1],'eq',$key[0]);
$this->addWhereFromFilters($filters[$table],$sql,$params);
$result = $this->db->query($sql,$params);
if (!$result) return null;
@ -1467,13 +1470,14 @@ class PHP_CRUD_API {
$callback = $this->parseGetParameter($get, 'callback', 'a-zA-Z0-9\-_');
$page = $this->parseGetParameter($get, 'page', '0-9,');
$filters = $this->parseGetParameterArray($get, 'filter', false);
$satisfy = $this->parseGetParameter($get, 'satisfy', 'a-zA-Z');
$satisfy = $this->parseGetParameter($get, 'satisfy', 'a-zA-Z0-9\-_,.');
$columns = $this->parseGetParameter($get, 'columns', 'a-zA-Z0-9\-_,.*');
$order = $this->parseGetParameter($get, 'order', 'a-zA-Z0-9\-_,');
$transform = $this->parseGetParameter($get, 'transform', 't1');
$tables = $this->processTableAndIncludeParameters($database,$table,$include,$action);
$key = $this->processKeyParameter($key,$tables,$database);
$satisfy = $this->processSatisfyParameter($tables,$satisfy);
$filters = $this->processFiltersParameter($tables,$satisfy,$filters);
$page = $this->processPageParameter($page);
$order = $this->processOrderParameter($order);
@ -1608,9 +1612,7 @@ class PHP_CRUD_API {
foreach ($select[$table] as $field => $path) {
$values = $collect[$path[0]][$path[1]];
if ($values) {
if (!isset($filters[$table])) $filters[$table] = array();
if (!isset($filters[$table]['and'])) $filters[$table]['and'] = array();
$filters[$table]['and'][] = $this->convertFilter($field,'in',implode(',',$values));
$this->addFilter($filters,$table,'and',$field,'in',implode(',',$values));
}
if ($first_row) $first_row = false;
else echo ',';

View file

@ -569,6 +569,20 @@ class PHP_CRUD_API_Test extends PHPUnit_Framework_TestCase
$test->expect('{"posts":[{"category_id":"1","categories":[{"id":"1","name":"announcement"}]}]}');
}
public function testFilterOnRelationAnd()
{
$test = new API($this);
$test->get('/categories?include=posts&filter[]=id,ge,1&filter[]=id,le,1&filter[]=id,le,2&filter[]=posts.id,lt,8&filter[]=posts.id,gt,4');
$test->expect('{"categories":{"columns":["id","name","icon"],"records":[["1","announcement",null]]},"posts":{"relations":{"category_id":"categories.id"},"columns":["id","user_id","category_id","content"],"records":[["5","1","1","#1"],["6","1","1","#2"],["7","1","1","#3"]]}}');
}
public function testFilterOnRelationOr()
{
$test = new API($this);
$test->get('/categories?include=posts&filter[]=id,ge,1&filter[]=id,le,1&filter[]=posts.id,eq,5&filter[]=posts.id,eq,6&filter[]=posts.id,eq,7&satisfy=all,posts.any');
$test->expect('{"categories":{"columns":["id","name","icon"],"records":[["1","announcement",null]]},"posts":{"relations":{"category_id":"categories.id"},"columns":["id","user_id","category_id","content"],"records":[["5","1","1","#1"],["6","1","1","#2"],["7","1","1","#3"]]}}');
}
public function testColumnsOnWrongInclude()
{
$test = new API($this);