toin0u/geocoder-laravel
Geocoder for Laravel integrates the Geocoder PHP library into Laravel apps, enabling address geocoding and reverse geocoding with configurable providers and caching. Requires PHP 8.2+ and Laravel 11+, with service provider auto-discovery for quick setup.
Architecture fit
geocoder-php/Geocoder library. It contributes one container binding (geocoder), one config file, and a fluent query-builder aggregator (ProviderAndDumperAggregator). There is no persistence layer, no schema migration, and no background processing — it is a synchronous, request-scoped outbound HTTP client. Fit is therefore high for any Laravel app that needs address→coordinate, coordinate→address, or IP→locale resolution, and low for apps expecting batch/async geocoding at scale.toin0u/geocoder-laravel → geocoder-php/Geocoder → individual provider packages (google-maps, geoplugin, nominatim, geoip2) → a PSR-18 client. That means the meaningful "integration" work is choosing providers and an adapter, not wiring the package itself.Geocoder\Laravel\Http\LaravelHttpClient) delegates to Laravel's Http facade. This is a genuine architectural win: Http::fake()/Http::assertSent() work in tests with zero setup, existing global HTTP middleware applies, and timeout/retry/connectTimeout are centrally configurable. It also means geocoding inherits whatever middleware you have registered (logging, tracing, header injection) — usually desirable, occasionally surprising.Integration feasibility
composer require, set GOOGLE_MAPS_API_KEY, and app('geocoder')->geocode('...')->get(). Auto-discovery handles provider registration on modern Laravel; manual provider registration is only relevant pre-5.5.5.x).vendor:publish --tag=config) and the defaults are sensible but opinionated: a Chain of GoogleMaps then GeoPlugin. That default implies a paid API key and a third-party free service in the request path — both need explicit sign-off.Technical risk
9999999/999999999 seconds, and the README itself is internally inconsistent about which). Google's Geocoding API terms restrict how long results may be cached. A near-forever cache is a compliance exposure that surfaces only at audit or contract review. This is the single highest-severity item in the package's default posture, and it is a policy decision, not a code bug.cache.serializable_classes. Under Laravel 13, the package scans vendor/geocoder-php/* at boot and merges every model class it finds into the app's cache allow-list, narrowly relaxing a security hardening default. The README is commendably explicit about the blast radius and offers an opt-out (auto_register_serializable_classes => false). A TPM should treat this as a security-review item, not an install detail — the correct answer for a regulated environment is likely opt-out plus an explicit allow-list, or doNotCache().5.0.0 → 13.0.0 with no intermediate releases, where the major now tracks the Laravel major rather than the Geocoder major. Composer constraints and dependency-upgrade tooling will look alarming on the diff. It is safe, but it costs reviewer time every upgrade.[Class => [args]] forwards arguments straight to the constructor with no signature check ("it's on you to match its signature"). A typo produces a runtime failure at first geocode call, not at boot.geocoder-php org — it is that you are one of few consumers exercising the Laravel-specific surface, so obscure bugs may be yours to find.Key questions
cache.serializable_classes acceptable, or do we opt out and maintain an explicit allow-list?Stack fit
Http facade and a Redis cache, with modest geocoding volume (form autocomplete, address normalization, IP-based locale/currency defaults).GeoIP/GeoPlugin half of the default chain is often mistaken for a substitute for proper IP intelligence. For anything beyond coarse country-level inference, evaluate the local GeoIp2 reader (the reader config key exists for exactly this) rather than an external HTTP hop.Migration path
cache.store at a dedicated store, ship.5.x: the jump to 13.x is a major with behavioral changes — the new default HTTP adapter, the versioning scheme change, and the Laravel 13 cache allow-list behavior. Read the Upgrading section, do not assume semver does the thinking for you. Pinned adapter behavior (Curl\Client, custom CA bundles, proxies) is the most likely regression point.GeocoderService interface so the package's fluent API does not leak into domain code. This also preserves a swap path to a self-hosted provider later.Compatibility
composer.lock before merging.cache.serializable_classes decision described above. On Laravel 11/12 this is a non-issue, so the decision can be deferred — but it should be made once, not rediscovered during the framework upgrade.Http facade; apps that have replaced or wrapped the HTTP layer should confirm the PSR-18 delegation still resolves.Sequencing
geocode + reverse + locale against a handful of representative inputs, including garbage inputs.cache-duration accordingly.database index) so geocode cache churn cannot evict application cache; verify eviction policy.adapter explicitly with timeout/connectTimeout/retry; decide auto_register_serializable_classes.Collection<Address> return into a domain DTO; decide the empty-result contract.Http::fake() fixtures for each provider, asserted in CI so upstream response shape changes fail loudly.Maintenance
Support
Scaling
doNotCache() should be reserved for cases where staleness is genuinely unacceptable, since each call is real money and real latency.Failure modes
->get() returns an empty Collection for unresolvable input. Code that assumes [0] exists will throw; code that treats empty as an error will surface bad UX for legitimately vague addresses. Decide explicitly.serializable_classes is neither auto-registered nor explicitly allow-listed → __PHP_Incomplete_Class errors that look like cache-layer bugs.Ramp-up
geocode, reverse, geocodeQuery) plus locale(), using(), doNotCache(), dump(). A competent engineer is productive in under an hour using the README alone.app('geocoder') calls, so the empty-result contract, locale defaults, and caching policy are enforced in one place instead of being re-derived at every call site.How can I help you explore Laravel packages today?