Come usare Laravel Scout
Laravel Scout è un pacchetto ufficiale del framework Laravel che fornisce un'implementazione semplice e basata su driver per aggiungere la ricerca full-text ai modelli Eloquent. Grazie a Scout, è possibile mantenere automaticamente sincronizzati gli indici di ricerca con i record del database, rendendo la ricerca full-text accessibile e facilmente configurabile anche in applicazioni complesse.
In questo articolo vedremo come installare e configurare Laravel Scout, come integrarlo con i principali driver disponibili, come indicizzare i modelli Eloquent e come eseguire ricerche avanzate all'interno di un'applicazione Laravel.
Installazione
Laravel Scout viene installato tramite Composer. Per aggiungere il pacchetto al progetto è sufficiente eseguire il seguente comando dalla radice del progetto:
composer require laravel/scout
Dopo l'installazione, è necessario pubblicare il file di configurazione di Scout con il comando Artisan dedicato:
php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider"
Questo comando creerà il file config/scout.php nella directory di configurazione del progetto, dove è possibile definire il driver da utilizzare e le relative opzioni.
Configurazione del file scout.php
Il file di configurazione generato contiene le impostazioni principali per il funzionamento di Scout. Di seguito è riportata la struttura di base con le opzioni più rilevanti:
<?php
// Configurazione principale di Laravel Scout
return [
// Driver di ricerca da utilizzare (algolia, meilisearch, typesense, database, collection)
'driver' => env('SCOUT_DRIVER', 'algolia'),
// Prefisso applicato agli indici di ricerca
'prefix' => env('SCOUT_PREFIX', ''),
// Indica se la sincronizzazione con l'indice è attiva
'queue' => env('SCOUT_QUEUE', false),
// Numero di modelli processati per ogni operazione di importazione in batch
'chunk' => [
'searchable' => 500,
'unsearchable' => 500,
],
// Configurazione Algolia
'algolia' => [
'id' => env('ALGOLIA_APP_ID', ''),
'secret' => env('ALGOLIA_SECRET', ''),
],
// Configurazione Meilisearch
'meilisearch' => [
'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'),
'key' => env('MEILISEARCH_KEY', null),
'index-settings' => [],
],
// Configurazione Typesense
'typesense' => [
'client-settings' => [
'api_key' => env('TYPESENSE_API_KEY', 'xyz'),
'nodes' => [
[
'host' => env('TYPESENSE_HOST', 'localhost'),
'port' => env('TYPESENSE_PORT', '8108'),
'protocol' => env('TYPESENSE_PROTOCOL', 'http'),
],
],
],
'model-settings' => [],
],
];
I driver disponibili
Laravel Scout supporta diversi driver di ricerca, ognuno con caratteristiche e requisiti specifici. La scelta del driver dipende dalle esigenze del progetto, dal budget e dall'infrastruttura disponibile.
Driver Algolia
Algolia è un servizio di ricerca cloud hosted, noto per le sue prestazioni elevate e per la facilità di integrazione. Per utilizzarlo è necessario installare il pacchetto client ufficiale:
composer require algolia/algoliasearch-client-php
Le credenziali di accesso vanno inserite nel file .env:
# Configurazione Algolia nel file .env
SCOUT_DRIVER=algolia
ALGOLIA_APP_ID=il_tuo_app_id
ALGOLIA_SECRET=la_tua_secret_key
Driver Meilisearch
Meilisearch è un motore di ricerca open source, self-hosted e altamente performante. Può essere eseguito localmente o tramite Docker. Per installare il client PHP necessario:
composer require meilisearch/meilisearch-php http-interop/http-factory-guzzle
La configurazione nel file .env prevede l'indirizzo del server e, se configurata, la chiave di autenticazione:
# Configurazione Meilisearch nel file .env
SCOUT_DRIVER=meilisearch
MEILISEARCH_HOST=http://localhost:7700
MEILISEARCH_KEY=la_tua_master_key
Per avviare Meilisearch tramite Docker Compose, è possibile usare la seguente configurazione:
# Servizio Meilisearch per Docker Compose
services:
meilisearch:
image: getmeili/meilisearch:latest
ports:
- "7700:7700"
volumes:
- meilisearch_data:/meili_data
environment:
MEILI_MASTER_KEY: la_tua_master_key
volumes:
meilisearch_data:
Driver Typesense
Typesense è un altro motore di ricerca open source, veloce e tollerante agli errori tipografici. L'installazione del client richiede:
composer require typesense/typesense-php
Driver database e collection
Per applicazioni più semplici o in fase di sviluppo, Scout mette a disposizione due driver nativi che non richiedono servizi esterni. Il driver database utilizza le funzionalità LIKE di SQL per eseguire le ricerche direttamente sul database, mentre il driver collection filtra i modelli in memoria usando le collection di Laravel. Questi driver sono adatti per set di dati ridotti e non per ambienti di produzione con grandi volumi.
# Driver database per ambienti di sviluppo
SCOUT_DRIVER=database
Rendere un modello ricercabile
Per abilitare la ricerca full-text su un modello Eloquent, è sufficiente aggiungere il trait Searchable alla classe del modello. Scout si occuperà automaticamente di mantenere l'indice aggiornato ogni volta che il record viene creato, modificato o eliminato.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
class Article extends Model
{
// Abilitazione della ricerca full-text sul modello
use Searchable;
protected $fillable = [
'title',
'content',
'author',
'published_at',
];
}
Personalizzare i dati indicizzati
Per impostazione predefinita Scout serializza tutti gli attributi del modello e li invia all'indice. È possibile personalizzare i dati indicizzati sovrascrivendo il metodo toSearchableArray nel modello:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Laravel\Scout\Searchable;
class Article extends Model
{
use Searchable;
protected $fillable = [
'title',
'content',
'author',
'category_id',
'published_at',
'is_published',
];
/**
* Definisce i campi da includere nell'indice di ricerca.
*/
public function toSearchableArray(): array
{
return [
// Solo i campi rilevanti per la ricerca
'id' => $this->id,
'title' => $this->title,
'content' => $this->content,
'author' => $this->author,
'published_at' => $this->published_at?->toDateString(),
];
}
}
Personalizzare il nome dell'indice
Per impostazione predefinita Scout utilizza il nome della tabella del modello come nome dell'indice. È possibile sovrascrivere questo comportamento tramite il metodo searchableAs:
/**
* Restituisce il nome dell'indice di ricerca per il modello.
*/
public function searchableAs(): string
{
// Prefisso personalizzato per separare gli indici per ambiente
return 'articles_index';
}
Importare i dati esistenti
Quando si aggiunge Scout a un progetto già esistente con dati nel database, è necessario importare manualmente i record nell'indice tramite il comando Artisan dedicato:
# Importazione di tutti i record del modello Article nell'indice
php artisan scout:import "App\Models\Article"
Per rimuovere tutti i record di un modello dall'indice, si utilizza invece:
# Rimozione di tutti i record del modello Article dall'indice
php artisan scout:flush "App\Models\Article"
È anche possibile eliminare e ricreare completamente un indice:
# Eliminazione dell'indice
php artisan scout:delete-index articles_index
# Ricreazione dell'indice e reimportazione dei dati
php artisan scout:import "App\Models\Article"
Eseguire ricerche
Una volta configurato Scout e indicizzati i dati, è possibile eseguire ricerche full-text sui modelli tramite il metodo statico search. Scout restituisce un'istanza di Builder compatibile con Eloquent, che supporta la paginazione e altri metodi di query.
<?php
namespace App\Http\Controllers;
use App\Models\Article;
use Illuminate\Http\Request;
class SearchController extends Controller
{
/**
* Esegue la ricerca full-text sugli articoli.
*/
public function search(Request $request)
{
// Ricerca semplice per termine
$query = $request->input('query', '');
$articles = Article::search($query)->get();
return response()->json($articles);
}
}
Per ottenere risultati paginati, si utilizza il metodo paginate, identico a quello di Eloquent:
// Ricerca con paginazione: 15 risultati per pagina
$articles = Article::search($query)->paginate(15);
return view('articles.index', compact('articles'));
Ricerca con vincoli aggiuntivi via Eloquent
È possibile combinare la ricerca Scout con i normali vincoli Eloquent tramite il metodo query. Questo permette di filtrare i risultati restituiti dall'indice applicando ulteriori condizioni a livello di database:
// Ricerca full-text combinata con filtri Eloquent aggiuntivi
$articles = Article::search($query)
->query(function ($builder) {
// Filtra solo gli articoli pubblicati
$builder->where('is_published', true)
->orderBy('published_at', 'desc');
})
->paginate(10);
Filtraggio nativo dell'indice (where)
Oltre ai vincoli Eloquent, Scout mette a disposizione il metodo where per applicare filtri direttamente sull'indice di ricerca, senza passare dal database. Questa modalità è supportata dai driver Algolia, Meilisearch e Typesense:
// Filtro diretto sull'indice di ricerca
$articles = Article::search($query)
->where('author', 'Mario Rossi')
->get();
È possibile anche filtrare per valori multipli tramite il metodo whereIn:
// Filtro per più autori sull'indice
$articles = Article::search($query)
->whereIn('author', ['Mario Rossi', 'Luca Bianchi'])
->get();
Ordinamento dei risultati
Scout supporta l'ordinamento dei risultati tramite il metodo orderBy, che viene applicato direttamente sull'indice quando il driver lo supporta:
// Ordinamento per data di pubblicazione decrescente
$articles = Article::search($query)
->orderBy('published_at', 'desc')
->get();
Soft delete e ricerca
Se il modello utilizza il trait SoftDeletes, Scout è in grado di gestire automaticamente l'esclusione dei record eliminati dall'indice. Per abilitare questa funzionalità occorre impostare l'opzione soft_delete nella configurazione:
// In config/scout.php
'soft_delete' => true,
Con questa impostazione attiva, Scout mantiene i record eliminati nell'indice ma li filtra nelle ricerche normali. Per includere i record eliminati nei risultati si usa il metodo withTrashed:
// Ricerca che include i record con soft delete
$articles = Article::search($query)->withTrashed()->get();
// Ricerca che restituisce solo i record eliminati
$deletedArticles = Article::search($query)->onlyTrashed()->get();
Sospendere e riprendere l'indicizzazione
In alcuni scenari è necessario eseguire operazioni di massa sui modelli senza aggiornare l'indice di ricerca ad ogni modifica, per evitare un numero eccessivo di chiamate al servizio. Scout fornisce i metodi withoutSyncingToSearch e disableSearchSyncing per gestire questi casi:
// Esecuzione di operazioni in massa senza aggiornare l'indice
Article::withoutSyncingToSearch(function () {
// Queste modifiche non verranno sincronizzate con Scout
Article::where('is_published', false)->update([
'content' => 'Contenuto aggiornato in blocco',
]);
});
In alternativa, è possibile usare i metodi di istanza sul singolo modello:
$article = Article::find(1);
// Disabilitazione della sincronizzazione per questa istanza
$article->disableSearchSyncing();
$article->update(['title' => 'Nuovo titolo']);
// Riabilitazione della sincronizzazione
$article->enableSearchSyncing();
Indicizzazione manuale
È possibile aggiungere o rimuovere manualmente un modello dall'indice tramite i metodi searchable e unsearchable:
$article = Article::find(1);
// Aggiunta manuale all'indice
$article->searchable();
// Rimozione manuale dall'indice
$article->unsearchable();
Questi metodi funzionano anche su collection di modelli:
// Aggiunta in blocco di più articoli all'indice
Article::where('is_published', true)->searchable();
// Rimozione in blocco dall'indice
Article::where('is_published', false)->unsearchable();
Utilizzo della coda per l'indicizzazione
Per evitare che le operazioni di indicizzazione rallentino le richieste HTTP, Scout supporta l'esecuzione asincrona tramite le code di Laravel. Per abilitare questa modalità è sufficiente impostare l'opzione queue nel file .env:
# Abilitazione della coda per Scout
SCOUT_QUEUE=true
Con la coda abilitata, tutte le operazioni di sincronizzazione con l'indice verranno inviate alla coda di Laravel e processate in background dal worker. È necessario assicurarsi che il worker sia in esecuzione:
# Avvio del worker per processare i job in coda
php artisan queue:work
Configurare le impostazioni degli indici Meilisearch
Meilisearch consente di configurare attributi filtrabili e ordinabili direttamente dal file di configurazione di Scout tramite la chiave index-settings. Questo è utile per ottimizzare le prestazioni dell'indice in base ai campi effettivamente utilizzati nelle ricerche:
// In config/scout.php, sezione meilisearch
'meilisearch' => [
'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'),
'key' => env('MEILISEARCH_KEY', null),
'index-settings' => [
// Impostazioni specifiche per l'indice degli articoli
App\Models\Article::class => [
'filterableAttributes' => ['author', 'is_published', 'published_at'],
'sortableAttributes' => ['published_at', 'title'],
],
],
],
Dopo aver modificato queste impostazioni, è necessario sincronizzarle con Meilisearch tramite il comando Artisan:
# Sincronizzazione delle impostazioni degli indici con Meilisearch
php artisan scout:sync-index-settings
Esempio completo: controller di ricerca
Di seguito è riportato un esempio completo di controller che integra la ricerca Scout con validazione dell'input e risposta JSON strutturata, adatto a essere usato come endpoint API:
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Article;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ArticleSearchController extends Controller
{
/**
* Esegue la ricerca full-text sugli articoli pubblicati.
*/
public function __invoke(Request $request): JsonResponse
{
// Validazione dei parametri di ricerca
$validated = $request->validate([
'query' => ['required', 'string', 'min:2', 'max:100'],
'per_page' => ['integer', 'min:5', 'max:50'],
'author' => ['nullable', 'string'],
]);
$query = $validated['query'];
$perPage = $validated['per_page'] ?? 15;
// Costruzione della ricerca con filtri opzionali
$searchBuilder = Article::search($query)
->query(function ($builder) {
// Limita la ricerca solo agli articoli pubblicati
$builder->where('is_published', true)
->orderBy('published_at', 'desc');
});
// Aggiunta del filtro per autore se specificato
if (isset($validated['author'])) {
$searchBuilder->where('author', $validated['author']);
}
$results = $searchBuilder->paginate($perPage);
return response()->json([
'data' => $results->items(),
'meta' => [
'current_page' => $results->currentPage(),
'last_page' => $results->lastPage(),
'per_page' => $results->perPage(),
'total' => $results->total(),
],
]);
}
}
La route corrispondente da registrare in routes/api.php:
// Registrazione della route di ricerca API
use App\Http\Controllers\Api\ArticleSearchController;
Route::get('/articles/search', ArticleSearchController::class);
Conclusioni
Laravel Scout rappresenta una soluzione elegante e flessibile per integrare la ricerca full-text nelle applicazioni Laravel, senza dover scrivere da zero la logica di indicizzazione e sincronizzazione. Grazie all'astrazione dei driver, è possibile scegliere il motore di ricerca più adatto alle proprie esigenze, passando da soluzioni cloud come Algolia a soluzioni self-hosted come Meilisearch, fino ai driver nativi per lo sviluppo locale.
La profonda integrazione con Eloquent rende Scout particolarmente intuitivo per chi è già familiare con l'ORM di Laravel: la logica di ricerca si affianca naturalmente alle query esistenti, riducendo al minimo la quantità di codice necessaria per offrire un'esperienza di ricerca efficace agli utenti.