Passer au contenu principal

Introduction

Laravel Scout est une solution simple, à base de drivers, qui ajoute la recherche plein texte aux modèles Eloquent. Un observer synchronise automatiquement les enregistrements avec l’index. Scout inclut un moteur database intégré qui utilise l’index full text et LIKE de MySQL / PostgreSQL — sans service externe. Pour de grosses productions avec tolérance aux fautes, facettes ou géo-recherche, un moteur externe est utile.

Moteurs supportés

MoteurCaractéristiquesUsage
databaseIndex full text MySQL / PostgreSQLLa plupart des apps
collectionFiltrage en PHP (compatible SQLite)Dev local, tests
MeilisearchOpen source, tolérant aux fautes, rapideProduction
AlgoliaSaaS cloud, fonctions avancéesProduction
TypesenseOpen source, vector searchProduction

Installation

composer require laravel/scout
Publiez la configuration :
php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"
Ajoutez le trait Laravel\Scout\Searchable aux modèles à indexer.
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;

class Post extends Model
{
    use Searchable;
}

Configuration de la queue

Avec un moteur autre que database / collection, il est vivement recommandé de configurer une queue pour que la synchronisation d’index se fasse en arrière-plan et n’impacte pas les temps de réponse. Dans config/scout.php, mettez queue à true.
'queue' => true,
Vous pouvez préciser connexion et nom de queue :
'queue' => [
    'connection' => 'redis',
    'queue' => 'scout'
],
Démarrez un worker dédié :
php artisan queue:work redis --queue=scout

Jobs uniques

Pour éviter des jobs dupliqués sur le même enregistrement, enregistrez MakeSearchableUniquely / RemoveFromSearchUniquely (souvent dans boot).
use Laravel\Scout\Jobs\MakeSearchableUniquely;
use Laravel\Scout\Jobs\RemoveFromSearchUniquely;
use Laravel\Scout\Scout;

Scout::makeSearchableUsing(MakeSearchableUniquely::class);
Scout::removeFromSearchUsing(RemoveFromSearchUniquely::class);
Ils utilisent les jobs uniques pour prévenir les doublons.

Prérequis par driver

Algolia

Configurez id et secret dans config/scout.php et installez le SDK.
composer require algolia/algoliasearch-client-php
.env :
ALGOLIA_APP_ID=your-app-id
ALGOLIA_SECRET=your-secret-key

Paramètres d’index

Gérez les paramètres d’index dans 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)'],
        ],
    ],
],
Puis synchronisez :
php artisan scout:sync-index-settings

Meilisearch

Meilisearch est un moteur open source performant. En local, via Docker Sail :
./vendor/bin/sail up -d meilisearch
Ou directement avec Docker :
docker run -it --rm \
    -p 7700:7700 \
    getmeili/meilisearch:latest \
    meilisearch --master-key="masterKey"
Installez le 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
À chaque upgrade Scout, consultez aussi les breaking changes de Meilisearch.

Paramètres d’index (Meilisearch)

Avant d’utiliser where(), déclarez filterableAttributes ; pour 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'],
        ],
    ],
],
Attention aux types numériques : Meilisearch ne filtre correctement que si les types sont bons.
public function toSearchableArray(): array
{
    return [
        'id' => (int) $this->id,
        'name' => $this->name,
        'price' => (float) $this->price,
    ];
}
Synchronisez :
php artisan scout:sync-index-settings

Typesense

Typesense prend en charge keyword, sémantique, géo et vector search.
composer require typesense/typesense-php
.env :
SCOUT_DRIVER=typesense
TYPESENSE_API_KEY=masterKey
TYPESENSE_HOST=localhost
TYPESENSE_PORT=8108
TYPESENSE_PATH=
TYPESENSE_PROTOCOL=http
Convertissez la clé primaire en string et created_at en timestamp UNIX.
public function toSearchableArray(): array
{
    return array_merge($this->toArray(), [
        'id' => (string) $this->id,
        'created_at' => $this->created_at->timestamp,
    ]);
}

Moteurs database / collection

Idéal sans service externe. database : full text + LIKE sur MySQL / PostgreSQL. Suffit dans la plupart des cas.
SCOUT_DRIVER=database
collection : filtre en PHP, fonctionne avec toutes les DB de Laravel dont SQLite. Adapté dev / tests / petits datasets.
SCOUT_DRIVER=collection
Avec le moteur database, pas de gestion d’index manuelle : la recherche est directement sur la table.

Trait Searchable

Personnaliser toSearchableArray()

Par défaut, toArray() alimente l’index. Surchargez si nécessaire.
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;

class Post extends Model
{
    use Searchable;

    /**
     * Retourne les données à indexer.
     *
     * @return array<string, mixed>
     */
    public function toSearchableArray(): array
    {
        $array = $this->toArray();

        // Personnalisation...

        return $array;
    }
}

Personnaliser le nom d’index

Par défaut, le nom de table (pluriel). Surchargez searchableAs.
public function searchableAs(): string
{
    return 'posts_index';
}

Stratégies pour le moteur database

Précisez la stratégie par colonne avec des attributs 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,
    ];
}
Pour SearchUsingFullText, ajoutez un index full text sur les colonnes.

Indexation conditionnelle

Définissez shouldBeSearchable.
/**
 * Détermine si ce modèle doit être indexé.
 */
public function shouldBeSearchable(): bool
{
    return $this->isPublished();
}
shouldBeSearchable ne fonctionne pas avec le moteur database. Utilisez une clause where à la place.

Gestion des index

Cette section concerne principalement Algolia, Meilisearch, Typesense. Le moteur database n’a pas besoin de gestion d’index.

Importer des enregistrements existants

scout:import importe les enregistrements existants.
php artisan scout:import "App\Models\Post"
Import en queue :
php artisan scout:queue-import "App\Models\Post" --chunk=500

Vider l’index

php artisan scout:flush "App\Models\Post"

Pause de la synchronisation

Utilisez withoutSyncingToSearch pour désactiver temporairement la sync.
use App\Models\Order;

Order::withoutSyncingToSearch(function () {
    // Les opérations à l'intérieur ne synchronisent pas
});

Ajout / suppression manuels

// Ajouter le résultat d'une requête
Order::where('price', '>', 100)->searchable();

// Via une relation
$user->orders()->searchable();
unsearchable retire :
Order::where('price', '>', 100)->unsearchable();
delete sur un modèle retire automatiquement de l’index.

Recherche

search puis get :
use App\Models\Order;

$orders = Order::search('Star Trek')->get();
Renvoyer directement depuis une route donne du JSON.
use Illuminate\Http\Request;

Route::get('/search', function (Request $request) {
    return Order::search($request->search)->get();
});
raw() pour la réponse brute :
$orders = Order::search('Star Trek')->raw();

Pagination

$orders = Order::search('Star Trek')->paginate();

// Par page
$orders = Order::search('Star Trek')->paginate(15);
Sur le moteur database, simplePaginate est efficace pour de gros volumes.
$orders = Order::search('Star Trek')->simplePaginate(15);
Blade :
<div class="container">
    @foreach ($orders as $order)
        {{ $order->price }}
    @endforeach
</div>

{{ $orders->links() }}

Filtres et tri

where ajoute des filtres.
use App\Models\Order;

// Égalité
$orders = Order::search('Star Trek')->where('user_id', 1)->get();

// Dans un tableau
$orders = Order::search('Star Trek')->whereIn('status', ['open', 'paid'])->get();

// Pas dans un tableau
$orders = Order::search('Star Trek')->whereNotIn('status', ['closed'])->get();
Sous Meilisearch, déclarez filterableAttributes au préalable.
query personnalise la requête Eloquent finale.
use Illuminate\Database\Eloquent\Builder;

$orders = Order::search('Star Trek')
    ->query(fn (Builder $query) => $query->with('invoices'))
    ->get();

Eager Loading

Scout récupère les IDs puis les modèles via Eloquent. Évitez N+1 avec with() dans query.
use App\Models\Order;
use Illuminate\Database\Eloquent\Builder;

$orders = Order::search('Star Trek')
    ->query(fn (Builder $query) => $query->with(['invoices', 'user']))
    ->get();
Pour les imports en batch, définissez makeAllSearchableUsing.
use Illuminate\Database\Eloquent\Builder;

protected function makeAllSearchableUsing(Builder $query): Builder
{
    return $query->with('author');
}
makeAllSearchableUsing peut être inopérant avec des imports en queue : les relations ne survivent pas nécessairement à la sérialisation.

Soft delete

Si le modèle utilise soft delete, activez soft_delete: true dans config/scout.php.
'soft_delete' => true,
Vous pouvez alors utiliser withTrashed / onlyTrashed.
// Inclut supprimés
$orders = Order::search('Star Trek')->withTrashed()->get();

// Uniquement supprimés
$orders = Order::search('Star Trek')->onlyTrashed()->get();

Moteur personnalisé

Créez votre propre moteur en étendant Laravel\Scout\Engines\Engine et en implémentant 8 méthodes.
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);
Inspirez-vous de Laravel\Scout\Engines\AlgoliaEngine. Enregistrez-le dans boot :
use App\ScoutExtensions\MySqlSearchEngine;
use Laravel\Scout\EngineManager;

public function boot(): void
{
    resolve(EngineManager::class)->extend('mysql', function () {
        return new MySqlSearchEngine;
    });
}
Puis dans config/scout.php :
'driver' => 'mysql',

Pages associées

Eloquent ORM

Rappels sur les modèles Eloquent.

Relations Eloquent

Relations et eager loading.

Queues

Scout peut mettre à jour l’index en arrière-plan via les queues.
Dernière modification le 13 juillet 2026