pdphilip/elasticsearch
An Elasticsearch implementation of Laravel's Eloquent ORM. Use familiar Eloquent methods and models while leveraging full-text search, geo queries, fuzzy matching, highlights, and aggregations through a seamless Laravel-native integration.
Architecture fit
This is not a search sidecar — it is an alternative persistence driver: Elasticsearch becomes the store behind Eloquent models, with schema/index blueprints replacing migrations, and PDPhilip\Elasticsearch\Eloquent\Model replacing Illuminate\Database\Eloquent\Model. That makes architectural fit a domain decision, not a library decision.
whereGeoDistance), fuzzy/phrase-prefix search with highlighting, aggregation-heavy reporting, and entities whose access pattern is "search first, join later."Integration feasibility
Mechanically low-friction and idiomatic to Laravel: a Composer dependency, a config/database.php connection block of type elasticsearch (env-driven, http/cloud/API-key/SSL, comma-separated multi-node hosts, index prefix, retries, logging, default_limit), an optional manual service-provider registration, and models that extend the package base class with protected $connection = 'elasticsearch'. Index creation runs through a Blueprint schema builder, so it slots into the migration habit even though it is not php artisan migrate in the SQL sense.
Constraint to verify early: ES 8.x only on the maintained lines, and Laravel/PHP pairings are strict — ~5.6 for Laravel 11/12/13 on PHP 8.3/8.4; LTS lines back to Laravel 8 on older majors. Laravel 10 was dropped in 5.6.0, so this package tracks Laravel's release train rather than floating independently.
Technical risk
where() changed from a match to a term query; _id deprecated for id; MAX_SIZE → $defaultLimit; orderByRandom removed; fuzzy/boost/min-should-match collapsed into callback SearchOptions; IndexBlueprint/AnalyzerBlueprint merged into Blueprint; geo() → geoPoint(); index() → indexField(); alias() → aliasField(). The where() shift is the dangerous one: it fails silently and correctly-looking, returning empty or partial result sets rather than erroring.ES_OPT_BYPASS_MAP_VALIDATION disables a safety net, and ES_OPT_DEFAULT_LIMIT=1000 means unbounded-looking queries truncate silently.Key questions
elasticsearch-php client version is pinned, and what is the ES 8.x→9.x plan?refresh=true/wait_for on writes, or is read-after-write left to the application?index.max_result_window (10k) handled — search_after support, or hard stop?retries and ES_ERROR_INDEX constitute a circuit breaker, or just logging?Stack fit
Greenfield Laravel 11–13 on PHP 8.3/8.4 against an ES 8.x cluster managed by the team: clean fit, low ceremony. Brownfield apps on Laravel 9/10 must go to LTS lines (~5.5, ~4, ~3.9) — acceptable but means you inherit the older API surface and the older where() semantics, which complicates any later hop to 5.6.
Migration path
UserLog, UserProfile-shaped data. Keep transactional entities (orders, payments, anything needing rollback) in SQL.where() = term trap.Compatibility
Pin to an exact minor (~5.6.x), not ^5, and treat Laravel major upgrades as paired work: this package needs a companion release each time. Run composer test:l11/test:l12/test:l13 in CI, and add an ES service container so your own models are exercised against a real cluster — not just the package's tests. Laravel 13 / Symfony 8 support landed in 5.6.0 and has minimal field time; if you are on 12, stay there until the line matures.
Sequencing
Blueprint index creation, CRUD, paginate, one geo query, one fuzzy query, one ES→SQL relationship. Kill criteria: mapping lifecycle or pagination semantics don't fit our data shape.ES_INDEX_PREFIX no longer auto-appends _ — set it explicitly), alias strategy, reindex runbook, mapping review checklist, default_limit policy.Maintenance
Ongoing effort is dominated by two cadences: Laravel majors (the package must ship a new line; you must re-pin, re-test) and Elasticsearch majors. Internally you need an owner who is genuinely dual-fluent — Eloquent internals (custom connection, grammar, builder) and ES DSL/mapping/analyzers. That is a rare combination and the single biggest staffing risk. Given maintainer concentration, retain the practical option to vendor/fork and patch; MIT licensing makes that viable. Note the v5 history — a near-total rewrite mid-life — as evidence that upgrade guides, not deprecation shims, are the norm here.
Support
Community-only: GitHub issues, docs site (~15 pages across Eloquent, relationships, schema, and a mapping reference), no commercial SLA. Escalation path in a production incident is essentially the maintainer's goodwill plus your own debugging. Budget for that by keeping raw elasticsearch-php client access available for emergency queries that bypass the Eloquent layer.
Scaling
ES clusters scale horizontally and the driver supports multi-node ES_HOSTS, retries, and connection logging. The risk is abstraction-induced: Eloquent-shaped queries can generate expensive aggregations or scripts that look innocent in PHP and are costly on the cluster. Guardrails worth installing:
ES_OPT_DEFAULT_LIMIT — raise deliberately, never leave at 1000 with "returns everything" assumptions.search_after support before any UI paginates beyond 10k.get().allow_id_sort / ES_OPT_ID_SORTABLE — enabling ID sorting has cluster-side cost; treat as opt-in with justification.Failure modes
| Mode | Symptom | Mitigation |
|---|---|---|
| Cluster unreachable | Read/write exceptions on ES models only | Retries + circuit breaker at the app layer; decide ES_ERROR_INDEX policy up front |
| Refresh lag (~1s) | Write then immediate read misses | refresh=wait_for on critical writes, or UX tolerance |
where() = term |
Zero/partial results, no error | Code review + integration tests asserting result counts, not just HTTP 200 |
| Mapping drift | Indexing rejects documents | Keep bypass_map_validation off; schema migration + reindex drill |
| Silent truncation | 1000 rows returned where 5,000 expected | Explicit limits everywhere; monitoring on result counts |
| Index name collision | Dev writes into prod indices | Mandatory ES_INDEX_PREFIX per environment |
| Minor upgrade surprise | Behavior change on composer update |
Exact pin, changelog review gate, staged rollout |
Ramp-up
The 80% is cheap: any Laravel developer writes where/create/paginate/with on day one, and the docs examples are immediately productive. The remaining 20% is where projects stall — mapping design, analyzer choice, aggregation and nested-query semantics, scoring/SearchOptions, and reindex operations. Realistic estimate: one to two sprints for a mid-level team to be productive, plus dedicated ES fundamentals training for the designated owner. Given the maturity score (10.5) and moderate opportunity score (55.25), the honest read is: high intrinsic leverage, low external validation. Adopt on a bounded, log-or-search-shaped domain with a spike gate and a rollback plan — not as a wholesale replacement for your relational core.
How can I help you explore Laravel packages today?