runroom/fos-elastica-bundle
A Symfony bundle that integrates Elasticsearch through the Elastica client, extending FOSElasticaBundle with index configuration, data population commands, and search services for keeping application entities indexed and queryable.
Before you install anything, note the reality of this package: runroom/fos-elastica-bundle is an archived fork of the long-standing friendsofsymfony/elastica-bundle. The metadata (0 stars, 0 dependents, archived) means it is not a de-facto standard you should reach for on a greenfield project. Treat it as a compatibility/patched fork of upstream FOSElasticaBundle — evaluate upstream first, and only use this fork if you specifically need Runroom's patches (typically Symfony/Elastica version bumps). If you adopt it, pin the exact version and plan an exit path.
Assuming you are already on it (or comparing it to upstream), the setup path is:
Install with Composer, matching the Elasticsearch server version to the Elastica client version the bundle supports:
composer require runroom/fos-elastica-bundle
Check composer.json constraints for Symfony, PHP, and ruflin/elastica — the bundle is only a thin integration layer; the ES version support comes from Elastica.
Register the bundle in config/bundles.php (or bundles.php on older Symfony):
FOS\ElasticaBundle\FOSElasticaBundle::class => ['all' => true],
Configure clients and indexes in config/packages/fos_elastica.yaml:
fos_elastica:
clients:
default:
host: '%env(ELASTICSEARCH_HOST)%'
port: 9200
indexes:
app:
finder: ~
persistence:
driver: orm
model: App\Entity\Product
provider: ~
listener: ~
properties:
name: { type: text }
price: { type: float }
First use case — index a Doctrine entity and search it. Run the populate command, then query through the finder service:
bin/console fos:elastica:populate --index=app
$finder = $this->container->get('fos_elastica.finder.app');
$results = $finder->find('widget');
Where to look first: the bundle's DependencyInjection/Configuration.php is the authoritative schema for every option (it is more current than most docs), and bin/console debug:container fos_elastica lists the generated services and aliases.
Mapper configuration lives in YAML (or PHP/XML), not annotations. The index definition is the source of truth for mappings, analyzers, and per-type persistence. Keep it in version control and treat mapping changes as schema migrations — see gotchas.
Finder usage. The bundle exposes one finder service per index (fos_elastica.finder.<index>). The common patterns:
$finder = $this->container->get('fos_elastica.finder.app');
// Raw results (arrays by default)
$results = $finder->find($query);
// Hydrated entities via the configured transformer
$results = $finder->findHybrid($query);
// Pagination for controllers
$paginator = $finder->findPaginated($query);
// Adapter for Pagerfanta/KnpPaginator
$adapter = $finder->createPaginatorAdapter($query);
Query objects are plain Elastica (Elastica\Query, Elastica\Query\BoolQuery, aggregations, etc.). The bundle does not abstract query building away, which is good — you get full ES expressiveness — but it means your query code is coupled to Elastica's API and version.
Persistence listeners vs. manual indexing. With persistence.listener: ~, Doctrine ORM postPersist/postUpdate/postRemove events index documents automatically. That is convenient but fragile at scale (see gotchas). For more control, drop the listener and index explicitly:
$index = $this->container->get('fos_elastica.index.app');
$index->addDocument(new \Elastica\Document($product->getId(), $serialized));
Custom providers. When the default ORM provider does not fit (e.g., you need aggregates, translations, or cross-entity data), implement the bundle's provider interface and register it as fos_elastica.provider.<index>.<type>. This is the standard extension point for anything that isn't a straight entity serialization.
Populate command flags are the day-to-day workhorse:
bin/console fos:elastica:populate --index=app --batch-size=500 --sleep=100
bin/console fos:elastica:populate --no-delete # append instead of clean
bin/console fos:elastica:reset --index=app # drop/recreate then populate
Use --sleep when indexing parent/child relationships or when the server needs backpressure. Use --batch-size to keep memory bounded.
Aliases for zero-downtime reindexing. Configure use_alias: true on the index; fos:elastica:reset then creates a new physical index and atomically repoints the alias. This is the recommended workflow for mapping changes instead of indexing in place.
Serializer integration. For JMS Serializer users, the bundle ships a serializer handler and callback provider. Configure serializer_groups per type and let the provider serialize entities rather than hand-building arrays. Keep groups narrow — over-serializing entities into ES is a common source of bloat and stale embedded data.
Testing. Give tests their own index configuration and clear it between runs. The bundle supports distinct index names per environment; if you can't prefix, at minimum fos:elastica:reset in a test bootstrap. Parallel test runners will collide on the same index name unless you isolate them.
It is archived. There will be no security or compatibility fixes from Runroom. Upstream FOSElasticaBundle is the maintained option. If you need this fork, pin a release, mirror it in your own VCS (Composer repositories + vcs type), and track upstream for fixes worth cherry-picking.
Version matrix is everything. Elastica's version determines which Elasticsearch servers work; the bundle's version determines which Symfony/PHP versions work. A mismatch produces runtime NoNodesAvailableException or 400s on mapping creation, not a helpful composer error. Check both constraints before upgrading either side.
Documents are not searchable until refresh. Default refresh_interval is 1s. During bulk populate, set refresh_interval: -1 and number_of_replicas: 0 on the target index, then restore after. Otherwise populate time balloons.
Doctrine listener pitfalls. The ORM listener indexes on flush but does not know about DQL updates, raw SQL, or bulk operations. It also fires inside the flush, which can cause recursion (repositories inside listeners triggering another flush) and can index entities in an inconsistent state if the flush fails downstream. For high-volume writes, disable the listener and index in a post-commit hook or via a queue consumer.
Mapping changes require a full reindex. There is no auto-migration. Bump the alias/index, reindex, flip the alias. Keep old indices around briefly for rollback.
nested mapping is not optional for nested queries. Querying a nested field without the mapping (or without wrapping the query in Elastica\Query\Nested) silently returns zero hits — a classic debugging trap.
Type shape changed in ES 7. The bundle's types/type configuration is a legacy of ES 6. On ES 7+ you use a single _doc type, and the mapping structure differs. Confirm the fork's mapping builder matches your server version; the YAML keys are not identical across ES majors.
Finder results are untyped by default. Without a transformer configured, find() returns arrays (the raw _source). If you expect entities, set finder: ~ and use findHybrid(), or configure a custom transformer. Mixing the two in one codebase causes subtle array vs. entity bugs.
Debugging. Inspect the cluster directly (curl localhost:9200/_cat/indices, _mapping, _search?explain=true) rather than guessing from PHP. In Symfony, debug:container fos_elastica shows the generated service ids — aliases can change between bundle versions, so resolve them rather than hardcoding.
Extension points, in rough order of usefulness: custom providers, the fos_elastica.client service for raw client access, decorating the finder, and a custom IndexBuilder/populator for non-standard data sources. Avoid subclassing the bundle's internals — they are not covered by a stability guarantee, especially in a fork.
Elasticsearch is not a database. Do not treat the index as authoritative. Keep the relational store as the source of truth and treat the index as a rebuildable cache; that mindset makes reindexing, failures, and version upgrades tractable.
How can I help you explore Laravel packages today?