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

Elasticsearch Bundle Laravel Package

markup/elasticsearch-bundle

A Symfony bundle that integrates Elasticsearch into PHP applications, offering configuration, service wiring, and tools for indexing and searching documents. Simplifies client setup and custom index management for Symfony projects.

View on GitHub
Deep Wiki
Context7

Getting Started

First, decide which job you're doing. This is a Symfony bundle, not a Laravel package. There are only two legitimate day-to-day uses for a Laravel developer:

  1. You inherited or are contracting on a Symfony service and need to work inside it. Then you use the bundle as-is (below).
  2. You're on Laravel and want the pattern — a configured Elasticsearch client service plus dev-time query visibility. Then you read the bundle's DependencyInjection/Configuration.php and Resources/config/services.* and reimplement ~150 lines. Do not composer require it into a Laravel app; nothing in it registers with Illuminate\Support\ServiceProvider.

Check composer constraints before writing any code. The bundle targets the Symfony 2.x/3.x era against elasticsearch/elasticsearch 2.x/5.x. On Symfony 5/6/7 or elasticsearch-php 7/8 the graph usually refuses to resolve, and the two client generations are not API-compatible (ClientBuilder, connection pool API, response objects all changed).

composer require markup/elasticsearch-bundle
composer why-not markup/elasticsearch-bundle symfony/framework-bundle

Register it — app/AppKernel.php on legacy Symfony, config/bundles.php on 4+:

$bundles[] = new Markup\ElasticsearchBundle\MarkupElasticsearchBundle();

Minimal config, one connection and one client:

# config/packages/markup_elasticsearch.yaml
markup_elasticsearch:
    connections:
        default:
            hosts: ['%env(ELASTICSEARCH_HOST)%']
    clients:
        default:
            connection: default

Then verify what you actually got instead of trusting the README:

bin/console config:dump-reference markup_elasticsearch   # the config tree IS the API
bin/console debug:container markup_elastic                # real service IDs, don't guess
bin/console debug:container --tag=data_collector

Read the source in this order: DependencyInjection/Configuration.php → Resources/config/services.xml (or .yml) → the DataCollector class → the Extension. The config tree and service definitions are the entire package; there is no meaningful runtime code beyond client construction and profiler hooks.

First real use case: inject the client, index one document, read it back.

use Elasticsearch\Client;

final class ProductIndexer
{
    public function __construct(private Client $client) {}

    public function index(array $product): void
    {
        $this->client->index([
            'index' => 'products',
            'id'    => $product['id'],
            'body'  => $product,
        ]);
    }
}

If autowiring doesn't pick up the client (likely — the bundle predates ClientInterface aliasing), add an explicit alias in services.yaml:

services:
    Elasticsearch\Client:
        alias: markup_elasticsearch.client.default
        public: true

Implementation Patterns

Named clients per use case. Always declare clients explicitly rather than relying on a default. Even if the bundle only supports a single client, write your calling code against a named service alias so splitting read/write or per-tenant clusters later is a container change, not a refactor.

markup_elasticsearch:
    connections:
        default: { hosts: ['%env(ELASTICSEARCH_HOST)%'] }
    clients:
        default:   { connection: default }
        # add `read`/`write` here if multi-client is supported

Wrap the bundle's client, don't scatter it. The bundle gives you a raw client; every caller then reinvents bulk chunking, retry, and partial-failure handling. Put that in one application-level service and inject that:

final class SearchGateway
{
    public function __construct(private Client $client) {}

    public function bulkIndex(string $index, array $docs, int $chunk = 500): array
    {
        $errors = [];
        foreach (array_chunk($docs, $chunk) as $batch) {
            $body = [];
            foreach ($batch as $doc) {
                $body[] = ['index' => ['_index' => $index, '_id' => $doc['id']]];
                $body[] = $doc;
            }
            $res = $this->client->bulk(['body' => $body]);
            if (!empty($res['errors'])) {
                foreach ($res['items'] as $item) {
                    if (isset($item['index']['error'])) {
                        $errors[] = $item['index']['error'];
                    }
                }
            }
        }
        return $errors;
    }
}

Index and mapping management as console commands, not request-time calls. The description advertises "helpers for index and mapping management" — treat those as deploy/migration steps. One command that creates the index if missing and applies mappings, idempotent, run in CI:

// src/Command/SearchEnsureIndexCommand.php
protected function execute(InputInterface $in, OutputInterface $out): int
{
    if (!$this->client->indices()->exists(['index' => 'products'])) {
        $this->client->indices()->create([
            'index' => 'products',
            'body'  => ['mappings' => require __DIR__.'/../../config/search/products_mapping.php'],
        ]);
    }
    return self::SUCCESS;
}

Version your mappings as files and use aliases (products_v1 behind alias products) so a reindex is a swap, not a downtime.

Use the profiler as a development workflow, not a feature. In a Symfony app the DataCollector shows query, duration, and response per request. The day-to-day value is: open a page, see exactly which ES query ran and how long it took. That is what you replicate in Laravel — a Telescope watcher or a Debugbar collector recording index name, query shape, duration only:

// Laravel port, app/Providers/ElasticsearchServiceProvider.php
$this->app->singleton(Client::class, fn ($app) => ClientBuilder::create()
    ->setHosts(config('elasticsearch.hosts'))
    ->setRetries(config('elasticsearch.retries', 2))
    ->build());

// and in a Telescope watcher or middleware, record duration only — never bodies

Integration tip: gate the collector hard. Whatever the bundle's collector does, confirm it is dev-only before you trust an environment. In your own implementation make it config('elasticsearch.profiler.enabled') && app()->environment('local'), never inferred.

Testing. Because the client is PSR-friendly, mock the Elasticsearch\Client at your SearchGateway boundary, not inside every test. For integration tests spin up a single-node container and exercise the real client — the SDK's response shapes are what break on upgrade, and a mock won't catch it.

Migration path off the bundle. Mirror the YAML into a plain service definition tree, replace constructor injection of markup_elasticsearch.client.default with an alias to your own binding, and re-point any bundle-specific exception handling. The bundle doesn't appear to own index state, so there should be no data migration.

Gotchas and Tips

A typo silently points you at localhost:9200. Every ES client builder in this generation falls back to a default host when a config key is missing or malformed. A mistyped key under connections.default means the app boots happily and queries the wrong cluster. Assert config presence at container compile or on boot — not at first query, where it looks like a search bug.

debug:container and config:dump-reference are your only real docs. Zero stars and zero dependents means searching an error message returns nothing. On-call reads vendor/markup/elasticsearch-bundle/ source, not a wiki. Budget for that in incident response.

Symfony service IDs are convention, not contract. The docs suggest markup_elasticsearch.client.default; confirm it. Guessing wrong produces a "cannot autowire" error at container compile — which is good, it fails loudly at deploy — but only at deploy. Run bin/console cache:clear && bin/console debug:container locally after every config change.

Profiler PII leakage. A collector that captures ES request/response bodies will serialize document contents into the profiler store, which on most setups lives on disk. Hard-gate it to local/dev behind an explicit flag, and audit what it persists. In long-running workers or an accidentally prod-enabled environment it also balloons memory.

Wrapper exception masking. If any glue catches and rethrows with a generic message, you lose the ES type/reason fields that on-call actually needs. Read the exception path before you rely on the log output; if it masks, rethrow with the original getPrevious().

Version coupling is the real maintenance cost. Symfony bundles are pinned to Symfony majors via composer constraints; elasticsearch-php 7 → 8 is a breaking client rewrite. You own every bump: Symfony major, PHP minor, ES client major. If you fork, that's a permanently maintained artifact.

No bulk/retry ergonomics. It's a raw client. Without a wrapper, each feature team writes its own chunking and partial-failure handling, and the failure mode is silent data inconsistency (documents missing from the index), not an exception. Centralize it.

indices()->create in a request is a latency and race-condition trap. Two concurrent requests both see "not exists" and both create. Only ever create/apply mappings from a command or a deploy step.

Extension points are Symfony-shaped. The Extension, compiler passes, and DataCollector are the places to hook customization — but none of those concepts exist in Laravel. If you're porting, port the configuration schema (hosts, auth, retries, serializer choice) and the observability idea, then build with first-party primitives: elasticsearch/elasticsearch behind a singleton binding, with Telescope or an APM for query visibility.

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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib