시작하기
Laravel Scout 는 Eloquent 모델 에 전문 검색 기능을 추가하기 위한, 심플한 드라이버 기반의 솔루션입니다. 모델 옵저버를 사용해, Eloquent 레코드와 검색 인덱스를 자동적으로 동기합니다.
Scout에는, MySQL / PostgreSQL의 전문 인덱스와 LIKE 절을 사용해 데이터베이스를 직접 검색하는 조립된 database 엔진이 포함되어 있어, 외부 서비스는 불필요합니다. 대규모의 프로덕션 환경에서 오타 허용·패싯 검색·지오 서치가 필요한 경우는, 외부 엔진이 도움이 됩니다.
대응 엔진 일람
엔진 특징 용도 databaseMySQL / PostgreSQL의 전문 인덱스 대부분의 앱 collectionPHP로 필터링 (SQLite도 가능) 로컬 개발·테스트 Meilisearch 오픈 소스, 오타 허용, 고속 프로덕션 환경 Algolia 클라우드 SaaS, 고도의 기능 프로덕션 환경 Typesense 오픈 소스, 벡터 검색 대응 프로덕션 환경
Composer로 패키지를 설치합니다.
composer require laravel/scout
설치 후, vendor:publish 명령으로 설정 파일을 공개합니다. config/scout.php가 생성됩니다.
php artisan vendor:publish --provider= "Laravel\Scout\ScoutServiceProvider"
마지막으로, 검색 가능하게 하고 싶은 모델에 Laravel\Scout\Searchable 트레이트를 추가합니다. 이 트레이트가 모델 옵저버를 등록하고, 검색 드라이버와의 자동 동기를 활성화합니다.
<? php
namespace App\Models ;
use Illuminate\Database\Eloquent\ Model ;
use Laravel\Scout\ Searchable ;
class Post extends Model
{
use Searchable ;
}
큐 설정
database 또는 collection 엔진 이외를 사용하는 경우, Scout를 사용하기 전에 큐 드라이버 의 설정을 강력히 권장합니다. 큐 워커를 동작시킴으로써, 인덱스 동기 조작이 백그라운드에서 실행되고, 웹 인터페이스의 응답 속도가 대폭 향상됩니다.
config/scout.php의 queue 옵션을 true로 설정합니다.
접속명과 큐명을 지정할 수도 있습니다.
'queue' => [
'connection' => 'redis' ,
'queue' => 'scout'
],
설정 후, 전용의 큐 워커를 기동합니다.
php artisan queue:work redis --queue=scout
유니크한 잡의 사용
쓰기가 많은 애플리케이션에서는, 같은 모델 레코드에 대한 중복된 큐 잡이 큐에 등록되는 것을 막고 싶은 경우가 있습니다. config/scout.php에서 MakeSearchableUniquely와 RemoveFromSearchUniquely 잡 클래스를 등록함으로써, 유니크한 인덱스 잡을 사용할 수 있습니다. 통상, 이것은 서비스 프로바이더의 boot 메서드에서 설정합니다.
use Laravel\Scout\Jobs\ MakeSearchableUniquely ;
use Laravel\Scout\Jobs\ RemoveFromSearchUniquely ;
use Laravel\Scout\ Scout ;
Scout :: makeSearchableUsing ( MakeSearchableUniquely :: class );
Scout :: removeFromSearchUsing ( RemoveFromSearchUniquely :: class );
이러한 잡은 Laravel의 유니크한 잡 락 을 사용해, 이미 큐에 등록되어 있는 같은 모델 레코드의 중복된 인덱스 조작을 디스패치하는 것을 막습니다.
드라이버의 전제 조건
Algolia
Algolia 드라이버를 사용하는 경우는, config/scout.php에 id와 secret의 인증 정보를 설정하고, Algolia PHP SDK를 설치합니다.
composer require algolia/algoliasearch-client-php
.env 파일에 인증 정보를 추가합니다.
ALGOLIA_APP_ID =your-app-id
ALGOLIA_SECRET =your-secret-key
인덱스 설정
Algolia에서는 config/scout.php에서 인덱스 설정을 관리할 수 있습니다.
use App\Models\ User ;
'algolia' => [
'id' => env ( 'ALGOLIA_APP_ID' , '' ),
'secret' => env ( 'ALGOLIA_SECRET' , '' ),
'index-settings' => [
User :: class => [
'searchableAttributes' => [ 'id' , 'name' , 'email' ],
'attributesForFaceting' => [ 'filterOnly(email)' ],
],
],
],
설정 후, scout:sync-index-settings 명령을 실행해 Algolia에 설정을 반영시킵니다.
php artisan scout:sync-index-settings
Meilisearch
Meilisearch 는 고속의 오픈 소스 검색 엔진입니다. 로컬 개발에서는 Laravel Sail 의 Docker를 사용하는 것이 가장 간단합니다.
# Laravel Sail을 사용하는 경우
./vendor/bin/sail up -d meilisearch
Sail을 사용하지 않는 경우는, Docker로 직접 기동할 수 있습니다.
docker run -it --rm \
-p 7700:7700 \
getmeili/meilisearch:latest \
meilisearch --master-key= "masterKey"
Meilisearch PHP SDK를 설치합니다.
composer require meilisearch/meilisearch-php http-interop/http-factory-guzzle
.env 파일에 드라이버와 호스트를 설정합니다.
SCOUT_DRIVER =meilisearch
MEILISEARCH_HOST =http://127.0.0.1:7700
MEILISEARCH_KEY =masterKey
Scout를 업그레이드할 때는, Meilisearch 서비스 자체의 파괴적 변경 도 반드시 확인해 주세요.
인덱스 설정 (Meilisearch)
Meilisearch에서는 where()로 필터링하는 컬럼을 filterableAttributes에, orderBy()로 정렬하는 컬럼을 sortableAttributes에 사전 등록할 필요가 있습니다.
use App\Models\ User ;
'meilisearch' => [
'host' => env ( 'MEILISEARCH_HOST' , 'http://localhost:7700' ),
'key' => env ( 'MEILISEARCH_KEY' , null ),
'index-settings' => [
User :: class => [
'filterableAttributes' => [ 'id' , 'name' , 'email' ],
'sortableAttributes' => [ 'created_at' ],
],
],
],
수치 컬럼의 데이터 타입에 주의가 필요합니다. Meilisearch는 올바른 타입의 데이터에만 필터 조작(>, < 등)을 실행할 수 있습니다.
public function toSearchableArray () : array
{
return [
'id' => ( int ) $this -> id ,
'name' => $this -> name ,
'price' => ( float ) $this -> price ,
];
}
설정 후, scout:sync-index-settings 명령을 실행합니다.
php artisan scout:sync-index-settings
Typesense
Typesense 는 고속의 오픈 소스 검색 엔진으로, 키워드 검색·시맨틱 검색·지오 검색·벡터 검색에 대응하고 있습니다.
composer require typesense/typesense-php
.env 파일에 접속 정보를 설정합니다.
SCOUT_DRIVER =typesense
TYPESENSE_API_KEY =masterKey
TYPESENSE_HOST =localhost
TYPESENSE_PORT =8108
TYPESENSE_PATH =
TYPESENSE_PROTOCOL =http
Typesense를 사용하는 경우, toSearchableArray 메서드로 모델의 주 키를 문자열로, 작성 일시를 UNIX 타임스탬프로 캐스트할 필요가 있습니다.
public function toSearchableArray () : array
{
return array_merge ( $this -> toArray (), [
'id' => ( string ) $this -> id ,
'created_at' => $this -> created_at -> timestamp ,
]);
}
데이터베이스 / 컬렉션 엔진
외부 서비스 없이 검색을 추가하고 싶은 경우에 최적의 옵션입니다.
데이터베이스 엔진 은 MySQL / PostgreSQL의 전문 인덱스와 LIKE 절을 사용합니다. 대부분의 애플리케이션에서 이것으로 충분합니다.
컬렉션 엔진 은 PHP로 필터링하기 때문에, SQLite를 포함한 Laravel이 지원하는 모든 데이터베이스에서 동작합니다. 로컬 개발·테스트·소규모 데이터셋용입니다.
데이터베이스 엔진에서는 외부 엔진과 달리 인덱스의 수동 관리는 불필요합니다. 데이터베이스 테이블을 직접 검색합니다.
Searchable 트레이트
toSearchableArray()의 커스터마이즈
기본적으로, 모델의 toArray()의 전 데이터가 검색 인덱스에 보존됩니다. 인덱스에 동기하는 데이터를 커스터마이즈하려면, toSearchableArray 메서드를 오버라이드합니다.
<? php
namespace App\Models ;
use Illuminate\Database\Eloquent\ Model ;
use Laravel\Scout\ Searchable ;
class Post extends Model
{
use Searchable ;
/**
* 모델의 인덱스 가능한 데이터 배열을 취득합니다.
*
* @return array < string , mixed>
*/
public function toSearchableArray () : array
{
$array = $this -> toArray ();
// 데이터를 커스터마이즈...
return $array ;
}
}
인덱스명의 커스터마이즈
기본적으로, 모델의 테이블명(복수형)이 인덱스명으로서 사용됩니다. searchableAs 메서드를 오버라이드해 커스터마이즈할 수 있습니다.
public function searchableAs () : string
{
return 'posts_index' ;
}
데이터베이스 엔진용의 검색 전략
데이터베이스 엔진에서는, 컬럼별로 효율적인 검색 전략을 PHP 애트리뷰트로 지정할 수 있습니다.
use Laravel\Scout\Attributes\ SearchUsingFullText ;
use Laravel\Scout\Attributes\ SearchUsingPrefix ;
#[ SearchUsingPrefix ([ 'id' , 'email' ])]
#[ SearchUsingFullText ([ 'bio' ])]
public function toSearchableArray () : array
{
return [
'id' => $this -> id ,
'name' => $this -> name ,
'email' => $this -> email ,
'bio' => $this -> bio ,
];
}
SearchUsingFullText를 사용하기 전에, 대상 컬럼에 전문 인덱스 가 부여되어 있는지 확인해 주세요.
조건부로 검색 가능하게 하기
특정한 조건하에서만 모델을 검색 가능하게 하고 싶은 경우는, shouldBeSearchable 메서드를 정의합니다.
/**
* 이 모델을 검색 가능하게 해야 할지 판단합니다.
*/
public function shouldBeSearchable () : bool
{
return $this -> isPublished ();
}
shouldBeSearchable는 데이터베이스 엔진에서는 기능하지 않습니다. 데이터베이스 엔진에서 같은 동작을 실현하려면, where 절 을 사용해 주세요.
인덱스의 관리
이 섹션의 명령은, 주로 Algolia·Meilisearch·Typesense 등의 서드파티 엔진을 사용하는 경우에 관계합니다. 데이터베이스 엔진에서는 인덱스 관리는 불필요합니다.
기존 레코드의 임포트
기존 프로젝트에 Scout를 도입하는 경우, scout:import 명령으로 기존 레코드를 인덱스에 임포트합니다.
php artisan scout:import "App\Models\Post"
큐를 사용해 백그라운드에서 임포트할 수도 있습니다.
php artisan scout:queue-import "App\Models\Post" --chunk=500
인덱스의 클리어
모델의 모든 레코드를 검색 인덱스로부터 삭제하려면 scout:flush를 사용합니다.
php artisan scout:flush "App\Models\Post"
인덱스의 일시 정지
Eloquent 조작 중에 검색 인덱스와의 동기를 일시적으로 멈추고 싶은 경우는 withoutSyncingToSearch를 사용합니다.
use App\Models\ Order ;
Order :: withoutSyncingToSearch ( function () {
// 이 안의 모델 조작은 검색 인덱스에 동기되지 않는다
});
레코드의 수동 추가·삭제
쿼리를 사용해 모델의 컬렉션을 인덱스에 추가할 수 있습니다.
// 쿼리 결과를 추가
Order :: where ( 'price' , '>' , 100 ) -> searchable ();
// 릴레이션 경유로 추가
$user -> orders () -> searchable ();
인덱스에서 레코드를 삭제하려면 unsearchable을 사용합니다.
Order :: where ( 'price' , '>' , 100 ) -> unsearchable ();
모델을 delete하면, 인덱스에서도 자동적으로 삭제됩니다.
search 메서드로 모델을 검색합니다. get을 연결해 Eloquent 모델의 컬렉션을 취득합니다.
use App\Models\ Order ;
$orders = Order :: search ( 'Star Trek' ) -> get ();
컨트롤러나 라우트에서 직접 반환하면 JSON으로 자동 변환됩니다.
use Illuminate\Http\ Request ;
Route :: get ( '/search' , function ( Request $request ) {
return Order :: search ( $request -> search ) -> get ();
});
생 검색 결과가 필요한 경우는 raw 메서드를 사용합니다.
$orders = Order :: search ( 'Star Trek' ) -> raw ();
페이지네이션
paginate 메서드로 검색 결과를 페이지네이션할 수 있습니다. 통상의 Eloquent 쿼리의 페이지네이션과 마찬가지로 기능합니다.
$orders = Order :: search ( 'Star Trek' ) -> paginate ();
// 1페이지당 건수를 지정
$orders = Order :: search ( 'Star Trek' ) -> paginate ( 15 );
데이터베이스 엔진에서는 simplePaginate도 사용할 수 있습니다. 총 건수를 취득하지 않기 때문에 대규모 데이터셋에서 효율적입니다.
$orders = Order :: search ( 'Star Trek' ) -> simplePaginate ( 15 );
Blade 템플릿에서의 표시 예:
< div class = "container" >
@foreach ($orders as $order)
{{ $order->price }}
@endforeach
</ div >
{{ $orders->links() }}
필터링과 정렬
where 메서드로 검색 쿼리에 필터 조건을 추가할 수 있습니다.
use App\Models\ Order ;
// 등가 필터
$orders = Order :: search ( 'Star Trek' ) -> where ( 'user_id' , 1 ) -> get ();
// 배열 내의 어느 하나에 일치
$orders = Order :: search ( 'Star Trek' ) -> whereIn ( 'status' , [ 'open' , 'paid' ]) -> get ();
// 배열 내의 어느 것에도 일치하지 않음
$orders = Order :: search ( 'Star Trek' ) -> whereNotIn ( 'status' , [ 'closed' ]) -> get ();
Meilisearch를 사용하는 경우, where를 사용하기 전에 필터링 가능한 속성 을 설정할 필요가 있습니다.
query 메서드로 Eloquent 쿼리를 커스터마이즈할 수도 있습니다.
use Illuminate\Database\Eloquent\ Builder ;
$orders = Order :: search ( 'Star Trek' )
-> query ( fn ( Builder $query ) => $query -> with ( 'invoices' ))
-> get ();
Eager Loading
Scout를 사용하면, 검색 엔진으로부터 ID의 일람을 취득한 후, Eloquent로 모델을 취득합니다. N+1 문제를 피하려면, query 메서드에서 with()을 사용한 Eager Loading을 지정합니다.
use App\Models\ Order ;
use Illuminate\Database\Eloquent\ Builder ;
$orders = Order :: search ( 'Star Trek' )
-> query ( fn ( Builder $query ) => $query -> with ([ 'invoices' , 'user' ]))
-> get ();
배치 임포트 시에 릴레이션을 Eager Load하려면, makeAllSearchableUsing 메서드를 정의합니다.
use Illuminate\Database\Eloquent\ Builder ;
protected function makeAllSearchableUsing ( Builder $query ) : Builder
{
return $query -> with ( 'author' );
}
makeAllSearchableUsing은 큐를 사용한 배치 임포트에서는 사용할 수 없는 경우가 있습니다. 큐 잡에서 모델 컬렉션이 처리될 때 릴레이션은 복원되지 않습니다.
소프트 삭제
인덱스된 모델이 소프트 삭제 를 사용하고 있고, 삭제된 모델도 검색하고 싶은 경우는, config/scout.php의 soft_delete 옵션을 true로 설정합니다.
활성화하면, withTrashed나 onlyTrashed로 삭제된 레코드를 검색할 수 있습니다.
// 삭제된 것을 포함해 검색
$orders = Order :: search ( 'Star Trek' ) -> withTrashed () -> get ();
// 삭제된 것만 검색
$orders = Order :: search ( 'Star Trek' ) -> onlyTrashed () -> get ();
커스텀 엔진
조립된 검색 엔진이 니즈에 맞지 않는 경우는, 독자적인 커스텀 엔진을 구현할 수 있습니다. 커스텀 엔진은 Laravel\Scout\Engines\Engine 추상 클래스를 상속하고, 이하의 8개의 메서드를 구현해야 합니다.
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 클래스를 확인해 주세요.
작성한 커스텀 엔진은, App\Providers\AppServiceProvider의 boot 메서드에서 Scout에 등록합니다.
use App\ScoutExtensions\ MySqlSearchEngine ;
use Laravel\Scout\ EngineManager ;
public function boot () : void
{
resolve ( EngineManager :: class ) -> extend ( 'mysql' , function () {
return new MySqlSearchEngine ;
});
}
등록 후, config/scout.php에서 드라이버로서 지정합니다.
관련 페이지
Eloquent ORM Eloquent 모델의 기본적인 사용법을 확인합니다.
Eloquent 릴레이션 릴레이션의 정의와 Eager Loading을 확인합니다.
큐 Scout는 큐와 함께 인덱스를 백그라운드에서 업데이트할 수 있습니다.