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

Location Laravel Package

mattrothenberg/location

A Laravel package for working with geographic location data. It provides a simple API to resolve and use details like city, region, country, and coordinates, making it easy to add location-aware features such as localized content, analytics, and regional defaults.

View on GitHub
Deep Wiki
Context7

Getting Started

This is the initial 1.0 release, so there is no migration path to worry about — but also no battle-tested surface area. Treat the package README as the source of truth and read it end-to-end before wiring anything into a model, because at 1.0 the README is the API contract.

  1. Install and pin. composer require mattrothenberg/location:^1.0. Pin the major explicitly even though it's the first release; you want 1.x minors to arrive deliberately, not silently.
  2. Look for publishables first. Run php artisan vendor:publish --provider="Mattrothenberg\Location\LocationServiceProvider" and inspect what lands. The two things that determine your whole integration are (a) whether a config file ships (API keys, geocoder driver, precision) and (b) whether a migration ships (a locations-style table or added columns). Do this before writing any code.
  3. Check the service provider. Read the provider's register()/boot() to see what's bound into the container — particularly whether a geocoder is resolved through a contract you can swap. That binding is your main extension point later.
  4. First use case: location on an existing model. Take a model you already have (Venue, Store, Listing), add the package's trait (or casts, depending on the API), add latitude/longitude columns if the package doesn't ship them, and get one record saving real coordinates in tinker. Confirm the round trip — write, reload, read back — before touching forms.
  5. Only then build the map. Rendering a marker on a Leaflet/Mapbox map is the easy part and the easiest place to hide a data bug. Prove the coordinates are correct in the database first.

If the README's quick-start differs from what the provider actually publishes, trust the provider — at 1.0 docs drift is normal.

Implementation Patterns

Attach location to models, not the other way around. Whether the package gives you a trait, a cast, or a relation, keep the geo data on the model that already owns the domain concept. A polymorphic locations table is tempting, but it makes "venues near me" queries awkward and pushes joins into every listing page.

Geocode on write, but not inside the request. The single most common integration is "user types an address, we store lat/lng." Do the geocode in a queued job dispatched after save, and store a geocoded_at / geocode_status column so you can retry failures. Synchronous geocoding inside a saving observer will eventually hit a slow provider and time out a form submission.

Validate coordinates at the edge, not in the package. Assume nothing about input:

$request->validate([
    'latitude'  => ['required', 'numeric', 'between:-90,90'],
    'longitude' => ['required', 'numeric', 'between:-180,180'],
]);

Then cast to float on the model so comparisons and distance math don't silently operate on strings.

Cache geocoding results by normalized address. Hash the trimmed, lowercased, whitespace-collapsed address and cache the coordinate pair for a long TTL. This is the cheapest way to keep provider bills flat and makes re-seeding dev databases instant.

Distance queries belong in SQL, not PHP. For MySQL/Postgres, use ST_Distance_Sphere(POINT(lng, lat), POINT(?, ?)) (or earth_distance / PostGIS ST_DWithin). Pulling 5,000 rows into a collection and sorting by Haversine in PHP is the pattern that works in the demo and falls over in production. Add a bounding-box WHERE lat BETWEEN ... AND lng BETWEEN ... first so the spatial function only runs on a small candidate set — that's what actually uses your index.

Ship coordinates to the frontend as a plain array. Whatever your view layer (Blade + Alpine, Inertia, Livewire), pass ['lat' => ..., 'lng' => ...] explicitly rather than serializing the model and letting the JS guess. It also gives you one place to convert to [lng, lat] for GeoJSON if you need it.

Testing pattern. Never let the test suite reach a real geocoder. If the package uses Laravel's HTTP client underneath, Http::fake() with a recorded response is enough. If it resolves a geocoder contract from the container, bind a fake implementation in TestCase::setUp() — cleaner and faster. Write one test asserting the fake is actually used, otherwise a future refactor can quietly reintroduce live calls.

Gotchas and Tips

1.0 means the minor versions may still bite. Semantic versioning says 1.x is non-breaking, but first-major packages routinely ship behaviour changes in minors while the API settles. Pin ^1.0 in composer.json, and read each changelog entry for anything touching coordinate storage or precision before bumping.

Latitude/longitude ordering is the bug you will actually hit. Eloquent columns, MySQL POINT(), GeoJSON, and most mapping JS libraries disagree. POINT(lng lat) in MySQL, [lng, lat] in GeoJSON and Mapbox, lat, lng in most human-facing APIs. Pick one convention for your column names, comment it at the top of the migration, and convert only at the boundary. Symptoms are subtle: markers land in the Indian Ocean off Africa when you swap them.

Use DECIMAL(10,7) / DECIMAL(11,8), not FLOAT or DOUBLE. Decimal degrees need ~7 digits of precision for centimetre accuracy. FLOAT gives you rounding drift, breaks exact-match where('latitude', ...) lookups, and makes test assertions flaky. If you need fast radius queries, a separate indexed POINT column alongside the decimals is the pragmatic compromise.

0,0 is a valid coordinate and a terrible sentinel. Null island (Gulf of Guinea) is where bad data goes to hide. Check is_null() explicitly rather than if ($lat && $lng) — otherwise legitimate equator/prime-meridian records silently disappear, and failed geocodes render a marker in the ocean instead of an error state.

Partial coordinates are worse than none. Enforce that latitude and longitude are set or unset together. A record with a latitude and a null longitude will break ST_Distance_Sphere in confusing ways. A saving guard or a CHECK constraint saves hours.

Geocoding failures are silent by default. Most providers return 200 OK with an empty results array for an unmatchable address. Explicitly treat "zero results" as a distinct outcome from "HTTP error," store it on the model, and surface it to the user. Otherwise support tickets read "my store doesn't show on the map" with no log line anywhere.

Watch the accessor/cast interaction. If the package casts coordinates to a value object, $model->latitude = 0.0 followed by save() may not be marked dirty, or toArray() may emit an unexpected shape that breaks an Inertia prop or an API resource. Dump $model->getChanges() and $model->toArray() once during integration and eyeball them.

Extension points to know. The service provider's container bindings are the supported seam: rebind the geocoder contract to your own implementation (a different provider, a caching decorator, a fake in tests) rather than editing published config in ways that collide on the next vendor:publish. If a config key exists for the geocoder driver, prefer it — config survives package upgrades better than a rebound singleton.

Publishing twice clobbers config. vendor:publish overwrites without asking in non-interactive contexts. Commit the published config, and use --force consciously.

Finally: this release has zero production history. Wrap the integration behind a thin application-level service (one class that owns "give me coordinates for this address" and "give me records within X km") so that if the package's API shifts or you outgrow it, you're swapping one class rather than grepping the codebase. Given the size of the problem it solves, that wrapper will cost you an hour and buy back a day.

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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package