Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Elastica Bundle Laravel Package

friendsofsymfony/elastica-bundle

FOSElasticaBundle integrates Elasticsearch and the Elastica library into Symfony, with configurable index mappings and Doctrine event listeners for automatic indexing. Version 7 supports Elasticsearch 8 and 9 on Symfony 6.4+ and PHP 8.1+.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the version that matches your stack — the bundle majors are pinned to Elasticsearch majors, so this is the first decision you make, not the last:

composer require friendsofsymfony/elastica-bundle:^7.1   # ES 8.x, Symfony 6.4/7.4/8.0, PHP 8.1+
# or ^6.6 for Elasticsearch 7.x / Symfony 5.4–7.1 / PHP 7.4–8.1

With Flex, accept the recipe and you get config/packages/fos_elastica.yaml plus env wiring for the host. Read doc/setup.md, then doc/configuration.md — the config reference is the real documentation for this bundle; almost every question you will have ("how do I map a nested object?", "how do I index a relation?") is answered by a key in that file.

The minimum viable config (v7 shape — note there is no connections level anymore):

fos_elastica:
    clients:
        default:
            hosts: ['%env(ELASTICSEARCH_URL)%']
            # ssl / auth via client_config, other transport options via client_options
    indexes:
        app:
            use_alias: true
            types:
                product:
                    mappings:
                        properties:
                            name:   { type: text }
                            price:  { type: float }
                            tags:   { type: keyword }
                    persistence:
                        driver: orm
                        model: App\Entity\Product
                        listener: ~   # Doctrine listener, indexes on persist/update
                        finder: ~     # exposes a TransformedFinder service

Then:

bin/console fos:elastica:create      # create index + mapping (or alias)
bin/console fos:elastica:populate    # backfill existing rows

Your first use case is almost always "put an entity in, get entities out": inject fos_elastica.finder.app.product and call find() with a query, get hydrated Product objects back. Everything else (pagination, aggregations, hybrids) is a variation on that.

Implementation Patterns

Injecting finders. Finders are private services. Either use the explicit service id:

public function __construct(
    #[Autowire(service: 'fos_elastica.finder.app.product')]
    private TransformedFinder $products,
) {}

…or rely on the argument-name aliases registered since 6.4, where the index/type pair becomes a named argument ($appProductFinder maps to that finder). The explicit id is the one that survives refactors, so I use it in application code.

Querying with the Elastica query DSL. The finder takes an Elastica\Query and returns hydrated entities — you write Elasticsearch queries, not SQL:

$bool = new BoolQuery();
$bool->addMust(new MultiMatch($term, ['name', 'description']));
$bool->addFilter(new Term(['tags' => 'sale']));

$query = new Query($bool);
$query->setSize(20);
$query->addAggregation(new Terms('by_brand', 'brand.raw'));

$products = $this->products->find($query);          // Product[]

Because the finder is transformed, find() gives you domain objects; if you need raw hits or aggregations, use findPaginated()/createRawPaginatorAdapter() or drop to $finder->getIndex()->search($query) via the index service.

Pagination. findPaginated($query, $limit) returns a Pagerfanta Paginator, and the bundle ships an adapter that speaks the Elasticsearch count/offset protocol. Use findPaginated, not find with setFrom, whenever the page number comes from a request.

Hybrid search (ES + database). findHybrid() returns HybridResult objects: the ES hit score plus the Doctrine entity, with a documented order. Use it when the searchable fields are a small subset of what you render — it avoids denormalizing your whole table into the index.

Custom repository classes. For anything non-trivial, subclass FOS\ElasticaBundle\Repository and register it against the type (repository: App\Search\ProductRepository). You keep the bundle's finder/pagination plumbing but get typed, testable query methods like searchByBrand() instead of raw Elastica\Query construction sprinkled through controllers.

Manual indexing. For data that isn't Doctrine-driven (API imports, aggregates), take fos_elastica.object_persister.app.product (or the ObjectPersisterInterface) and call insertOne/replaceOne/addDocuments. Batch with an Elastica\Bulk-backed call rather than one-by-one in a loop.

Async population and writes. driver: orm plus a large table means fos:elastica:populate will run for a long time. The bundle has a pager-persister abstraction that can push batches through Symfony Messenger — configure it and run the populate command with the async persister so indexing happens in workers. Same idea for the Doctrine listener: keep it on postPersist for low-volume entities; for bulk imports, turn the listener off and reindex deliberately.

Aliases and zero-downtime reindex. use_alias: true is the pattern that matters in production: the bundle creates app_<timestamp> and points the app alias at it, so fos:elastica:populate builds a fresh index and swaps the alias atomically. Your application code always queries the alias name and never needs to care that the underlying index changed. Pair it with fos:elastica:reset-templates when you version index templates.

Serialization. You choose between the Symfony Serializer and JMS in config. Groups/@Expose are how you keep Doctrine relations out of the document — the auto-transformer walks the object graph, so an unguarded bidirectional relation becomes a circular reference or a document with half your database in it.

Gotchas and Tips

  • v7 config is a hard break, not a rename. host, port, url, proxy, auth_type, aws_*, ssl, curl, compression, connectTimeout, and the whole connections level are gone. Hosts are an array; SSL lives under client_config and the rest under client_options. Copying a v6 snippet from a blog post is the single most common way to lose an afternoon.
  • The Doctrine listener fires during populate. If you reindex 500k rows and the listener is active, you get a second, redundant write per row. Disable it (or defer to kernel.terminate) around bulk operations.
  • Refresh interval causes "it's not there" bugs. ES only makes documents visible after a refresh (~1s by default). Tests and same-request write-then-read flows will intermittently see stale results. Either set a refresh setting on the index for test environments, or refresh: true on the persist call, and accept the throughput cost.
  • fos:elastica:reset deletes data. Use --no-reset when populating in an environment where you aren't rebuilding from scratch; combined with use_alias you almost never want a reset in production.
  • Mapping types are not interchangeable. object flattens arrays into per-field indexes (you cannot filter on individual tags); nested preserves them but requires nested queries to match. Choosing wrong is a mapping rebuild, so think about it during the first create.
  • Aggregations on text fields fail. text is analyzed; terms aggregations need keyword (either a separate field or fields: { raw: { type: keyword } }). This shows up as an empty bucket map rather than an error if you're not checking the response.
  • Populate memory and speed. Tune --batch-size and --sleep together; the default batch is often too large for objects with relations (Doctrine identity map grows), and a small sleep keeps the ES bulk queue from rejecting writes. Watch --offset if you need to resume.
  • Debugging queries. Set debug_logging: true and queries land in the Symfony profiler's Elastica panel and the logger. When a query is wrong, pull the JSON from the profiler and paste it into Kibana's dev console — reproducing the exact DSL beats guessing at the PHP.
  • Exceptions are wrapped. Client and request failures surface through ElasticaRequestExceptionEvent and PostElasticaRequestEvent; if you log ES errors centrally, subscribe to those rather than catching Elastica\Exception\* in every caller.
  • Dates and enums need care. The model transformer normalizes DateTime/DateInterval and supports BackedEnum, but custom value objects need a transformer or a serializer normalizer. Otherwise you get mapping errors on the first populate, not at deploy time.
  • In tests, don't mock the DSL. Mocking TransformedFinder is fine for controller tests; for repository/query tests, run against a real Elasticsearch (Docker image matching your production major) — mocked Elastica\ResultSet objects hide exactly the mapping and analyzer mistakes you're trying to catch.
  • Private services. Nearly everything except the tagged finders was made private across 6.x. If you're used to $container->get('fos_elastica.resetter'), the migration path is constructor injection with the service id, or the alias registered for your argument name.
  • Index name vs alias in commands. The CreateCommand resolves the physical index name for aliased indexes; if you script around index names, read them from the index manager rather than computing app_<timestamp> yourself.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
ably/pubsub-server
mstfkhazaal/defender
mfonte/base62x
karnoweb/metapilot
tobya/bcsapiwrapper
swalker2/cpanel
vish4395/laravel-translation-manager
morningtrain/economic
teksite/systeminfo-laravel
atomcms/voting
run6/weather
siokas/laravelembeddirectives
adamlien/pkuuids
gilsonsouza/lara-doc-generator
manelgavalda/enrollment_mobile
unabomber/timezone
delatbabel/contacts
astritzeqiri/g-recaptcha
kevinongko/laravel-genode
developeruz/laravel-analytics