mitru/elasticsearch-bundle
mitru/elasticsearch-bundle is a PHP package that simplifies integrating Elasticsearch into your application, offering configuration and service wiring for indexing and querying data. A lightweight starting point for adding search with minimal setup.
First, calibrate expectations: mitru/elasticsearch-bundle is a Symfony bundle, not a Laravel package. It will not auto-register in Laravel — there's no bundle kernel, no config/bundles.php, and no DependencyInjection compiler pass to hook into. With 0 stars and 0 dependents, treat it as unvetted third-party code you are adopting, not a library you are consuming.
Minimal path in a Laravel app:
composer require mitru/elasticsearch-bundle — then immediately inspect composer.json for two things: the PHP version constraint and, critically, the elasticsearch/elasticsearch (elasticsearch-php) constraint. That determines which client API you're coding against (7.x $client->index(['index' => ..., 'body' => ...]) vs 8.x named-argument style).src/ in this order: the *Bundle class → DependencyInjection/*Extension.php → the service definition file (services.xml/services.yaml) → any ClientFactory/ClientBuilder wrapper. Those four files tell you 90% of what the bundle actually does. Most bundles of this shape are a thin factory around elasticsearch-php plus a config tree.// config/services.php or a dedicated config/elasticsearch.php
'elasticsearch' => [
'hosts' => [env('ELASTIC_HOST', 'http://localhost:9200')],
],
// App\Providers\ElasticsearchServiceProvider
$this->app->singleton(Client::class, fn () => ClientBuilder::create()
->setHosts(config('elasticsearch.hosts'))
->build());
index() and a match query so you learn the client contract before layering abstractions.If the bundle's value proposition turns out to be document mapping + repositories (the FOSElasticaBundle/ONGR pattern), decide early whether to reimplement that thin layer yourself rather than fight the DI mismatch. It's usually ~100 lines.
The Symfony-native pattern (for reference, or if you actually are on Symfony): autowire the client service, inject it into a repository/service class, and keep queries in dedicated query-builder classes rather than scattered through controllers. Config lives in config/packages/mitru_elasticsearch.yaml. If the bundle exposes a document-manager abstraction, use it for mapping and drop to the raw client for anything the abstraction doesn't cover — do not contort your query into the bundle's DSL.
The Laravel-native pattern (what you'll actually do):
Indexing via model events. Keep it out of the request cycle:
class Product extends Model {
protected static function booted(): void {
static::saved(fn ($p) => IndexProduct::dispatch($p)->onQueue('search'));
static::deleted(fn ($p) => RemoveProduct::dispatch($p->id)->onQueue('search'));
}
}
Make jobs idempotent (index with an explicit id, not a blind create) so retries and out-of-order delivery are harmless.
Bulk for backfills, never per-document in a loop. Use the bulk API with batches of 500–1000 and refresh left default during the fill, then a single refresh at the end. This is the single biggest performance lever.
Alias-based reindexing. Never point code at a concrete index name. Write to products_v2, then atomically swap the products alias. This makes schema changes a deploy step rather than an outage:
$client->indices()->updateAliases(['body' => ['actions' => [
['remove' => ['index' => 'products_v1', 'alias' => 'products']],
['add' => ['index' => 'products_v2', 'alias' => 'products']],
]]]);
Configuration over magic. Keep index names environment-prefixed (staging_products) so a misconfigured .env can't corrupt production. One config file, one place to read index names from — no string literals in services.
Testing. Bind a fake client in tests or, better, run against a real container in CI via Testcontainers/Docker Compose. Assertions on "did we send the right bulk payload" are cheap and catch most mapping bugs; assertions on search relevance are not — don't try.
composer.json, and if the package is load-bearing, vendor the source into your repo or fork it. Assume no upstream security patches and no answers to your questions.body wrapping in places). Whatever constraint the bundle declares, verify it matches the server version you're running — an 8.x client against a 7.x cluster fails in confusing, non-obvious ways.hosts vs connections, index_prefix vs prefix, and whether the config tree is strict (->cannotBeEmpty()) or silently ignores typos. Symfony config components often ignore unknown keys unless the tree is strict — a typo'd key can mean the default silently wins.sku: "0012" into a number-like type or explode on a mixed-type field. Define mappings up front, and remember you cannot change a field's type in place — you need the alias reindex dance.refresh will bite you in tests and in read-after-write UX. Default refresh interval is 1s. Use refresh: 'wait_for' on writes you immediately read back in tests; use refresh: true only in tests, never in production bulk paths.curl against _search and _cat/indices?v. Most "the bundle is broken" issues are a mapping problem or a document that never got indexed. Check document count first, query second.Carbon instances and null fields serialize inconsistently. Normalize to ISO-8601 strings and decide explicitly whether null means "remove from index" or "store as null" before you write your first document — it's much harder to fix retroactively.How can I help you explore Laravel packages today?