50 KiB
Laravel Scout
- Introduction
- Installation
- Queueing
- Driver Prerequisites
- Algolia
- Meilisearch
- Typesense
- Configuration
- Configuring Model Indexes
- Configuring Searchable Data
- Configuring the Model ID
- Configuring Search Engines per Model
- Identifying Users
- Database / Collection Engines
- Database Engine
- Collection Engine
- Indexing
- Batch Import
- Adding Records
- Updating Records
- Removing Records
- Pausing Indexing
- Conditionally Searchable Model Instances
- Searching
- Where Clauses
- Pagination
- Soft Deleting
- Customizing Engine Searches
- Custom Engines
Introduction
Laravel Scout provides a simple, driver- based solution for adding full-text search to your Eloquent models. Using model observers, Scout will automatically keep your search indexes in sync with your Eloquent records.
Currently, Scout ships with Algolia,
Meilisearch,
Typesense, and MySQL / PostgreSQL (database)
drivers. In addition, Scout includes a "collection" driver that is designed
for local development usage and does not require any external dependencies or
third-party services. Furthermore, writing custom drivers is simple and you
are free to extend Scout with your own search implementations.
Installation
First, install Scout via the Composer package manager:
1composer require laravel/scout
composer require laravel/scout
After installing Scout, you should publish the Scout configuration file using
the vendor:publish Artisan command. This command will publish the
scout.php configuration file to your application's config directory:
1php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"
php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"
Finally, add the Laravel\Scout\Searchable trait to the model you would like
to make searchable. This trait will register a model observer that will
automatically keep the model in sync with your search driver:
1<?php
2
3namespace App\Models;
4
5use Illuminate\Database\Eloquent\Model;
6use Laravel\Scout\Searchable;
7
8class Post extends Model
9{
10 use Searchable;
11}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
class Post extends Model
{
use Searchable;
}
Queueing
While not strictly required to use Scout, you should strongly consider configuring a queue driver before using the library. Running a queue worker will allow Scout to queue all operations that sync your model information to your search indexes, providing much better response times for your application's web interface.
Once you have configured a queue driver, set the value of the queue option
in your config/scout.php configuration file to true:
1'queue' => true,
'queue' => true,
Even when the queue option is set to false, it's important to remember
that some Scout drivers like Algolia and Meilisearch always index records
asynchronously. Meaning, even though the index operation has completed within
your Laravel application, the search engine itself may not reflect the new and
updated records immediately.
To specify the connection and queue that your Scout jobs utilize, you may
define the queue configuration option as an array:
1'queue' => [
2 'connection' => 'redis',
3 'queue' => 'scout'
4],
'queue' => [
'connection' => 'redis',
'queue' => 'scout'
],
Of course, if you customize the connection and queue that Scout jobs utilize, you should run a queue worker to process jobs on that connection and queue:
1php artisan queue:work redis --queue=scout
php artisan queue:work redis --queue=scout
Driver Prerequisites
Algolia
When using the Algolia driver, you should configure your Algolia id and
secret credentials in your config/scout.php configuration file. Once your
credentials have been configured, you will also need to install the Algolia
PHP SDK via the Composer package manager:
1composer require algolia/algoliasearch-client-php
composer require algolia/algoliasearch-client-php
Meilisearch
Meilisearch is a blazingly fast and open source search engine. If you aren't sure how to install Meilisearch on your local machine, you may use Laravel Sail, Laravel's officially supported Docker development environment.
When using the Meilisearch driver you will need to install the Meilisearch PHP SDK via the Composer package manager:
1composer require meilisearch/meilisearch-php http-interop/http-factory-guzzle
composer require meilisearch/meilisearch-php http-interop/http-factory-guzzle
Then, set the SCOUT_DRIVER environment variable as well as your Meilisearch
host and key credentials within your application's .env file:
1SCOUT_DRIVER=meilisearch
2MEILISEARCH_HOST=http://127.0.0.1:7700
3MEILISEARCH_KEY=masterKey
SCOUT_DRIVER=meilisearch
MEILISEARCH_HOST=http://127.0.0.1:7700
MEILISEARCH_KEY=masterKey
For more information regarding Meilisearch, please consult the Meilisearch documentation.
In addition, you should ensure that you install a version of
meilisearch/meilisearch-php that is compatible with your Meilisearch binary
version by reviewing [Meilisearch's documentation regarding binary
compatibility](https://github.com/meilisearch/meilisearch-php#-compatibility-
with-meilisearch).
When upgrading Scout on an application that utilizes Meilisearch, you should always review any additional breaking changes to the Meilisearch service itself.
Typesense
Typesense is a lightning-fast, open source search engine and supports keyword search, semantic search, geo search, and vector search.
You can [self-host](https://typesense.org/docs/guide/install- typesense.html#option-2-local-machine-self-hosting) Typesense or use Typesense Cloud.
To get started using Typesense with Scout, install the Typesense PHP SDK via the Composer package manager:
1composer require typesense/typesense-php
composer require typesense/typesense-php
Then, set the SCOUT_DRIVER environment variable as well as your Typesense
host and API key credentials within your application's .env file:
1SCOUT_DRIVER=typesense
2TYPESENSE_API_KEY=masterKey
3TYPESENSE_HOST=localhost
SCOUT_DRIVER=typesense
TYPESENSE_API_KEY=masterKey
TYPESENSE_HOST=localhost
If you are using Laravel Sail, you may need to adjust the
TYPESENSE_HOST environment variable to match the Docker container name. You
may also optionally specify your installation's port, path, and protocol:
1TYPESENSE_PORT=8108
2TYPESENSE_PATH=
3TYPESENSE_PROTOCOL=http
TYPESENSE_PORT=8108
TYPESENSE_PATH=
TYPESENSE_PROTOCOL=http
Additional settings and schema definitions for your Typesense collections can
be found within your application's config/scout.php configuration file. For
more information regarding Typesense, please consult the Typesense
documentation.
Preparing Data for Storage in Typesense
When utilizing Typesense, your searchable models must define a
toSearchableArray method that casts your model's primary key to a string and
creation date to a UNIX timestamp:
1/**
2 * Get the indexable data array for the model.
3 *
4 * @return array<string, mixed>
5 */
6public function toSearchableArray(): array
7{
8 return array_merge($this->toArray(),[
9 'id' => (string) $this->id,
10 'created_at' => $this->created_at->timestamp,
11 ]);
12}
/**
* Get the indexable data array for the model.
*
* @return array<string, mixed>
*/
public function toSearchableArray(): array
{
return array_merge($this->toArray(),[
'id' => (string) $this->id,
'created_at' => $this->created_at->timestamp,
]);
}
You should also define your Typesense collection schemas in your application's
config/scout.php file. A collection schema describes the data types of each
field that is searchable via Typesense. For more information on all available
schema options, please consult the [Typesense
documentation](https://typesense.org/docs/latest/api/collections.html#schema-
parameters).
If you need to change your Typesense collection's schema after it has been
defined, you may either run scout:flush and scout:import, which will
delete all existing indexed data and recreate the schema. Or, you may use
Typesense's API to modify the collection's schema without removing any indexed
data.
If your searchable model is soft deletable, you should define a
__soft_deleted field in the model's corresponding Typesense schema within
your application's config/scout.php configuration file:
1User::class => [
2 'collection-schema' => [
3 'fields' => [
4 // ...
5 [
6 'name' => '__soft_deleted',
7 'type' => 'int32',
8 'optional' => true,
9 ],
10 ],
11 ],
12],
User::class => [
'collection-schema' => [
'fields' => [
// ...
[
'name' => '__soft_deleted',
'type' => 'int32',
'optional' => true,
],
],
],
],
Dynamic Search Parameters
Typesense allows you to modify your [search
parameters](https://typesense.org/docs/latest/api/search.html#search-
parameters) dynamically when performing a search operation via the options
method:
1use App\Models\Todo;
2
3Todo::search('Groceries')->options([
4 'query_by' => 'title, description'
5])->get();
use App\Models\Todo;
Todo::search('Groceries')->options([
'query_by' => 'title, description'
])->get();
Configuration
Configuring Model Indexes
Each Eloquent model is synced with a given search "index", which contains all
of the searchable records for that model. In other words, you can think of
each index like a MySQL table. By default, each model will be persisted to an
index matching the model's typical "table" name. Typically, this is the plural
form of the model name; however, you are free to customize the model's index
by overriding the searchableAs method on the model:
1<?php
2
3namespace App\Models;
4
5use Illuminate\Database\Eloquent\Model;
6use Laravel\Scout\Searchable;
7
8class Post extends Model
9{
10 use Searchable;
11
12 /**
13 * Get the name of the index associated with the model.
14 */
15 public function searchableAs(): string
16 {
17 return 'posts_index';
18 }
19}
<?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.
*/
public function searchableAs(): string
{
return 'posts_index';
}
}
Configuring Searchable Data
By default, the entire toArray form of a given model will be persisted to
its search index. If you would like to customize the data that is synchronized
to the search index, you may override the toSearchableArray method on the
model:
1<?php
2
3namespace App\Models;
4
5use Illuminate\Database\Eloquent\Model;
6use Laravel\Scout\Searchable;
7
8class Post extends Model
9{
10 use Searchable;
11
12 /**
13 * Get the indexable data array for the model.
14 *
15 * @return array<string, mixed>
16 */
17 public function toSearchableArray(): array
18 {
19 $array = $this->toArray();
20
21 // Customize the data array...
22
23 return $array;
24 }
25}
<?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<string, mixed>
*/
public function toSearchableArray(): array
{
$array = $this->toArray();
// Customize the data array...
return $array;
}
}
Some search engines such as Meilisearch will only perform filter operations
(>, <, etc.) on data of the correct type. So, when using these search
engines and customizing your searchable data, you should ensure that numeric
values are cast to their correct type:
1public function toSearchableArray()
2{
3 return [
4 'id' => (int) $this->id,
5 'name' => $this->name,
6 'price' => (float) $this->price,
7 ];
8}
public function toSearchableArray()
{
return [
'id' => (int) $this->id,
'name' => $this->name,
'price' => (float) $this->price,
];
}
Configuring Index Settings (Algolia)
Sometimes you may want to configure additional settings on your Algolia
indexes. While you can manage these settings via the Algolia UI, it is
sometimes more efficient to manage the desired state of your index
configuration directly from your application's config/scout.php
configuration file.
This approach allows you to deploy these settings through your application's automated deployment pipeline, avoiding manual configuration and ensuring consistency across multiple environments. You may configure filterable attributes, ranking, faceting, or [any other supported settings](https://www.algolia.com/doc/rest- api/search/#tag/Indices/operation/setSettings).
To get started, add settings for each index in your application's
config/scout.php configuration file:
1use App\Models\User;
2use App\Models\Flight;
3
4'algolia' => [
5 'id' => env('ALGOLIA_APP_ID', ''),
6 'secret' => env('ALGOLIA_SECRET', ''),
7 'index-settings' => [
8 User::class => [
9 'searchableAttributes' => ['id', 'name', 'email'],
10 'attributesForFaceting'=> ['filterOnly(email)'],
11 // Other settings fields...
12 ],
13 Flight::class => [
14 'searchableAttributes'=> ['id', 'destination'],
15 ],
16 ],
17],
use App\Models\User;
use App\Models\Flight;
'algolia' => [
'id' => env('ALGOLIA_APP_ID', ''),
'secret' => env('ALGOLIA_SECRET', ''),
'index-settings' => [
User::class => [
'searchableAttributes' => ['id', 'name', 'email'],
'attributesForFaceting'=> ['filterOnly(email)'],
// Other settings fields...
],
Flight::class => [
'searchableAttributes'=> ['id', 'destination'],
],
],
],
If the model underlying a given index is soft deletable and is included in the
index-settings array, Scout will automatically include support for faceting
on soft deleted models on that index. If you have no other faceting attributes
to define for a soft deletable model index, you may simply add an empty entry
to the index-settings array for that model:
1'index-settings' => [
2 Flight::class => []
3],
'index-settings' => [
Flight::class => []
],
After configuring your application's index settings, you must invoke the
scout:sync-index-settings Artisan command. This command will inform Algolia
of your currently configured index settings. For convenience, you may wish to
make this command part of your deployment process:
1php artisan scout:sync-index-settings
php artisan scout:sync-index-settings
Configuring Filterable Data and Index Settings (Meilisearch)
Unlike Scout's other drivers, Meilisearch requires you to pre-define index search settings such as filterable attributes, sortable attributes, and other supported settings fields.
Filterable attributes are any attributes you plan to filter on when invoking
Scout's where method, while sortable attributes are any attributes you plan
to sort by when invoking Scout's orderBy method. To define your index
settings, adjust the index-settings portion of your meilisearch
configuration entry in your application's scout configuration file:
1use App\Models\User;
2use App\Models\Flight;
3
4'meilisearch' => [
5 'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'),
6 'key' => env('MEILISEARCH_KEY', null),
7 'index-settings' => [
8 User::class => [
9 'filterableAttributes'=> ['id', 'name', 'email'],
10 'sortableAttributes' => ['created_at'],
11 // Other settings fields...
12 ],
13 Flight::class => [
14 'filterableAttributes'=> ['id', 'destination'],
15 'sortableAttributes' => ['updated_at'],
16 ],
17 ],
18],
use App\Models\User;
use App\Models\Flight;
'meilisearch' => [
'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'),
'key' => env('MEILISEARCH_KEY', null),
'index-settings' => [
User::class => [
'filterableAttributes'=> ['id', 'name', 'email'],
'sortableAttributes' => ['created_at'],
// Other settings fields...
],
Flight::class => [
'filterableAttributes'=> ['id', 'destination'],
'sortableAttributes' => ['updated_at'],
],
],
],
If the model underlying a given index is soft deletable and is included in the
index-settings array, Scout will automatically include support for filtering
on soft deleted models on that index. If you have no other filterable or
sortable attributes to define for a soft deletable model index, you may simply
add an empty entry to the index-settings array for that model:
1'index-settings' => [
2 Flight::class => []
3],
'index-settings' => [
Flight::class => []
],
After configuring your application's index settings, you must invoke the
scout:sync-index-settings Artisan command. This command will inform
Meilisearch of your currently configured index settings. For convenience, you
may wish to make this command part of your deployment process:
1php artisan scout:sync-index-settings
php artisan scout:sync-index-settings
Configuring the Model ID
By default, Scout will use the primary key of the model as the model's unique
ID / key that is stored in the search index. If you need to customize this
behavior, you may override the getScoutKey and the getScoutKeyName methods
on the model:
1<?php
2
3namespace App\Models;
4
5use Illuminate\Database\Eloquent\Model;
6use Laravel\Scout\Searchable;
7
8class User extends Model
9{
10 use Searchable;
11
12 /**
13 * Get the value used to index the model.
14 */
15 public function getScoutKey(): mixed
16 {
17 return $this->email;
18 }
19
20 /**
21 * Get the key name used to index the model.
22 */
23 public function getScoutKeyName(): mixed
24 {
25 return 'email';
26 }
27}
<?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.
*/
public function getScoutKey(): mixed
{
return $this->email;
}
/**
* Get the key name used to index the model.
*/
public function getScoutKeyName(): mixed
{
return 'email';
}
}
Configuring Search Engines per Model
When searching, Scout will typically use the default search engine specified
in your application's scout configuration file. However, the search engine
for a particular model can be changed by overriding the searchableUsing
method on the model:
1<?php
2
3namespace App\Models;
4
5use Illuminate\Database\Eloquent\Model;
6use Laravel\Scout\Engines\Engine;
7use Laravel\Scout\EngineManager;
8use Laravel\Scout\Searchable;
9
10class User extends Model
11{
12 use Searchable;
13
14 /**
15 * Get the engine used to index the model.
16 */
17 public function searchableUsing(): Engine
18 {
19 return app(EngineManager::class)->engine('meilisearch');
20 }
21}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Engines\Engine;
use Laravel\Scout\EngineManager;
use Laravel\Scout\Searchable;
class User extends Model
{
use Searchable;
/**
* Get the engine used to index the model.
*/
public function searchableUsing(): Engine
{
return app(EngineManager::class)->engine('meilisearch');
}
}
Identifying Users
Scout also allows you to auto identify users when using
Algolia. Associating the authenticated user with search
operations may be helpful when viewing your search analytics within Algolia's
dashboard. You can enable user identification by defining a SCOUT_IDENTIFY
environment variable as true in your application's .env file:
1SCOUT_IDENTIFY=true
SCOUT_IDENTIFY=true
Enabling this feature will also pass the request's IP address and your authenticated user's primary identifier to Algolia so this data is associated with any search request that is made by the user.
Database / Collection Engines
Database Engine
The database engine currently supports MySQL and PostgreSQL.
If your application interacts with small to medium sized databases or has a light workload, you may find it more convenient to get started with Scout's "database" engine. The database engine will use "where like" clauses and full text indexes when filtering results from your existing database to determine the applicable search results for your query.
To use the database engine, you may simply set the value of the SCOUT_DRIVER
environment variable to database, or specify the database driver directly
in your application's scout configuration file:
1SCOUT_DRIVER=database
SCOUT_DRIVER=database
Once you have specified the database engine as your preferred driver, you must configure your searchable data. Then, you may start executing search queries against your models. Search engine indexing, such as the indexing needed to seed Algolia, Meilisearch or Typesense indexes, is unnecessary when using the database engine.
Customizing Database Searching Strategies
By default, the database engine will execute a "where like" query against
every model attribute that you have configured as searchable. However, in some
situations, this may result in poor performance. Therefore, the database
engine's search strategy can be configured so that some specified columns
utilize full text search queries or only use "where like" constraints to
search the prefixes of strings (example%) instead of searching within the
entire string (%example%).
To define this behavior, you may assign PHP attributes to your model's
toSearchableArray method. Any columns that are not assigned additional
search strategy behavior will continue to use the default "where like"
strategy:
1use Laravel\Scout\Attributes\SearchUsingFullText;
2use Laravel\Scout\Attributes\SearchUsingPrefix;
3
4/**
5 * Get the indexable data array for the model.
6 *
7 * @return array<string, mixed>
8 */
9#[SearchUsingPrefix(['id', 'email'])]
10#[SearchUsingFullText(['bio'])]
11public function toSearchableArray(): array
12{
13 return [
14 'id' => $this->id,
15 'name' => $this->name,
16 'email' => $this->email,
17 'bio' => $this->bio,
18 ];
19}
use Laravel\Scout\Attributes\SearchUsingFullText;
use Laravel\Scout\Attributes\SearchUsingPrefix;
/**
* Get the indexable data array for the model.
*
* @return array<string, mixed>
*/
#[SearchUsingPrefix(['id', 'email'])]
#[SearchUsingFullText(['bio'])]
public function toSearchableArray(): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'bio' => $this->bio,
];
}
Before specifying that a column should use full text query constraints, ensure that the column has been assigned a full text index.
Collection Engine
While you are free to use the Algolia, Meilisearch, or Typesense search engines during local development, you may find it more convenient to get started with the "collection" engine. The collection engine will use "where" clauses and collection filtering on results from your existing database to determine the applicable search results for your query. When using this engine, it is not necessary to "index" your searchable models, as they will simply be retrieved from your local database.
To use the collection engine, you may simply set the value of the
SCOUT_DRIVER environment variable to collection, or specify the
collection driver directly in your application's scout configuration file:
1SCOUT_DRIVER=collection
SCOUT_DRIVER=collection
Once you have specified the collection driver as your preferred driver, you may start executing search queries against your models. Search engine indexing, such as the indexing needed to seed Algolia, Meilisearch, or Typesense indexes, is unnecessary when using the collection engine.
Differences From Database Engine
On first glance, the "database" and "collections" engines are fairly similar.
They both interact directly with your database to retrieve search results.
However, the collection engine does not utilize full text indexes or LIKE
clauses to find matching records. Instead, it pulls all possible records and
uses Laravel's Str::is helper to determine if the search string exists
within the model attribute values.
The collection engine is the most portable search engine as it works across all relational databases supported by Laravel (including SQLite and SQL Server); however, it is less efficient than Scout's database engine.
Indexing
Batch Import
If you are installing Scout into an existing project, you may already have
database records you need to import into your indexes. Scout provides a
scout:import Artisan command that you may use to import all of your existing
records into your search indexes:
1php artisan scout:import "App\Models\Post"
php artisan scout:import "App\Models\Post"
The scout:queue-import command may be used to import all of your existing
records using queued jobs:
1php artisan scout:queue-import "App\Models\Post" --chunk=500
php artisan scout:queue-import "App\Models\Post" --chunk=500
The flush command may be used to remove all of a model's records from your
search indexes:
1php artisan scout:flush "App\Models\Post"
php artisan scout:flush "App\Models\Post"
Modifying the Import Query
If you would like to modify the query that is used to retrieve all of your
models for batch importing, you may define a makeAllSearchableUsing method
on your model. This is a great place to add any eager relationship loading
that may be necessary before importing your models:
1use Illuminate\Database\Eloquent\Builder;
2
3/**
4 * Modify the query used to retrieve models when making all of the models searchable.
5 */
6protected function makeAllSearchableUsing(Builder $query): Builder
7{
8 return $query->with('author');
9}
use Illuminate\Database\Eloquent\Builder;
/**
* Modify the query used to retrieve models when making all of the models searchable.
*/
protected function makeAllSearchableUsing(Builder $query): Builder
{
return $query->with('author');
}
The makeAllSearchableUsing method may not be applicable when using a queue
to batch import models. Relationships are not
restored when model collections are
processed by jobs.
Adding Records
Once you have added the Laravel\Scout\Searchable trait to a model, all you
need to do is save or create a model instance and it will automatically be
added to your search index. If you have configured Scout to use queues this
operation will be performed in the background by your queue worker:
1use App\Models\Order;
2
3$order = new Order;
4
5// ...
6
7$order->save();
use App\Models\Order;
$order = new Order;
// ...
$order->save();
Adding Records via Query
If you would like to add a collection of models to your search index via an
Eloquent query, you may chain the searchable method onto the Eloquent query.
The searchable method will [chunk the results](/docs/12.x/eloquent#chunking-
results) of the query and add the records to your search index. Again, if you
have configured Scout to use queues, all of the chunks will be imported in the
background by your queue workers:
1use App\Models\Order;
2
3Order::where('price', '>', 100)->searchable();
use App\Models\Order;
Order::where('price', '>', 100)->searchable();
You may also call the searchable method on an Eloquent relationship
instance:
1$user->orders()->searchable();
$user->orders()->searchable();
Or, if you already have a collection of Eloquent models in memory, you may
call the searchable method on the collection instance to add the model
instances to their corresponding index:
1$orders->searchable();
$orders->searchable();
The searchable method can be considered an "upsert" operation. In other
words, if the model record is already in your index, it will be updated. If it
does not exist in the search index, it will be added to the index.
Updating Records
To update a searchable model, you only need to update the model instance's
properties and save the model to your database. Scout will automatically
persist the changes to your search index:
1use App\Models\Order;
2
3$order = Order::find(1);
4
5// Update the order...
6
7$order->save();
use App\Models\Order;
$order = Order::find(1);
// Update the order...
$order->save();
You may also invoke the searchable method on an Eloquent query instance to
update a collection of models. If the models do not exist in your search
index, they will be created:
1Order::where('price', '>', 100)->searchable();
Order::where('price', '>', 100)->searchable();
If you would like to update the search index records for all of the models in
a relationship, you may invoke the searchable on the relationship instance:
1$user->orders()->searchable();
$user->orders()->searchable();
Or, if you already have a collection of Eloquent models in memory, you may
call the searchable method on the collection instance to update the model
instances in their corresponding index:
1$orders->searchable();
$orders->searchable();
Modifying Records Before Importing
Sometimes you may need to prepare the collection of models before they are
made searchable. For instance, you may want to eager load a relationship so
that the relationship data can be efficiently added to your search index. To
accomplish this, define a makeSearchableUsing method on the corresponding
model:
1use Illuminate\Database\Eloquent\Collection;
2
3/**
4 * Modify the collection of models being made searchable.
5 */
6public function makeSearchableUsing(Collection $models): Collection
7{
8 return $models->load('author');
9}
use Illuminate\Database\Eloquent\Collection;
/**
* Modify the collection of models being made searchable.
*/
public function makeSearchableUsing(Collection $models): Collection
{
return $models->load('author');
}
Removing Records
To remove a record from your index you may simply delete the model from the
database. This may be done even if you are using soft
deleted models:
1use App\Models\Order;
2
3$order = Order::find(1);
4
5$order->delete();
use App\Models\Order;
$order = Order::find(1);
$order->delete();
If you do not want to retrieve the model before deleting the record, you may
use the unsearchable method on an Eloquent query instance:
1Order::where('price', '>', 100)->unsearchable();
Order::where('price', '>', 100)->unsearchable();
If you would like to remove the search index records for all of the models in
a relationship, you may invoke the unsearchable on the relationship
instance:
1$user->orders()->unsearchable();
$user->orders()->unsearchable();
Or, if you already have a collection of Eloquent models in memory, you may
call the unsearchable method on the collection instance to remove the model
instances from their corresponding index:
1$orders->unsearchable();
$orders->unsearchable();
To remove all of the model records from their corresponding index, you may
invoke the removeAllFromSearch method:
1Order::removeAllFromSearch();
Order::removeAllFromSearch();
Pausing Indexing
Sometimes you may need to perform a batch of Eloquent operations on a model
without syncing the model data to your search index. You may do this using the
withoutSyncingToSearch method. This method accepts a single closure which
will be immediately executed. Any model operations that occur within the
closure will not be synced to the model's index:
1use App\Models\Order;
2
3Order::withoutSyncingToSearch(function () {
4 // Perform model actions...
5});
use App\Models\Order;
Order::withoutSyncingToSearch(function () {
// Perform model actions...
});
Conditionally Searchable Model Instances
Sometimes you may need to only make a model searchable under certain
conditions. For example, imagine you have App\Models\Post model that may be
in one of two states: "draft" and "published". You may only want to allow
"published" posts to be searchable. To accomplish this, you may define a
shouldBeSearchable method on your model:
1/**
2 * Determine if the model should be searchable.
3 */
4public function shouldBeSearchable(): bool
5{
6 return $this->isPublished();
7}
/**
* Determine if the model should be searchable.
*/
public function shouldBeSearchable(): bool
{
return $this->isPublished();
}
The shouldBeSearchable method is only applied when manipulating models
through the save and create methods, queries, or relationships. Directly
making models or collections searchable using the searchable method will
override the result of the shouldBeSearchable method.
The shouldBeSearchable method is not applicable when using Scout's
"database" engine, as all searchable data is always stored in the database. To
achieve similar behavior when using the database engine, you should use where
clauses instead.
Searching
You may begin searching a model using the search method. The search method
accepts a single string that will be used to search your models. You should
then chain the get method onto the search query to retrieve the Eloquent
models that match the given search query:
1use App\Models\Order;
2
3$orders = Order::search('Star Trek')->get();
use App\Models\Order;
$orders = Order::search('Star Trek')->get();
Since Scout searches return a collection of Eloquent models, you may even return the results directly from a route or controller and they will automatically be converted to JSON:
1use App\Models\Order;
2use Illuminate\Http\Request;
3
4Route::get('/search', function (Request $request) {
5 return Order::search($request->search)->get();
6});
use App\Models\Order;
use Illuminate\Http\Request;
Route::get('/search', function (Request $request) {
return Order::search($request->search)->get();
});
If you would like to get the raw search results before they are converted to
Eloquent models, you may use the raw method:
1$orders = Order::search('Star Trek')->raw();
$orders = Order::search('Star Trek')->raw();
Custom Indexes
Search queries will typically be performed on the index specified by the
model's searchableAs method. However, you may use the within method to
specify a custom index that should be searched instead:
1$orders = Order::search('Star Trek')
2 ->within('tv_shows_popularity_desc')
3 ->get();
$orders = Order::search('Star Trek')
->within('tv_shows_popularity_desc')
->get();
Where Clauses
Scout allows you to add simple "where" clauses to your search queries. Currently, these clauses only support basic numeric equality checks and are primarily useful for scoping search queries by an owner ID:
1use App\Models\Order;
2
3$orders = Order::search('Star Trek')->where('user_id', 1)->get();
use App\Models\Order;
$orders = Order::search('Star Trek')->where('user_id', 1)->get();
In addition, the whereIn method may be used to verify that a given column's
value is contained within the given array:
1$orders = Order::search('Star Trek')->whereIn(
2 'status', ['open', 'paid']
3)->get();
$orders = Order::search('Star Trek')->whereIn(
'status', ['open', 'paid']
)->get();
The whereNotIn method verifies that the given column's value is not
contained in the given array:
1$orders = Order::search('Star Trek')->whereNotIn(
2 'status', ['closed']
3)->get();
$orders = Order::search('Star Trek')->whereNotIn(
'status', ['closed']
)->get();
Since a search index is not a relational database, more advanced "where" clauses are not currently supported.
If your application is using Meilisearch, you must configure your application's filterable attributes before utilizing Scout's "where" clauses.
Pagination
In addition to retrieving a collection of models, you may paginate your search
results using the paginate method. This method will return an
Illuminate\Pagination\LengthAwarePaginator instance just as if you had
paginated a traditional Eloquent query:
1use App\Models\Order;
2
3$orders = Order::search('Star Trek')->paginate();
use App\Models\Order;
$orders = Order::search('Star Trek')->paginate();
You may specify how many models to retrieve per page by passing the amount as
the first argument to the paginate method:
1$orders = Order::search('Star Trek')->paginate(15);
$orders = Order::search('Star Trek')->paginate(15);
Once you have retrieved the results, you may display the results and render the page links using Blade just as if you had paginated a traditional Eloquent query:
1<div class="container">
2 @foreach ($orders as $order)
3 {{ $order->price }}
4 @endforeach
5</div>
6
7{{ $orders->links() }}
<div class="container">
@foreach ($orders as $order)
{{ $order->price }}
@endforeach
</div>
{{ $orders->links() }}
Of course, if you would like to retrieve the pagination results as JSON, you may return the paginator instance directly from a route or controller:
1use App\Models\Order;
2use Illuminate\Http\Request;
3
4Route::get('/orders', function (Request $request) {
5 return Order::search($request->input('query'))->paginate(15);
6});
use App\Models\Order;
use Illuminate\Http\Request;
Route::get('/orders', function (Request $request) {
return Order::search($request->input('query'))->paginate(15);
});
Since search engines are not aware of your Eloquent model's global scope definitions, you should not utilize global scopes in applications that utilize Scout pagination. Or, you should recreate the global scope's constraints when searching via Scout.
Soft Deleting
If your indexed models are soft deleting
and you need to search your soft deleted models, set the soft_delete option
of the config/scout.php configuration file to true:
1'soft_delete' => true,
'soft_delete' => true,
When this configuration option is true, Scout will not remove soft deleted
models from the search index. Instead, it will set a hidden __soft_deleted
attribute on the indexed record. Then, you may use the withTrashed or
onlyTrashed methods to retrieve the soft deleted records when searching:
1use App\Models\Order;
2
3// Include trashed records when retrieving results...
4$orders = Order::search('Star Trek')->withTrashed()->get();
5
6// Only include trashed records when retrieving results...
7$orders = Order::search('Star Trek')->onlyTrashed()->get();
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();
When a soft deleted model is permanently deleted using forceDelete, Scout
will remove it from the search index automatically.
Customizing Engine Searches
If you need to perform advanced customization of the search behavior of an
engine you may pass a closure as the second argument to the search method.
For example, you could use this callback to add geo-location data to your
search options before the search query is passed to Algolia:
1use Algolia\AlgoliaSearch\SearchIndex;
2use App\Models\Order;
3
4Order::search(
5 'Star Trek',
6 function (SearchIndex $algolia, string $query, array $options) {
7 $options['body']['query']['bool']['filter']['geo_distance'] = [
8 'distance' => '1000km',
9 'location' => ['lat' => 36, 'lon' => 111],
10 ];
11
12 return $algolia->search($query, $options);
13 }
14)->get();
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();
Customizing the Eloquent Results Query
After Scout retrieves a list of matching Eloquent models from your
application's search engine, Eloquent is used to retrieve all of the matching
models by their primary keys. You may customize this query by invoking the
query method. The query method accepts a closure that will receive the
Eloquent query builder instance as an argument:
1use App\Models\Order;
2use Illuminate\Database\Eloquent\Builder;
3
4$orders = Order::search('Star Trek')
5 ->query(fn (Builder $query) => $query->with('invoices'))
6 ->get();
use App\Models\Order;
use Illuminate\Database\Eloquent\Builder;
$orders = Order::search('Star Trek')
->query(fn (Builder $query) => $query->with('invoices'))
->get();
Since this callback is invoked after the relevant models have already been
retrieved from your application's search engine, the query method should not
be used for "filtering" results. Instead, you should use Scout where clauses.
Custom Engines
Writing the Engine
If one of the built-in Scout search engines doesn't fit your needs, you may
write your own custom engine and register it with Scout. Your engine should
extend the Laravel\Scout\Engines\Engine abstract class. This abstract class
contains eight methods your custom engine must implement:
1use Laravel\Scout\Builder;
2
3abstract public function update($models);
4abstract public function delete($models);
5abstract public function search(Builder $builder);
6abstract public function paginate(Builder $builder, $perPage, $page);
7abstract public function mapIds($results);
8abstract public function map(Builder $builder, $results, $model);
9abstract public function getTotalCount($results);
10abstract public function flush($model);
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);
You may find it helpful to review the implementations of these methods on the
Laravel\Scout\Engines\AlgoliaEngine class. This class will provide you with
a good starting point for learning how to implement each of these methods in
your own engine.
Registering the Engine
Once you have written your custom engine, you may register it with Scout using
the extend method of the Scout engine manager. Scout's engine manager may be
resolved from the Laravel service container. You should call the extend
method from the boot method of your App\Providers\AppServiceProvider class
or any other service provider used by your application:
1use App\ScoutExtensions\MySqlSearchEngine;
2use Laravel\Scout\EngineManager;
3
4/**
5 * Bootstrap any application services.
6 */
7public function boot(): void
8{
9 resolve(EngineManager::class)->extend('mysql', function () {
10 return new MySqlSearchEngine;
11 });
12}
use App\ScoutExtensions\MySqlSearchEngine;
use Laravel\Scout\EngineManager;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
resolve(EngineManager::class)->extend('mysql', function () {
return new MySqlSearchEngine;
});
}
Once your engine has been registered, you may specify it as your default Scout
driver in your application's config/scout.php configuration file:
1'driver' => 'mysql',
'driver' => 'mysql',