라라벨 Scout-스카우트
시작하기
라라벨 스카우트는 Eloquent 모델에 full-text 검색 기능을 장착할 수 있는 드라이버 기반의 단순한 솔루션을 제공합니다. 스카우트는 모델 옵저버를 사용하여 엘로퀀트 레코드와 동기화된 검색 인덱스를 자동으로 유지합니다.
현재 스카우트는 Algolia와 MeiliSearch 드라이버를 내장하고 있습니다. 여기에 더해 추가적인 의존성이 필요하지 않은 로컬 개발환경용 "컬렉션" 드라이버도 제공합니다. 그리고 간단하게 커스텀 드라이버를 작성할 수 있으므로, 자유롭게 스카우트를 확장하여 여러분 자신의 검색 기능을 구현할 수 있습니다.
설치하기
먼저, 컴포저 패키지 매니저를 사용하여 스카우트를 설치하십시오:
composer require laravel/scout
스카우트를 설치한 다음, vendor:publish
아티즌 명령어를 사용하여 스카우트 설정 파일을 퍼블리싱 해야합니다. 이 명령을 실행하면 애플리케이션의 config
디렉토리에 scout.php
설정파일이 생성됩니다.
php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"
마지막으로, Laravel\Scout\Searchable
trait를 검색 기능을 장착할 모델에 추가합니다. 이 trait는 모델과 검색 드라이버의 동기화를 자동으로 유지하기 위한 모델 옵저버를 등록할 것입니다.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
class Post extends Model
{
use Searchable;
}
드라이버 사전 준비사항
Algolia
Algolia 드라이버를 사용할 때, Algolia 계정의 id
와 secret
정보를 config/scout.php
설정 파일에 입력해야 합니다. 계정 정보를 설정한 다음에는, 컴포저를 통해 Algolia PHP SDK를 설치해야 합니다.
composer require algolia/algoliasearch-client-php
MeiliSearch
MeiliSearch는 엄청 빠른 속도를 자랑하는 오픈소스 검색엔진입니다. 로컬머신에서 MeiliSearch 를 설치하기 어렵다면 라라벨에서 공식적으로 지원하는 도커 개발환경인 라라벨 Sail을 사용할 수 있습니다.
MeiliSearch 드라이버를 사용하려면 컴포저 패키지 매니저를 통해서 다음과 같이 MeiliSearch PHP SDK 를 설치해야 합니다.
composer require meilisearch/meilisearch-php http-interop/http-factory-guzzle
그 다음에 애플리케이션의 .env
파일에 다음과 같이 드라이버를 지정하고 MeiliSearch host
와 key
자격증명값을 설정해야합니다.
SCOUT_DRIVER=meilisearch
MEILISEARCH_HOST=http://127.0.0.1:7700
MEILISEARCH_KEY=masterKey
보다 자세한 내용은 MeiliSearch 공식문서를 참고하십시오
추가로, MeiliSearch 바이너리 호환성 문서를 확인하여 MeiliSearch 바이너리 버전과 호환되는 meilisearch/meilisearch-php
버전을 설치했는지 확인하십시오.
{note} MeiliSearch를 사용하는 애플리케이션에서 스카우트를 업그레이드 할 때는 항상 MeiliSearch 서비스의 추가적인 변경사항 리뷰를 확인하십시오.
큐 사용하기
스카우트를 사용하기 위한 필수 요구사항은 아니지만, 스카우트를 사용하기에 앞서 큐 드라이버의 구성을 고려해 보아야 합니다. 큐를 사용하면 스카우트는 모델 정보와 검색 인덱스를 싱크하는 모든 작업을 큐에 등록할 것이고, 이는 여러분의 웹사이트가 더 나은 응답시간을 제공할 수 있도록 할 것입니다.
일단 큐 드라이버를 구성한 다음, config/scout.php
설정 파일의 queue
옵션을 true
로 지정하십시오.
'queue' => true,
환경 설정
모델 인덱스 설정하기
각각의 Eloquent 모델은 해당 모델에 대한 모든 검색 레코드를 가지고 있는 검색 "인덱스"와 동기화 됩니다. 다시 말해, 여러분은 각각의 인덱스를 Mysql 테이블처럼 생각할 수 있습니다. 기본적으로, 각각의 모델은 모델의 "테이블" 명과 매칭되는 인덱스에 저장됩니다. 일반적으로, 인덱스 명은 모델 이름의 복수형입니다; 그렇지만 모델의 searchableAs
메서드를 오버라이드 해서 인덱스명을 자유롭게 변경할 수 있습니다.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
class Post extends Model
{
use Searchable;
/**
* Get the name of the index associated with the model.
*
* @return string
*/
public function searchableAs()
{
return 'posts_index';
}
}
검색 데이터 설정하기
기본적으로, 주어진 모델의 toArray
형식 그대로가 검색 인덱스로 유지됩니다. 만약 당신이 검색 인덱스와 동기화되는 데이터를 변경하고 싶다면, 모델의 toSearchableArray
메서드를 오버라이드 할 수 있습니다.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
class Post extends Model
{
use Searchable;
/**
* Get the indexable data array for the model.
*
* @return array
*/
public function toSearchableArray()
{
$array = $this->toArray();
// Customize the data array...
return $array;
}
}
모델 ID 설정하기
기본적으로 Scout-스카우트는 모델의 primary 키를 검색 인덱스에 저장되는 고유한 ID/KEY 로 사용합니다. 이 동작을 커스터마이징 하고자 한다면, 모들의 getScoutKey
와 getScoutKeyName
메서드를 오버라이드 하면 됩니다.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
class User extends Model
{
use Searchable;
/**
* Get the value used to index the model.
*
* @return mixed
*/
public function getScoutKey()
{
return $this->email;
}
/**
* Get the key name used to index the model.
*
* @return mixed
*/
public function getScoutKeyName()
{
return 'email';
}
}
사용자 식별
Scout를 사용하면 Algolia를 사용할 때 사용자를 자동으로 식별 할 수도 있습니다. 인증 된 사용자를 검색 작업과 연결하면 Algolia의 대시 보드 내에서 검색 분석을 볼 때 도움이 될 수 있습니다. 애플리케이션의 .env
파일에서 SCOUT_IDENTIFY
를 true
로 정의하면 사용자 식별을 활성화 할 수 있습니다.
SCOUT_IDENTIFY=true
이 기능을 활성화하면 요청의 IP 주소와 인증 된 사용자의 기본 식별자도 Algolia에 전달되므로이 데이터는 사용자가 만든 모든 검색 요청과 연결됩니다.
로컬 개발
지역 개발 중에 Algolia 또는 MeiliSearch 검색 엔진을 자유롭게 사용할 수 있지만 "수집" 엔진으로 시작하는 것이 더 편리할 수 있습니다. 컬렉션 엔진은 "where" 절과 기존 데이터베이스의 결과에 대한 컬렉션 필터링을 사용하여 쿼리에 적용 가능한 검색 결과를 결정합니다. 이 엔진을 사용할 때 검색 가능한 모델을 "인덱싱"할 필요가 없습니다. 로컬 데이터베이스에서 간단히 검색되기 때문입니다.
컬렉션 엔진을 사용하려면 SCOUT_DRIVER
환경 변수의 값을 collection
으로 설정하거나 애플리케이션의 scout
설정 파일에서 직접 collection
드라이버를 지정할 수 있습니다.
SCOUT_DRIVER=collection
컬렉션 드라이버를 기본 드라이버로 지정했으면 모델에 대해 검색 쿼리 실행을 시작할 수 있습니다. Algolia 또는 MeiliSearch 색인을 시드하는 데 필요한 색인과 같은 검색 엔진 색인은 수집 엔진을 사용할 때 필요하지 않습니다.
인덱싱
배치작업을 통한 데이터 임포트
만약 기존에 존재하는 프로젝트에 스카우트를 추가로 설치했다면, 검색 드라이버로 인덱싱할 데이터베이스 데이터 레코드가 존재할 것입니다. 스카우트는 scout:import
아티즌 명령어를 사용하여 기존에 존재하는 모든 데이터베이스 레코드를 검색 인덱스로 가져오게 할 수 있습니다.
php artisan scout:import "App\Models\Post"
flush
명령어는 검색 인덱스에서 모델의 모든 레코드를 삭제하는데 사용합니다.
php artisan scout:flush "App\Models\Post"
임포트 쿼리 수정하기
배치 작업에서 사용하는 임포트 작업에서 사용되는 쿼리를 수정하려면 모델에 makeAllSearchableUsing
메서드를 정의하면 됩니다. 이 메서드는 모델을 임포팅 하기 전에 필요한 연관관계를 Eager 로딩하도록 지정하기 좋은 곳입니다.
/**
* Modify the query used to retrieve models when making all of the models searchable.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
protected function makeAllSearchableUsing($query)
{
return $query->with('author');
}
레코드 추가하기
모델에 Laravel\Scout\Searchable
trait를 추가했다면, 당신은 모델 인스턴스의 save
또는 create
메서드를 호출하기만 하면 됩니다. 그러면 자동으로 검색 인덱스에 추가될 것입니다. 만약 스카우트에 큐 사용 설정을 했다면, 이 작업은 당신의 큐 워커에 의해 백그라운드에서 실행될 것입니다.
use App\Models\Order;
$order = new Order;
// ...
$order->save();
쿼리를 통해 레코드 추가하기
만약 당신이 엘로퀀트 쿼리를 통해 조회된 모델의 컬렉션을 검색 인덱스에 추가하고 싶다면, 엘로퀀트 쿼리 인스턴스에 searchable
메서드를 체이닝하여 호출하면 됩니다. searchable
메서드는 쿼리의 결과를 묶음으로 처리하여 검색 인덱스에 레코드를 추가할 것입니다. 앞서와 마찬가지로 만약 당신이 스카우트에서 큐를 사용하기로 설정했다면, 모든 묶음은 큐 워커에 의해 백그라운드에서 추가될 것입니다.
use App\Models\Order;
Order::where('price', '>', 100)->searchable();
연관관계에 모델 인스턴스에서도 searchable
메서드를 호출할 수 있습니다.
$user->orders()->searchable();
또는 메모리에 엘로퀀트 모델 컬렉션을 획득한 다음, 컬렉션 인스턴스에서 searchable
메서드를 호출하여 모델에 해당하는 검색 인덱스에서 레코드를 추가 할 수 있습니다.
$orders->searchable();
{tip}
searchable
메서드를 "upsert" 작업으로 생각할 수도 있습니다. 다시 말해, 모델 레코드가 이미 인덱스에 존재한다면, 업데이트될 것입니다. 검색 인덱스에 존재하지 않는다면, 인덱스에 추가될 것입니다.
레코드 업데이트하기
모델을 업데이트하기 위해서는, 모델 인스턴스의 프로퍼티를 업데이트하고, 데이터베이스에 save
하기만 하면 됩니다. 스카우트는 자동으로 변경 사항을 검색 인덱스에 적용할 것입니다.
use App\Models\Order;
$order = Order::find(1);
// Update the order...
$order->save();
여러분은 모델 컬렉션을 업데이트 하는데 엘로퀀트 쿼리 인스턴스의 searchable
메서드를 호출할 수 있습니다. 만약 모델이 검색 인덱스에 존재하지 않는다면, 인덱스는 새로 생성됩니다.
Order::where('price', '>', 100)->searchable();
연관관계에 모델에 대한 검색 인덱스 레코드를 업데이트 하려면 연관관계 인스턴스에서 searchable
메서드를 호출하면 됩니다.
$user->orders()->searchable();
또는 메모리에 엘로퀀트 모델 컬렉션을 획득한 다음, 컬렉션 인스턴스에서 searchable
메서드를 호출하여 모델에 해당하는 검색 인덱스에서 레코드를 업데이트 할 수 있습니다.
$orders->searchable();
레코드 삭제하기
인덱스에서 레코드를 삭제하기 위해서는, 데이터베이스에서 해당 모델의 delete
메서드를 호출하면 됩니다. 이 삭제 방법은 soft deleted가 적용된 모델에서도 작동합니다.
use App\Models\Order;
$order = Order::find(1);
$order->delete();
만약 레코드를 삭제하기 전에 모델을 조회하고 싶지 않다면, 엘로퀀트 쿼리 인스턴스의 unsearchable
메서드를 사용할 수 있습니다.
Order::where('price', '>', 100)->unsearchable();
연관관계의 모든 모델에 대한 검색 인덱스 레코드를 삭제하려면 연관관계 인스턴스에서 unsearchable
메서드를 호출하면 됩니다.
$user->orders()->unsearchable();
또는 메모리에 엘로퀀트 모델 컬렉션을 획득한 다음, 컬렉션 인스턴스에서 unsearchable
메서드를 호출하여 모델에 해당하는 검색 인덱스에서 레코드를 삭제할 수 있습니다.
$orders->unsearchable();
인덱싱 일시 멈춤
가끔 검색 인덱스와 모델 데이터를 동기화하지 않으면서 모델에 엘로퀀트 작업을 일괄처리할 필요가 있습니다. 이때 withoutSyncingToSearch
메서드를 사용하면 됩니다. 이 메서드는 하나의 클로저를 인자로 받아서 바로 실행합니다. 이 클로저 안에서 실행되는 어떤 모델 작업이든 모델의 인덱스와 동기화 되지 않을 것입니다.
use App\Models\Order;
Order::withoutSyncingToSearch(function () {
// Perform model actions...
});
조건에 따른 검색 모델 인스턴스
때로는 특정한 조건에 맞는 경우에만 모델을 검색되도록 만들 수도 있습니다. 예를 들어, App\Models\Post
모델이 "작업중" 과 "발행됨" 두가지 상태를 가지는 경우를 생각해 보겠습니다. "발행됨" 상태의 글만 검색이 가능하기를 원할 수 있습니다. 이를 위해서는, 모델에 shouldBeSearchable
메서드를 정의하면 됩니다.
/**
* Determine if the model should be searchable.
*
* @return bool
*/
public function shouldBeSearchable()
{
return $this->isPublished();
}
shouldBeSearchable
메서드는 save
와 create
메서드, 쿼리 또는 관계 모델을 통해서 모델을 조작한 경우에만 적용됩니다. searchable
메서드를 사용하여 모델 또는 컬렉션을 직접적으로 검색 가능하게 하면, shouldBeSearchable
메서드의 결과를 덮어씌게 됩니다.
검색하기
search
메서드를 사용하여 모델을 검색할 수 있습니다. search 메서드는 모델을 검색할 때 사용될 단일 스트링을 파라미터로 가집니다. 그런 다음, 주어진 검색 쿼리와 일치하는 엘로퀀트 모델을 조회하기 위하여 get
메서드를 연결해서 사용해야 합니다.
use App\Models\Order;
$orders = Order::search('Star Trek')->get();
스카우트는 엘로퀀트 모델의 모음을 반환하기 때문에, 여러분은 라우트나 컨트롤러에서 결과를 바로 반환할 수 있으며, 그 결과는 JSON으로 자동 변환될 것입니다.
use App\Models\Order;
use Illuminate\Http\Request;
Route::get('/search', function (Request $request) {
return Order::search($request->search)->get();
});
검색 결과가 Eloquent 모델로 변환되기 전의 상태를 확인하고 싶다면, raw
메서드를 사용하면 됩니다.
$orders = Order::search('Star Trek')->raw();
커스텀 인덱스
검색 쿼리는 일반적으로 모델의 searchableAs
메서드에 의한 인덱스가 지정되어 실행됩니다. 대신에 within
메서드를 사용하여 검색하고자 하는 커스텀 인덱스를 지정할 수 있습니다.
$orders = Order::search('Star Trek')
->within('tv_shows_popularity_desc')
->get();
Where 클로저
여러분은 검색 쿼리를 위해 "where" 클로저를 스카우트에 추가할 수 있습니다. 현재로서는, 그 클로저는 오직 기본적인 수식을 지원하며, 주로 고유한 ID에 의해 검색 쿼리를 한정할 때 유용합니다.
use App\Models\Order;
$orders = Order::search('Star Trek')->where('user_id', 1)->get();
whereIn
메서드를 사용하여 검색결과에 조건을 추가할 수 있습니다.
$orders = Order::search('Star Trek')->whereIn(
'status', ['paid', 'open']
)->get();
검색 인덱스는 관계형 데이터베이스가 아니므로 현재 고급 "where" 절이 지원되지 않습니다.
페이지네이션
모델 컬렉션의 조회에 더해, paginate
메서드를 사용하여 검색결과를 페이징할 수 있습니다. 엘로퀀트 쿼리에서 페이징을 했던 것처럼, 이 메서드는 Illuminate\Pagination\LengthAwarePaginator
인스턴스를 반환합니다.
use App\Models\Order;
$orders = Order::search('Star Trek')->paginate();
paginate
메서드의 첫번째 파라미터를 사용하여 한 페이지에 검색할 모델의 수량을 지정할 수도 있습니다.
$orders = Order::search('Star Trek')->paginate(15);
결과를 검색한 다음에, 여러분은 엘로퀀트 쿼리에서 페이징했던 것처럼 검색 결과를 출력하고 블레이드를 사용하여 페이지 링크를 출력할 수 있습니다.
<div class="container">
@foreach ($orders as $order)
{{ $order->price }}
@endforeach
</div>
{{ $orders->links() }}
물론, 페이징이 적용된 결과를 JSON으로 조회하려면, 라우트나 컨트롤러에서 직접 Paginator 인스턴스를 반환할수도 있습니다
use App\Models\Order;
use Illuminate\Http\Request;
Route::get('/orders', function (Request $request) {
return Order::search($request->input('query'))->paginate(15);
});
소프트 삭제
인덱싱되는 모델이 소프트 삭제된 상태이고, 소프트 삭제된 모델을 검색할 필요가 있다면, config/scout.php
설정 파일의 soft_delete
옵션을 true
로 지정하면 됩니다.
'soft_delete' => true,
이 설정 값이 true
인 경우, Scout-스카우트는 소프트 삭제된 모델을 검색 인덱스에서 삭제 하지 않습니다. 대신에, 인덱스된 레코드에 __soft_deleted
라는 숨겨진 속성을 설정합니다. 그러면 검색할 때 withTrashed
또는 onlyTrashed
메서드를 사용하여 소프트 삭제된 레코드를 조회할 수 있습니다.
use App\Models\Order;
// Include trashed records when retrieving results...
$orders = Order::search('Star Trek')->withTrashed()->get();
// Only include trashed records when retrieving results...
$orders = Order::search('Star Trek')->onlyTrashed()->get();
{tip}
forceDelete
메서드를 사용하여 소프트 삭제된 모델을 완전히 삭제할 때, Scout-스카우트는 검색 인덱스에서 자동으로 이를 제거합니다.
엔진의 검색 커스터마이징
엔진의 검색 동작을 커스터마이징 할 필요가있는 경우, 클로저를 search
메서드의 두 번째 인수로 전달할 수 있습니다. 예를 들어 검색어를 Algolia에 전달하기 전에 이 콜백을 사용하여 검색 옵션에 지리적 위치 데이터를 추가 할 수 있습니다.
use Algolia\AlgoliaSearch\SearchIndex;
use App\Models\Order;
Order::search(
'Star Trek',
function (SearchIndex $algolia, string $query, array $options) {
$options['body']['query']['bool']['filter']['geo_distance'] = [
'distance' => '1000km',
'location' => ['lat' => 36, 'lon' => 111],
];
return $algolia->search($query, $options);
}
)->get();
커스텀 엔진
엔진 구현하기
만약 내장된 스카우트 검색 엔진들 중 하나도 여러분의 요구사항을 충족하지 못한다면, 여러분은 여러분만의 커스텀 엔진을 구현하고 스카우트에 등록할 수 있습니다. 여러분이 구현하는 엔진은 Laravel\Scout\Engines\Engine
추상클래스를 상속받아야 합니다. 이 추상클래스는 반드시 구현해야하는 7개의 메서드를 포함하고 있습니다.
use Laravel\Scout\Builder;
abstract public function update($models);
abstract public function delete($models);
abstract public function search(Builder $builder);
abstract public function paginate(Builder $builder, $perPage, $page);
abstract public function mapIds($results);
abstract public function map(Builder $builder, $results, $model);
abstract public function getTotalCount($results);
abstract public function flush($model);
Laravel\Scout\Engines\AlgoliaEngine
클래스에 구현된 그 메서드들을 살펴보는 것이 여러분에게 도움이 될 것입니다. 이 클래스는 여러분만의 엔진에 그 메서드를을 어떻게 구현해야 할지 배울 수 있는 좋은 출발점이 될 것입니다.
엔진 등록하기
커스텀엔진을 작성했다면, 스카우트 엔진 매니저의 extend
메서드를 사용하여 그 엔진을 등록할 수 있습니다. 스카우트 엔진 매니저는 라라벨 서비스 컨테이너에서 확인할 수 있습니다. 여러분은 이 extend
메서드를 App\Providers\AppServiceProvider
클래스 또는 애플리케이션에서 사용되는 다른 서비스 프로바이더의 boot
메서드에서 extend
메서드를 호출해야 합니다. 예를 들어, 만약 여러분이 MySqlSearchEngine
을 구현했다면, 다음과 같이 등록할 수 있습니다.
use App\ScoutExtensions\MySqlSearchEngine
use Laravel\Scout\EngineManager;
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
resolve(EngineManager::class)->extend('mysql', function () {
return new MySqlSearchEngine;
});
}
여러분의 엔진을 등록한 즉시, 애플리케이션의 config/scout.php
설정 파일의 driver
를 변경하여 스카우트의 기본 드라이버를 그것으로 지정하십시오.
'driver' => 'mysql',
빌더 매크로
커스텀 스카운 검색 빌더 메서드를 정의하고 싶다면 Laravel\Scout\Builder
클래스에 macro
메서드를 사용할 수 있습니다. 일반적으로 "매크로"는 서비스 프로바이더의 boot
메서드 내에 정의되어야합니다.
use Illuminate\Support\Facades\Response;
use Illuminate\Support\ServiceProvider;
use Laravel\Scout\Builder;
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
Builder::macro('count', function () {
return $this->engine()->getTotalCount(
$this->engine()->search($this)
);
});
}
macro
함수는 첫 번째 인수로 매크로의 이름을 받아들입니다. 두 번째 인수는 클로저입니다. 매크로의 클로저는 Laravel\Scout\Builder
구현에서 매크로 이름을 호출 할 때 실행됩니다.
use App\Models\Order;
Order::search('Star Trek')->count();