fos-gregooz/elastica-bundle
Symfony bundle that integrates Elasticsearch via the Elastica client, letting you define indexes and mappings in configuration and keep them in sync with Doctrine entities through automatic listeners. Provides repository services so you can run search queries without touching the low-level client.
Read the composer.json before anything else. The package name is fos-gregooz/elastica-bundle but the namespace is FOS\ElasticaBundle — it's a Symfony Bundle subclass, a fork of friendsofsymfony/elastica-bundle. On a Laravel project, composer require fos-gregooz/elastica-bundle installs the code, then nothing happens: no service provider auto-discovery, no config/elastica.php to publish, no artisan commands registered, no facade. The composer.json will not declare extra.laravel.providers, and even if it did, the bundle boots through Symfony's Kernel/bundle config pipeline, not Laravel's container.
The minimal path for a Laravel developer, which is what any competent reader should do first, is not this package at all:
composer require laravel/scout babenkoivan/elastic-scout-driverSCOUT_DRIVER=elastic and ELASTICSEARCH_HOST in .env.php artisan vendor:publish --provider="Laravel\Scout\ScoutServiceProvider" and php artisan vendor:publish --provider="Babenkoivan\Scout\Elasticsearch\ElasticsearchServiceProvider".use Laravel\Scout\Searchable; to the model you want to index.php artisan scout:import "App\Models\Product".Product::search('blue widget')->take(20)->get().That is the equivalent day-to-day workflow the FOSElasticaBundle represents in Symfony (fos_elastica YAML mapping + fos:elastica:populate + a configured repository service). If search is on the roadmap, this is the path — not the bundle.
Where to look first when the framework question is unresolved:
friendsofsymfony/elastica-bundle upstream, not this fork. Zero stars and zero dependents means no maintainer, no release discipline, no CVE path.laravel/scout and the Elasticsearch driver first, then the official elasticsearch/elasticsearch client if Scout is too opinionated.First use case: index App\Models\Product and search by name + description. If you follow Scout, this is a half-day. If you try to follow the bundle's documentation, every step lands on Symfony concepts (Kernel, config/packages/fos_elastica.yaml, fos_elastica.index.* services) that do not exist in a Laravel tree.
Pattern 1 — Scout + Elasticsearch driver (the default).
The model uses the Searchable trait; custom index mapping goes on the driver via config/scout.php. Index shape comes from searchableAs() (the alias-friendly index name) and toSearchableArray() (the document body). The two extension points a developer actually uses day-to-day are searchableAs() to control index naming (and to support products_v1 / products_v2 alias swaps later) and toSearchableArray() to flatten relations before they hit ES. For custom query DSL that Scout's fluent builder does not expose, override the searchableUsing() builder or drop to the driver's getClient() directly.
Pattern 2 — thin repository over the official client.
For anything beyond CRUD search (facets, search_after pagination, custom analyzers, ingest pipelines), bind Elastic\Elasticsearch\Client as a singleton in AppServiceProvider and wrap it in a ProductIndex class with index(), bulk(), delete(), search() methods. Bulk reindexing uses $client->bulk(['body' => $rows]) where each pair is ['index' => [...]] then the doc — inspect $response['errors'] on every call. Alias-based cutover is two calls: indices()->create(['index' => 'products_v2', 'body' => $mapping]), load data, then indices()->updateAliases(['body' => [['remove' => ...], ['add' => ['index' => 'products_v2', 'alias' => 'products']]]]). This is the pattern to reach for the moment the bundle's Symfony config starts feeling like a cage — which, on Laravel, is immediately.
Pattern 3 — what the bundle actually gives its Symfony users (for parity).
A fos_elastica: config tree with clients, indexes, types, and Doctrine listener wiring, plus a fos:elastica:populate command. If you're evaluating this fork specifically, the only reason to keep reading is to extract that YAML mapping shape as a reference for the equivalent raw JSON mapping you'd write against the official client. The translation is near 1:1: mappings.properties becomes the ES mapping body verbatim.
Integration tips.
SCOUT_QUEUE=true in .env and run php artisan queue:work, or dispatch a ReindexModel job on created/updated/deleted events if you're using Pattern 2. Never index synchronously inside an Eloquent observer in production.searchable cache if you cache search results; ES's refresh delay means a "search immediately after write" flow will return stale data unless you ->refresh('wait_for'), which Scout does not expose — so design for eventual consistency instead of trying to defeat it.php artisan scout:import "App\Models\Product" --chunk=500 and monitor worker memory. Scout's importer is chunkById-based but each document is serialized in full; large models OOM around chunk sizes of a few thousand.config/elasticsearch.php config file, a ReindexCommand artisan command, and an observer. That's a day of work and it stays inside Laravel idioms.fos_elastica config tree require symfony/config, symfony/dependency-injection, and a Kernel to compile. Wiring them manually is multi-day work with zero reusable output. If you're on Laravel, drop the package from composer.json and use Scout.composer install on a fresh machine breaks the day the fork is deleted. If you must depend on it (you shouldn't), pin it via a vcs or path repository pointing at your own mirror, and diff every update against upstream before merging.elasticsearch/elasticsearch client should be the default — the bundle's abstraction is a maintenance tax you inherit.$response['errors'] === true only tells you some item failed. Iterate $response['items'], check each item.index.error, and fail the batch explicitly. This is the single most common cause of "our index is quietly missing documents" tickets with any of these tools.save() flake for this reason — sleep(2) in tests or mock the index. In production, don't claim "real-time search" in a UI that isn't.from + size is capped at 10,000 by default. Scout does not abstract this. Any UI with "page N of results" needs search_after or scroll — plan for it before users hit page 100.toSearchableArray() does not change the ES mapping. The next document with the new shape will be rejected by ES (or silently coerced). The only fix is scout:flush + scout:import (or, better, the alias-swap from day one). Wire the alias before the first mapping change ships; retrofitting it after an incident is painful.setLogger() — point it at Laravel's Log in AppServiceProvider::boot(). Elastica's failure path surfaces errors through two abstraction layers (bundle → Elastica → official client) with less context; another reason the abstraction is a net cost for a Laravel team.fos_elastica.index.<name>.<type> repository services. On Laravel, the equivalent is your own ProductIndex class. Both are the place a team will spend 80% of its time — invest in making that one class good (typed methods, logged queries, a debug() helper that dumps the raw DSL) regardless of which path you take.How can I help you explore Laravel packages today?