Vai al contenuto principale

Introduzione

Laravel Scout è una soluzione driver-based che aggiunge la ricerca full-text ai modelli Eloquent. Sincronizza automaticamente record Eloquent e indici di ricerca tramite osservatori del modello. Scout include il motore integrato database che sfrutta il full-text di MySQL/PostgreSQL e la clausola LIKE senza servizi esterni. In produzione, per tolleranza ai typo, ricerche facettate e geospaziali, i motori esterni sono utili.

Motori supportati

MotoreCaratteristicheUso
databaseFull-text di MySQL/PostgreSQLLa maggior parte delle app
collectionFiltro in PHP (anche SQLite)Sviluppo/test
MeilisearchOpen source, tollerante ai typo, veloceProduzione
AlgoliaSaaS cloud, funzionalità avanzateProduzione
TypesenseOpen source, supporta vettoriProduzione

Installazione

composer require laravel/scout
php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"
Aggiungi il trait Laravel\Scout\Searchable al modello.
<?php

namespace App\Models;

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

class Post extends Model
{
    use Searchable;
}

Code

Con motori esterni si consiglia di attivare le code per la sincronizzazione in background.
'queue' => true,
Oppure:
'queue' => [
    'connection' => 'redis',
    'queue' => 'scout'
],
Avvia un worker dedicato:
php artisan queue:work redis --queue=scout

Job univoci

Per applicazioni molto scritte, evita job duplicati sullo stesso record.
use Laravel\Scout\Jobs\MakeSearchableUniquely;
use Laravel\Scout\Jobs\RemoveFromSearchUniquely;
use Laravel\Scout\Scout;

Scout::makeSearchableUsing(MakeSearchableUniquely::class);
Scout::removeFromSearchUsing(RemoveFromSearchUniquely::class);

Prerequisiti dei driver

Algolia

composer require algolia/algoliasearch-client-php
ALGOLIA_APP_ID=your-app-id
ALGOLIA_SECRET=your-secret-key

Impostazioni indice

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)'],
        ],
    ],
],
Sincronizza:
php artisan scout:sync-index-settings

Meilisearch

Meilisearch è veloce e open source. In locale è comodo con Laravel Sail.
./vendor/bin/sail up -d meilisearch
Docker diretto:
docker run -it --rm \
    -p 7700:7700 \
    getmeili/meilisearch:latest \
    meilisearch --master-key="masterKey"
composer require meilisearch/meilisearch-php http-interop/http-factory-guzzle
SCOUT_DRIVER=meilisearch
MEILISEARCH_HOST=http://127.0.0.1:7700
MEILISEARCH_KEY=masterKey
Controlla i breaking change di Meilisearch quando aggiorni Scout.

Impostazioni indice (Meilisearch)

Registra filterableAttributes per where() e sortableAttributes per orderBy().
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'],
        ],
    ],
],
Attenzione ai tipi:
public function toSearchableArray(): array
{
    return [
        'id' => (int) $this->id,
        'name' => $this->name,
        'price' => (float) $this->price,
    ];
}
php artisan scout:sync-index-settings

Typesense

Typesense è open source, supporta keyword, semantica, geo e vettori.
composer require typesense/typesense-php
SCOUT_DRIVER=typesense
TYPESENSE_API_KEY=masterKey
TYPESENSE_HOST=localhost
TYPESENSE_PORT=8108
TYPESENSE_PATH=
TYPESENSE_PROTOCOL=http
Casta la primary key a stringa e la data creazione a timestamp UNIX:
public function toSearchableArray(): array
{
    return array_merge($this->toArray(), [
        'id' => (string) $this->id,
        'created_at' => $this->created_at->timestamp,
    ]);
}

Motore database / collection

Senza servizi esterni. database: full-text MySQL/PostgreSQL e LIKE.
SCOUT_DRIVER=database
collection: filtro in PHP (anche SQLite), per dev/test.
SCOUT_DRIVER=collection
Con database engine gli indici non vanno gestiti a parte.

Trait Searchable

toSearchableArray()

<?php

namespace App\Models;

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

class Post extends Model
{
    use Searchable;

    public function toSearchableArray(): array
    {
        $array = $this->toArray();

        // Personalizza...

        return $array;
    }
}

Nome dell’indice

public function searchableAs(): string
{
    return 'posts_index';
}

Strategie di ricerca (database engine)

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,
    ];
}
Per SearchUsingFullText serve un indice full-text.

Condizionale

public function shouldBeSearchable(): bool
{
    return $this->isPublished();
}
Non funziona con database engine. Usa clausole where.

Gestione degli indici

Sezione rilevante con Algolia/Meilisearch/Typesense. Con database engine non serve.

Import di record esistenti

php artisan scout:import "App\Models\Post"
Con coda:
php artisan scout:queue-import "App\Models\Post" --chunk=500

Flush

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

Pausa della sincronizzazione

use App\Models\Order;

Order::withoutSyncingToSearch(function () {
    // Operazioni non sincronizzate
});

Aggiungi/rimuovi manualmente

Order::where('price', '>', 100)->searchable();
$user->orders()->searchable();

Order::where('price', '>', 100)->unsearchable();
Con delete() la rimozione è automatica.

Ricerca

use App\Models\Order;

$orders = Order::search('Star Trek')->get();
Da route o controller:
use Illuminate\Http\Request;

Route::get('/search', function (Request $request) {
    return Order::search($request->search)->get();
});
Grezzo:
$orders = Order::search('Star Trek')->raw();

Paginazione

$orders = Order::search('Star Trek')->paginate();
$orders = Order::search('Star Trek')->paginate(15);
Con database engine anche simplePaginate:
$orders = Order::search('Star Trek')->simplePaginate(15);
<div class="container">
    @foreach ($orders as $order)
        {{ $order->price }}
    @endforeach
</div>

{{ $orders->links() }}

Filtri e ordinamento

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();
Con Meilisearch registra prima gli attributi filtrabili.
Personalizza la query Eloquent:
use Illuminate\Database\Eloquent\Builder;

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

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();
In batch import:
use Illuminate\Database\Eloquent\Builder;

protected function makeAllSearchableUsing(Builder $query): Builder
{
    return $query->with('author');
}
Nelle code non è sempre possibile.

Soft delete

'soft_delete' => true,
$orders = Order::search('Star Trek')->withTrashed()->get();
$orders = Order::search('Star Trek')->onlyTrashed()->get();

Motori personalizzati

Estendi Laravel\Scout\Engines\Engine e implementa 8 metodi:
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);
Registra in AppServiceProvider::boot():
use App\ScoutExtensions\MySqlSearchEngine;
use Laravel\Scout\EngineManager;

public function boot(): void
{
    resolve(EngineManager::class)->extend('mysql', function () {
        return new MySqlSearchEngine;
    });
}
'driver' => 'mysql',

Pagine correlate

Eloquent ORM

Basi dei modelli Eloquent.

Relazioni Eloquent

Definizione delle relazioni e eager loading.

Code

Scout aggiorna gli indici in background via code.
Ultima modifica il 13 luglio 2026