검색
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.필터링
데이터를 필터링하는 방법에는 쿼리 스코프와 필터, 두 가지가 있습니다.
쿼리 스코프 사용을 권장합니다. 쿼리 제약 조건이 API(모델의 스코프 메서드) 안에 캡슐화되어 있으므로, 제약 조건이 변경되더라도 프런트엔드(엔드 클라이언트)를 수정할 필요가 없기 때문입니다.
반면 필터는 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'];
}
...
}
하나 이상의 스코프로 실제 데이터를 필터링하려면, 검색 엔드포인트에 요청을 보내면서 페이로드에 스코프의 name과 parameters를 담은 scopes 속성을 포함하세요:
// (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'];
}
...
}
검색 엔드포인트에 보내는 요청에 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 이상이거나(OR) options->visible 필드가 true이면서(AND) 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.<field> 표기법을 사용하면 되며, 여기서 <field>는 피벗 테이블의 필드입니다."화살표(arrow)" 표기법을 사용해 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 속성을 포함하세요:
// (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이 그러한 속성 중 하나입니다."화살표(arrow)" 표기법을 사용해 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 속성을 포함하세요:
// (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.<field> 표기법을 사용하면 되며, 여기서 <field>는 피벗 테이블의 필드입니다."화살표(arrow)" 표기법을 사용해 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'];
}
...
}
검색 엔드포인트에 보내는 요청에 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'];
}
...
}
검색 엔드포인트에 보내는 요청에 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 메서드에서 허용 목록에 등록되어 있어야 합니다.