malhal/laravel-geographical
Laravel package that adds geographic query scopes to Eloquent models. Store latitude and longitude, then calculate Haversine distances, filter records within a radius, and order results by proximity using clean, chainable Eloquent methods.
Architecture fit. malhal/laravel-geographical is a thin, trait-based convenience layer for Eloquent, not a geospatial engine. It contributes two things: a Blueprint macro (auto-discovered service provider) that adds latitude/longitude columns to a migration, and a model trait that injects a distance scope plus a computed distance accessor built from raw SQL trigonometry. That is a good architectural match for a Laravel monolith or modular monolith where location is a secondary attribute on existing records (stores, users, listings, assets) rather than a first-class geospatial domain. It is a poor fit if your roadmap includes polygon containment, route geometry, or radius search over millions of rows — that is PostGIS/geography-type territory and this package deliberately does not go there.
Integration feasibility. Very high. Adoption cost is roughly a migration line, a use statement on the model, and swapping an ad-hoc query for the scope. No external service, no queue, no infrastructure, no spatial extension required, MIT-licensed with no copyleft or commercial entanglement. A competent Laravel developer can produce a working POC in under a day. The realistic effort is not in the package but in the surrounding work: sourcing and backfilling coordinates (geocoding pipeline, data quality, null handling) and deciding what happens when coordinates are absent.
Technical risk. Three clusters. Database portability: the distance math is expressed in raw SQL functions (acos, cos, radians, sin). MySQL and Postgres are fine; SQLite in-memory test suites may not have math functions compiled in, which can break CI in ways that only surface on the test runner. Verify driver coverage explicitly in the POC. Query performance: a computed expression in ORDER BY/WHERE cannot use a B-tree index, so every distance query is a full scan with per-row trig. That is acceptable at thousands of rows, uncomfortable at hundreds of thousands, and untenable at millions. No built-in bounding-box pre-filter is advertised — you will likely need to wrap the scope with a cheap BETWEEN on lat/lng first. Schema and API coupling: the trait assumes conventional latitude/longitude column names and numeric precision; null or zero coordinates, unit mismatches (miles vs. kilometers vs. nautical miles), and unstable tie-breaking in ordering are the likely defect sources.
Key questions.
null, 0, or a SQL error?Verdict. The low opportunity score is the correct read: this is commodity functionality, not a differentiator. Consume it rather than build it, but treat it as an accelerator for the first iteration, and budget for replacing or wrapping the distance scope if volume or spatial requirements grow.
Stack fit. Assumes Laravel with Eloquent and a standard relational driver (MySQL/MariaDB or Postgres). It sits cleanly alongside existing models — no base-class changes, no repository rewrites, no event hooks. If the product already stores addresses or place IDs, this adds a coordinate pair rather than a new system of record. If the product already uses a search service with geo support (Meilisearch, Algolia, Elasticsearch), that service should own radius search and this package should own persistence and single-record distance display.
Migration path. Add nullable latitude/longitude columns in a new migration (never mutate the original create-table migration on a live system), deploy schema before code, backfill coordinates via a queued geocoding job keyed on existing address fields, then introduce the trait and the scope behind a feature flag on read paths. Keep the legacy query path until the new one has produced equivalent results in production for a burn-in window. Because the columns are additive and nullable, rollback is a code revert with no data loss.
Compatibility. Confirm three things before committing: the package's Laravel/PHP constraint against our pinned versions, the DB driver's support for the trigonometric functions used, and the behavior of the migration macro under our existing migration conventions (some teams ban third-party Blueprint macros for auditability). Note that a composite index on (latitude, longitude) will help a bounding-box pre-filter but will not help the distance expression itself — do not let an index create a false sense of scalability.
Sequencing.
Maintenance. Minimal footprint, but concentrated ownership: effectively one maintainer, with the May 2025 release indicating the project is still alive rather than actively developed. Given the tiny code surface and MIT license, the credible mitigation is vendor-and-fork — copy the trait into our codebase, own ~50 lines, and eliminate the dependency question entirely. I would evaluate that fork as the default end state and treat the Composer dependency as a way to get the first version faster.
Support. Community-only: issues and PRs, no SLA, no commercial backing, no security disclosure process we can point at. For a package that touches schema generation and raw SQL, that is tolerable only because the blast radius is small and the code is auditable in an afternoon. Any security review should focus on the fact that the scope injects bound parameters into raw SQL — confirm binding rather than string interpolation before it reaches a query that takes user-supplied coordinates.
Scaling. The dominant operational characteristic: distance queries are O(n) full scans with per-row trigonometric evaluation. Below roughly 10^4–10^5 candidate rows, with a bounding-box pre-filter, this is fine and cacheable. Above that, p95 latency degrades non-linearly and read replicas only buy time. The escape hatches, in increasing order of cost: bounding-box pre-filter plus result caching; precomputed distance tables for fixed origins (e.g., distance to a set of distribution centers); a real spatial index (Postgres + PostGIS, MySQL spatial types); or delegating radius search to a dedicated search engine. Plan the trigger point now so the decision is data-driven rather than reactive.
Failure modes. (1) Missing math functions on SQLite/CI — tests pass locally, fail in pipeline or vice versa. (2) Null or zero coordinates producing null, 0, or a database error depending on driver, silently corrupting "nearest" ordering. (3) Unit confusion between miles and kilometers surfacing as subtly wrong radii rather than obvious bugs — the worst kind. (4) Unstable ordering for equidistant records, causing pagination to duplicate or skip rows. (5) Index blindness: someone adds an index on lat/lng, assumes the problem is solved, and load arrives anyway. (6) Precision loss at column definition time that only manifests as small distance errors in production data. Each of these is cheap to guard against with tests and a documented coordinate policy, and expensive to debug from customer reports.
Ramp-up. Low — hours for a mid-level Laravel developer to be productive, and the conceptual model (haversine in SQL, distance as a scope) is widely understood. The onboarding cost that matters is not the package but the surrounding conventions: where coordinates come from, what precision we store, what the default unit is, and what the product does for records that cannot be geocoded. Those decisions belong to the TPM, not the package, and they should be written down before the first migration ships.
How can I help you explore Laravel packages today?