jackardios / es-scout-driver
Advanced Elasticsearch driver for Laravel Scout with full Query DSL support
Requires
- php: ^8.2
- elasticsearch/elasticsearch: ^8.0 || ^9.0
- illuminate/bus: ^12.0 || ^13.0
- illuminate/contracts: ^12.0 || ^13.0
- illuminate/database: ^12.0 || ^13.0
- illuminate/pagination: ^12.0 || ^13.0
- illuminate/support: ^12.0 || ^13.0
- laravel/scout: ^10.24 || ^11.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.88
- orchestra/testbench: ^10.0 || ^11.0
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^11.5.50 || ^12.5.8
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-08 01:44:37 UTC
README
Advanced Elasticsearch driver for Laravel Scout with full Query DSL support.
Features
- Full Elasticsearch Query DSL support
- Fluent API for building complex queries
- Bool queries with
must,should,filter,mustNot - Full-text queries:
match,multi_match,match_phrase,query_string - Term-level queries:
term,terms,range,exists,prefix,wildcard,regexp,fuzzy,ids - Geo queries:
geo_distance,geo_bounding_box,geo_shape - Compound queries:
bool,nested,function_score,dis_max,boosting,constant_score - Joining queries:
has_child,has_parent,parent_id - Aggregations:
terms,histogram,date_histogram,range,geo_distance,filter,filters,global,nested,reverse_nested,composite,avg,sum,min,max,stats,extended_stats,cardinality,percentiles,top_hits,geo_bounds,geo_centroid - Sorting with multiple options
- Highlighting
- Suggestions
- Pagination with cursor support
- Multi-index search
- Soft deletes support
Requirements
- PHP 8.2+ (Laravel 13 requires 8.3+)
- Laravel 12 or 13
- Laravel Scout 10.24+ or 11
- Elasticsearch 8.x or 9.x
Laravel 10 and 11 are supported by the 0.x line: composer require jackardios/es-scout-driver:^0.1.
The elasticsearch/elasticsearch client must have the same major version as the server. Composer installs the 9.x
client by default; for an Elasticsearch 8 server add composer require elasticsearch/elasticsearch:^8.0 to your
application. See Compatibility.
Installation
composer require jackardios/es-scout-driver
Publish the configuration files:
php artisan vendor:publish --provider="Jackardios\EsScoutDriver\ServiceProvider"
Configure your Elasticsearch connection in .env:
SCOUT_DRIVER=elastic ELASTIC_HOST=localhost:9200
Quick Start
1. Add the Searchable trait to your model
use Jackardios\EsScoutDriver\Searchable; class Book extends Model { use Searchable; public function toSearchableArray(): array { return [ 'id' => $this->id, 'title' => $this->title, 'author' => $this->author, 'price' => $this->price, 'published_at' => $this->published_at, ]; } }
2. Create your index (optional but recommended)
For index management, we recommend babenkoivan/elastic-migrations:
composer require babenkoivan/elastic-migrations php artisan elastic:make:migration create_books_index php artisan elastic:migrate
Note: The
config/elastic.client.phpis compatible with elastic-migrations.
3. Index your data
php artisan scout:import "App\Models\Book"
4. Search
use Jackardios\EsScoutDriver\Support\Query; // Simple search $books = Book::searchQuery(Query::match('title', 'laravel'))->execute(); // Complex search with bool query $books = Book::searchQuery() ->must(Query::match('title', 'laravel')) ->filter(Query::term('status', 'published')) ->filter(Query::range('price')->gte(10)->lte(50)) ->sort('published_at', 'desc') ->size(20) ->execute(); // Get models $models = $books->models(); // Get total count $total = $books->total;
Basic Usage
Match Query
Book::searchQuery(Query::match('title', 'elasticsearch'))->execute(); // With options Book::searchQuery( Query::match('title', 'elasticsearch') ->fuzziness('AUTO') ->operator('and') )->execute();
Multi-Match Query
Book::searchQuery( Query::multiMatch(['title', 'description'], 'search text') ->type('best_fields') ->fuzziness('AUTO') )->execute();
Bool Query
Book::searchQuery() ->must(Query::match('title', 'laravel')) ->must(Query::match('description', 'framework')) ->should(Query::term('featured', true)) ->filter(Query::range('price')->lte(100)) ->mustNot(Query::term('status', 'draft')) ->execute();
Range Query
Book::searchQuery( Query::range('price')->gte(10)->lte(50) )->execute(); // Date range Book::searchQuery( Query::range('published_at') ->gte('2024-01-01') ->lte('now') ->format('yyyy-MM-dd') )->execute();
Sorting
use Jackardios\EsScoutDriver\Sort\Sort; Book::searchQuery(Query::matchAll()) ->sort('price', 'asc') ->sort('_score', 'desc') ->execute(); // Advanced sorting Book::searchQuery(Query::matchAll()) ->sort(Sort::field('price')->desc()->missing('_last')) ->sort(Sort::score()) ->execute();
Pagination
// Standard pagination $paginator = Book::searchQuery(Query::matchAll()) ->paginate(perPage: 15, pageName: 'page', page: 1); // Access in Blade @foreach ($paginator->models() as $book) {{ $book->title }} @endforeach {{ $paginator->links() }}
perPage must be greater than 0, and page must be greater than or equal to 1.
Aggregations
use Jackardios\EsScoutDriver\Aggregations\Agg; $result = Book::searchQuery(Query::matchAll()) ->aggregate('avg_price', Agg::avg('price')) ->aggregate('by_author', Agg::terms('author')->size(10)) ->execute(); // Get aggregation results $avgPrice = $result->aggregationValue('avg_price'); $authorBuckets = $result->buckets('by_author');
Highlighting
$result = Book::searchQuery(Query::match('title', 'laravel')) ->highlight('title', preTags: ['<em>'], postTags: ['</em>']) ->highlight('description') ->execute(); foreach ($result->hits() as $hit) { $highlights = $hit->highlight; // ['title' => ['<em>Laravel</em> Guide']] }
Scout Integration
Model::search('text')->get()returns 10 models unlesstake()is called: Elasticsearch's default size.- Scout 11's
semantic()andhybrid()builder modes are ignored; useQuery::semantic(),Query::knn()orSearchBuilder::knn(). - A model that uses this package's
Searchabletrait works with theelasticandnulldrivers only; withcollection,databaseor another engine it throws aSearchException. - The package registers its own
nulldriver (Engine\NullEngine) in place of Scout's, whichever driver is configured. - With
scout.driver=elastic, Scout'sRemoveFromSearchjob is replaced byJobs\RemoveFromSearch, unlessScout::removeFromSearchUsing()already names another job. The job carries the index, id, routing and connection of each model instead of the models and sends the deletes to the client itself, so it does not call the engine: an engine wrapper does not see queued deletes. To change them, extend the job or write your own and register it withScout::removeFromSearchUsing(). The job refuses a model without the package's trait, so an application that also indexes models with another Scout engine must register a job that handles both.
Documentation
- Search Builder - Main search API
- Queries - All query types
- Aggregations - Aggregation types
- Sorting - Sorting options
- Search Results - Working with results
- Configuration - Configuration options
- Compatibility - ES 8.x/9.x version notes
Backward Compatibility
From 1.0.0 the package follows Semantic Versioning. Breaking changes to the public API ship only in a new major version.
The public API is every public class, method, constant and property not marked @internal: Searchable,
SearchBuilder, SearchResult, Hit, Suggestion, Paginator, SearchCursor, the Query, Agg and Sort
factories and the classes they return, the enums, the exceptions and the configuration files.
QueryInterface,AggregationInterface,SortInterfaceandEngineInterfacemay be implemented outside the package. New methods are added to them only in a major version.Engineis final. An engine of your own implementsEngineInterfaceand extendsLaravel\Scout\Engines\Engine, which the interface cannot express; to change the behaviour ofEngine, hold one and forward to it. For a test engine, extendEngine\NullEngine(does nothing) and override what you need: it keeps working when a major version adds a method toEngineInterface. Register the engine under theelasticdriver name withEngineManager::extend(): the package swaps theRemoveFromSearchjob only for that name.Jobs\RemoveFromSearchmay be extended; its$operationsandhandle(Client $client)are covered.SearchBuildermay be extended. Its state is private: a subclass works through the public methods.- Parameter names are part of the public API: methods may be called with named arguments.
- The
Query\Concerns,Aggregations\ConcernsandSort\Concernstraits are not covered: their methods belong to the public API of the classes that use them, but using a trait in your own class may break in a minor release. @internalcode (the engine helpers, model resolution, thefromRaw()factories, thePaginatorandSearchCursorconstructors and everySearchResultconstructor argument after$raw) may change in any release.
License
MIT License. See LICENSE for details.