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

Elinoi Search Bundle Laravel Package

elinoi-dev/elinoi-search-bundle

Symfony bundle for integrating Algolia search into your project. Configure in just a few lines of YAML, with full support for PHP Client v2 features like wait(), optional request options on every method, and type-hinted, dev-friendly APIs. Requires PHP 7.1+.

View on GitHub
Deep Wiki
Context7

Getting Started

First, a correction that shapes everything below: despite the brief, this is a Symfony bundle, not a Laravel package. It has no ServiceProvider, no facade, no php artisan vendor:publish, and Laravel's container will not auto-discover or boot a Symfony Bundle class. Practically, you have two choices, and the first is the only one I'd put in a real Laravel codebase.

If you are actually on Laravel (the framing of this assessment): do not install this package. Use algolia/scout-extended, the maintained first-party path, or call algolia/algoliasearch-client-php directly behind a small service class you own. Everything in this bundle — YAML index definitions, an import command, entity subscribers — is glue you'd rewrite in ~150 lines of Laravel-native code (a config file, an artisan command, and model observers). Pulling in a Symfony bundle to get that glue means bridging two containers and inheriting a dependency with 0 dependents and a 2023 release.

If you are on Symfony and treating this as a reference implementation: the package is a rename/fork of the upstream algolia/search-bundle, and the README still points at Algolia's badges, CI, and Packagist URLs. Install the upstream bundle instead unless you have a specific reason not to. The onboarding is genuinely ~5 lines:

composer require algolia/search-bundle

Set credentials via env (defaults are ALGOLIA_APP_ID / ALGOLIA_API_KEY), then declare indices:

# config/packages/algolia_search.yaml
algolia_search:
  indices:
    - name: posts
      class: App\Entity\Post

Where to look first: the indices config block is the contract. Each entry needs a name (the Algolia index) and a class (the Doctrine entity). If indexing misbehaves, that config is the first thing to interrogate — wrong FQCN, a proxy class, or a typo'd index name accounts for most first-run failures.

First use case: batch-import an existing table, then read from Algolia in a controller.

php bin/console search:import --indices=posts
$em = $this->getDoctrine()->getManagerForClass(Post::class);
$posts = $this->searchService->search($em, Post::class, 'query');

Note what that search() call does: it queries Algolia, gets back IDs, then hydrates a Doctrine collection from your database. That round-trip is the core mental model and the source of most surprises — see Gotchas.

Implementation Patterns

Config-driven index definitions, not code-driven. The bundle's value is that "what is searchable" lives in YAML. Keep it that way. One indices entry per entity, canonical lowercase index names, and index-name constants in a single PHP class that both the YAML and application code reference — you do not want string literals for index names scattered across controllers, commands, and tests.

The search-then-hydrate pattern is the primary workflow. Algolia returns ranked IDs; Doctrine fetches rows; the bundle rebuilds a collection in Algolia's order. This means:

  • Ranking is Algolia's; freshness is your database's. A row edited or deleted after the last index sync will either be missing from results or returned stale. This is the single most important operational fact about the integration, and it dictates your sync strategy.
  • Keep the Algolia-side record lean. If you index full entities, you're duplicating data and paying per-record storage for fields you never display in results. Index the fields you filter, facet, and rank on; hydrate the rest from Doctrine.
  • search() needs the right EntityManager. For multi-entity or aggregated models, resolve the manager per class (getManagerForClass), not the default manager.

Batch import is the backfill tool; it is not your sync mechanism. search:import clears and repopulates. Day-to-day correctness needs incremental sync: a Doctrine lifecycle listener (postPersist / postUpdate / postRemove) that pushes a single record to Algolia, ideally through a queue. The bundle exposes the underlying client and settings manager for this — wire your listener to call saveObjects / deleteObject on the index rather than re-running a full import on every edit.

Use atomic reindex on any live, customer-facing index. The --atomic flag builds a temporary index and swaps it in with moveIndex, and (per the changelog) waits for tasks to settle before moving. This is the difference between "search briefly returns half the catalog" and "search never notices the reindex." If you reindex on a schedule, schedule the atomic variant, off-peak, and alert on the command's exit code.

Treat $requestOptions as the extension seam. The README advertises that every method accepts optional request options. That's where you set filters, facets, hitsPerPage, numericFilters, restrictSearchableAttributes, and typo settings per call without forking the bundle. When product asks for a facet or a ranking tweak, look here before assuming you need to patch the package.

Separate "index schema" from "record shape." Two distinct concerns that get conflated: the index settings (searchable attributes, attributesForFaceting, custom ranking, replicas) and the record payload you push. Keep settings in version-controlled config or a deploy step so they're reproducible across environments; keep record shaping in an explicit serializer/mapper class so it's testable without hitting the API.

Environment isolation. Index names are global to the Algolia app, not to your deployment. Prefix index names by environment (prod_posts, staging_posts) or use separate Algolia apps. Two environments sharing posts is a guaranteed incident.

Integration with Doctrine proxies: older releases had bugs pulling Doctrine proxy class names into Algolia; the fix used ClassUtils to resolve the real entity. If you're on a fork pinned before that fix, assume proxy class names will leak into your records.

Gotchas and Tips

This is the biggest gotcha and it's structural: it's not a Laravel package. If the brief you were handed says "Laravel," the brief is wrong. Plan for algolia/scout-extended (Laravel) or upstream algolia/search-bundle (Symfony). Writing bridge code to make a Symfony bundle boot inside Laravel is a multi-day task that buys you a small amount of YAML and a console command.

This fork is not the maintained one. Zero stars, zero dependents, one release (July 2023), and a README that still points at Algolia's own badges and Packagist. There is no signal on PHP 8.3+/Symfony 7 support or on tracking upstream Algolia API changes. Adopting it directly is taking on maintenance debt you could avoid by pulling the upstream package. Verify the upstream support matrix before writing code against either.

Search results are only as fresh as your last sync. Because search() hydrates from the database, a stale index gives you stale ordering and membership, not stale content. This produces confusing bugs: a post edited five minutes ago shows its new title (fresh DB row) but at its old rank (stale index). Debugging tip: when search results look "wrong," first ask whether the ID set is correct; only then look at the row contents.

Deletes are the classic failure. postRemove listeners frequently fire after the entity is detached, or the ID is no longer readable, so the Algolia record is orphaned. search() then returns an ID that Doctrine can't hydrate — a null in your collection, or an exception depending on how you iterate. Filter nulls defensively and audit for orphaned records on a schedule.

The wait() behavior is a feature, not an implementation detail. Every write in the Algolia client is async by default; the bundle's robustness claim rests on waiting for task completion. In your own incremental-sync code, if you don't call wait() (or the equivalent), subsequent reads and moveIndex operations can observe an inconsistent index. The changelog explicitly fixed a wait-before-moveIndex race in v5.1.1 — a good reminder that this class of bug is real.

Config quirks to check when "nothing is indexed": (1) bundle registered in config/bundles.php; (2) YAML rooted at algolia_search with correct indentation under indices; (3) FQCN exactly matching the entity's namespace, not the proxy; (4) env vars actually present in the runtime environment — a .env file read locally but absent from a container or CI job is extremely common. php bin/console debug:config algolia_search is your friend.

parameters.yml vs .env: the README shows both ALGOLIA_APP_ID= style and a parameters.yml with env(...) indirection. If credentials are missing, the bundle doesn't always fail loudly at boot — it may fail at first API call. Add a startup assertion that both values are non-empty, especially in staging.

Entity listeners must be registered, not just written. A postPersist listener that isn't wired into services.yaml (or autoconfigured) silently does nothing — and you won't notice until a user complains about missing results. Write an integration test that persists an entity and asserts the index received a record.

Cost is a technical concern here, not just a finance one. Algolia bills on records and operations. A naive "reindex everything on every save" listener will both break freshness semantics and generate a surprising bill. Batch writes (saveObjects), debounce on hot records, and keep record payloads small.

Extension points, ranked by how much pain they save: (1) your own mapper class for record shaping; (2) a queue-backed sync listener for incremental updates; (3) per-call $requestOptions for faceting/filtering rather than forking; (4) a thin SearchService wrapper of your own that the rest of the app depends on — this is your vendor-exit seam. If Algolia pricing moves against you, that wrapper is the difference between swapping to Meilisearch in a sprint and rewriting every controller.

Debugging sequence for "search returns nothing": confirm the index exists and has records via the Algolia dashboard (bypasses all app code) → confirm the query string and filters via $requestOptions → confirm the hydrated IDs resolve in Doctrine. Each step isolates a different layer, and starting at the dashboard prevents hours lost in application code when the answer is "the import never ran in this environment."

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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager