搜索
Orion 为你的 API 端点提供了全面的搜索能力,包括排序、过滤、关键词搜索、聚合与包含。
// (POST) https://myapp.com/api/posts/search
{
"scopes" : [
{"name" : "active"},
{"name" : "whereCategory", "parameters" : ["my-category"]}
],
"filters" : [
{"field" : "created_at", "operator" : ">=", "value" : "2020-01-01"},
{"field" : "options->visible", "operator" : ">=", "value" : true},
{"type" : "or", "field" : "meta.source_id", "operator" : "in", "value" : [1,2,3]}
],
"search" : {
"value" : "Example post"
},
"sort" : [
{"field" : "name", "direction" : "asc"},
{"field" : "options->key", "direction" : "asc"},
{"field" : "meta.priority", "direction" : "desc"}
],
"aggregates": [
{
"relation": "tags",
"type": "count",
"filters": [
{"field" : "tags.created_at", "operator" : ">=", "value" : "2020-01-01"}
]
}
],
"includes": [
{
"relation": "tags",
"filters": [
{"field" : "tags.created_at", "operator" : ">=", "value" : "2020-01-01"}
]
}
]
}
scopes -> filters -> search -> sort -> includes -> aggregates。过滤
过滤数据有两种方式——查询作用域(scope)和过滤器(filter)。
推荐使用查询作用域,因为查询约束封装在 API 内部(模型上的 scope 方法),当这些约束发生变化时,前端(终端客户端)无需做任何改动。
而过滤器则提供了非常灵活的查询约束应用方式,就像你通过 Eloquent 查询构造器进行操作一样。
应用作用域
首先,需要在控制器上设置通过 API 暴露的作用域列表:
<?php
namespace App\Http\Controllers\Api;
use Orion\Http\Controllers\Controller;
class PostsController extends Controller
{
...
/**
* 可用查询作用域列表。
*
* @return array
*/
public function exposedScopes() : array
{
return ['active', 'whereCategory'];
}
...
}
若要实际使用一个或多个作用域来过滤数据,请向 search 端点发送请求,并在请求体中包含 scopes 属性,指定作用域的 name 及其 parameters:
// (POST) https://myapp.com/api/posts/search
{
"scopes" : [
{"name" : "active"},
{"name" : "whereCategory", "parameters" : ["my-category"]}
],
}
应用过滤器
如果你需要对查询约束进行细粒度控制,使用过滤器可能是更好的选择。与暴露作用域类似,我们需要将可用于过滤的字段加入白名单:
<?php
namespace App\Http\Controllers\Api;
use Orion\Http\Controllers\Controller;
class PostsController extends Controller
{
...
/**
* 用于过滤的属性。
*
* @return array
*/
public function filterableBy() : array
{
return ['id', 'title', 'options->visible', 'user.id', 'meta.source_id', 'created_at'];
}
...
}
在发送到 search 端点的请求中包含 filters 属性:
// (POST) https://myapp.com/api/posts/search
{
"filters" : [
{"field" : "created_at", "operator" : ">=", "value" : "2020-01-01"},
{"field" : "options->visible", "operator" : ">=", "value" : true},
{"type" : "or", "field" : "meta.source_id", "operator" : "in", "value" : [1,2,3]}
]
}
从上面的示例可以看到,每个过滤描述符由以下属性组成:type(可选)、field、operator 和 value。
field 属性的值就是白名单中的某个属性。
type 属性(默认为 and)作为组合多个过滤器的逻辑运算符,可以是 and 或 or。在底层,它决定应用过滤器时在查询构造器上使用 where 还是 orWhere 方法。
operator 属性必须是受支持的比较运算符之一:
'<', '<=', '>', '>=', '=', '!=', 'like', 'not like', 'ilike', 'not ilike', 'in', 'not in', 'all in', 'any in'
这些运算符(all in 和 any in 除外)与你平时在 Eloquent 查询构造器上传给 ->where('<some field>', '<operator>', '<value>') 调用的运算符完全相同。
以下运算符用于 json / jsonb 列,本质上是应用 whereJsonContains 约束:
'all in', 'any in'
all in 与 any in 的区别在于:应用 all in 时,要求给定的所有值都存在于该列中,实体才会被包含在结果里;而 any in 只要求至少有一个值存在。
最后同样重要的是 value——属性必须具有的实际值,用于满足指定的比较条件。
嵌套过滤器
如果你想按「分组」的方式应用过滤器,嵌套过滤器功能正是你需要的。
// (POST) https://myapp.com/api/posts/search
{
"filters" : [
{"field" : "created_at", "operator" : ">=", "value" : "2020-01-01"},
{"type": "or", "nested" : [
{"field" : "options->visible", "operator" : "=", "value" : true},
{"type" : "and", "field" : "meta.source_id", "operator" : "in", "value" : [1,2,3]}
]}
]
}
在上面的请求中,当 created_at 字段大于或等于 2020-01-01,或者 options->visible 字段等于 true 且 meta.source_id 字段的值在数组 [1,2,3] 中时,API 才会返回这些实体。
上述条件可以用伪语言表达如下:
(created_at >= "2020-01-01") OR (options->visible = true AND meta.source_id IN [1,2,3])
嵌套过滤器可以无限添加,但默认深度限制为 1,以防止该功能被过度使用,因为每层嵌套过滤器都会增加底层数据库查询的整体「复杂度」。
orion.php 配置文件中的 search.max_nested_depth。user.id 和 meta.source_id 就是这类属性。多对多关联的资源还可以按其中间表(pivot)的值进行过滤。只需使用
pivot.<field> 表示法,其中 <field> 是中间表上的字段。也可以基于 json 字段内部的值来过滤结果,只需使用「箭头」表示法将它们与其他属性一起加入白名单即可。 上面示例中的
options->visible 就是这类属性。关键词搜索
这类搜索是你通常会用到的功能,例如网站上的搜索框,用于查找所有包含「Laravel is awesome」这一短语的博客文章。 首先,你需要定义执行搜索时所涉及的字段列表:
orion.php 配置中将 search.case-sensitive 设为 false 来改变此行为。<?php
namespace App\Http\Controllers\Api;
use Orion\Http\Controllers\Controller;
class PostsController extends Controller
{
...
/**
* 用于搜索的属性。
*
* @return array
*/
public function searchableBy() : array
{
return ['title', 'description', 'options->key', 'user.name'];
}
...
}
在发送到 search 端点的请求中包含 search 属性:
// (POST) https://myapp.com/api/posts/search
{
"search" : {
"value" : "Laravel is awesome",
"case_sensitive": false // (默认值:true)
},
}
orion.php 配置中的 search.case-sensitive 设为 true,你也可以通过在请求中提供 case_sensitive: false 字段来执行不区分大小写的搜索。目前,搜索是通过数据库查询在所有指定字段上执行的。
对 Algolia 和 ElasticSearch 的支持也在计划之中。
user.name 就是这类属性。也可以对 json 字段内部的值执行搜索,只需使用「箭头」表示法将它们与其他属性一起加入白名单即可。 上面示例中的
options->key 就是这类属性。排序
与为过滤器加入字段白名单的方式类似,排序字段也需要事先指定:
<?php
namespace App\Http\Controllers\Api;
use Orion\Http\Controllers\Controller;
class PostsController extends Controller
{
...
/**
* 用于排序的属性。
*
* @return array
*/
public function sortableBy() : array
{
return ['id', 'name', 'options->key', 'meta.priority'];
}
...
}
在发送到 search 端点的请求中包含 search 属性:
// (POST) https://myapp.com/api/posts/search
{
"sort" : [
{"field" : "name", "direction" : "asc"},
{"field" : "options->key", "direction" : "asc"},
{"field" : "meta.priority", "direction" : "desc"}
]
}
每个排序描述符由以下属性组成:field 和 direction。
field 属性的值就是白名单中的某个属性,direction 则是 asc 或 desc。
meta.priority 就是这类属性。多对多关联的资源还可以按其中间表(pivot)的值进行排序。只需使用
pivot.<field> 表示法,其中 <field> 是中间表上的字段。也可以基于 json 字段内部的值对结果排序,只需使用「箭头」表示法将它们与其他属性一起加入白名单即可。 上面示例中的
options->key 就是这类属性。聚合
要使用聚合,需要将相关的关联和/或字段加入白名单。
可用的聚合有以下几种:count、avg、sum、min、max 和 exists。
<?php
namespace App\Http\Controllers\Api;
use Orion\Http\Controllers\Controller;
class PostsController extends Controller
{
...
/**
* 允许在资源上进行聚合的关联和字段。
*
* @return array
*/
public function aggregates() : array
{
return ['user', 'user.team', 'user.profile', 'meta'];
}
...
}
也可以使用通配符,减少逐一定义所有关联和/或字段的开销:
<?php
namespace App\Http\Controllers\Api;
use Orion\Http\Controllers\Controller;
class PostsController extends Controller
{
...
/**
* 允许在资源上进行聚合的关联和字段。
*
* @return array
*/
public function aggregates() : array
{
return ['user.*', 'meta'];
}
...
}
在发送到 search 端点的请求中包含 aggregates 属性:
// (POST) https://myapp.com/api/posts/search
{
"aggregates" : [
{"type" : "count", "relation" : "tags"},
{"type" : "exists", "relation" : "tags"},
{"type" : "avg", "relation" : "tags", "field": "stars"},
{"type" : "sum", "relation" : "tags", "field": "stars"},
{"type" : "min", "relation" : "tags", "field": "stars"},
{"type" : "max", "relation" : "tags", "field": "stars"}
]
}
count 和 exists 聚合与其他聚合的工作方式不同,只需要 relation 字段。应用过滤器
你也可以为聚合指定过滤器,并且支持嵌套过滤器。
{
"aggregates": [
{
"relation": "tags",
"type": "count",
"filters": [
{"field" : "tags.created_at", "operator" : ">=", "value" : "2020-01-01"}
]
},
{
"relation": "tags",
"field": "stars",
"type": "avg",
"filters": [
{"field" : "tags.created_at", "operator" : ">=", "value" : "2020-01-01"},
{"nested": [
{"field": "tags.id", "operator": "=", "value": 1},
{"field": "tags.id", "operator": ">", "value": 10, "type": "or"}
]}
]
}
]
}
filterableBy 方法中加入白名单。包含
有时你可能希望在返回资源的同时包含其关联。与聚合一样,包含也需要先加入白名单。
<?php
namespace App\Http\Controllers\Api;
use Orion\Http\Controllers\Controller;
class PostsController extends Controller
{
...
/**
* 允许与资源一起包含的关联。
*
* @return array
*/
public function includes() : array
{
return ['user', 'user.team', 'user.profile', 'meta'];
}
...
}
也可以使用通配符,减少逐一定义所有关联的开销:
<?php
namespace App\Http\Controllers\Api;
use Orion\Http\Controllers\Controller;
class PostsController extends Controller
{
...
/**
* 允许与资源一起包含的关联。
*
* @return array
*/
public function includes() : array
{
return ['user.*', 'meta'];
}
...
}
在发送到 search 端点的请求中包含 includes 属性:
// (POST) https://myapp.com/api/posts/search
{
"includes" : [
{"relation" : "tags", "limit" : 10},
{"relation" : "comments"}
]
}
limit 字段来限制返回的关联实体数量。始终包含的关联
要在不通过查询参数传递的情况下默认加载关联,请使用 alwaysIncludes 方法:
<?php
namespace App\Http\Controllers\Api;
use Orion\Http\Controllers\Controller;
class PostsController extends Controller
{
...
/**
* 默认与资源一起加载的关联。
*
* @return array
*/
public function alwaysIncludes() : array
{
return ['user', 'meta'];
}
...
}
include 方法不同,alwaysIncludes 方法不支持通配符。应用过滤器
你也可以为包含指定过滤器,并且支持嵌套过滤器。
{
"includes": [
{
"relation": "comments",
"filters": [
{"field" : "comments.created_at", "operator" : ">=", "value" : "2020-01-01"}
]
},
{
"relation": "tags",
"filters": [
{"field" : "tags.created_at", "operator" : ">=", "value" : "2020-01-01"},
{"nested": [
{"field": "tags.id", "operator": "=", "value": 1},
{"field": "tags.id", "operator": ">", "value": 20, "type": "or"}
]}
]
}
]
}
filterableBy 方法中加入白名单。