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

Geolocation Bundle Laravel Package

yipikai/geolocation-bundle

A PHP geolocation bundle that adds location-aware services and configuration to your application, providing a reusable way to integrate geographic data handling into Symfony-style projects.

View on GitHub
Deep Wiki
Context7

Getting Started

First, verify the framework. The package name ends in -bundle and the description says "This bundle" — that's Symfony vocabulary. Check composer.json for symfony/* requirements and look for a DependencyInjection/, Resources/config/, and a *Bundle.php class. If that's what you find, there is no Laravel service provider, no config/geolocation.php to publish, and composer require will not make anything resolvable from the container. Budget an hour for wiring before you write a line of feature code.

Where to look first, in order:

  1. composer.json — PHP constraint and, critically, which HTTP transport it pulls in (Guzzle vs. symfony/http-client). This determines whether you can reuse Laravel's Http facade or are stuck with a second HTTP stack.
  2. The service class — usually the only class with real logic, typically something like GeolocationService, AddressGeocoder, or ApiClient. Read it top to bottom; it's likely under 200 lines.
  3. The constructor signature — that tells you exactly what you must new up manually.

Minimal usage once you've found the service class: instantiate it directly, bypassing Symfony's container entirely. In a throwaway tinker session:

$geo = new \Yipikai\GeolocationBundle\Service\GeolocationService(/* deps from ctor */);
$result = $geo->geolocate('10 Rue de la Paix, 75002 Paris');

First real use case: geocode one address at write time and persist latitude/longitude as decimals on an Eloquent model. Do that for a single record by hand before building anything reusable — it confirms the API contract and the shape of the response object.

Worth knowing before you invest: the underlying service is api-adresse.data.gouv.fr (Base Adresse Nationale), which is free, keyless, and returns plain JSON. A working geocoder is genuinely about ten lines:

$res = Http::get('https://api-adresse.data.gouv.fr/search/', ['q' => $address, 'limit' => 1]);
$feature = $res->json('features.0');

Evaluate honestly whether the bundle is buying you anything over that. With zero stars and zero dependents, "a thin wrapper around one HTTP call" is the most likely reality.

Implementation Patterns

Wrap it, don't leak it. Bind your own interface in a service provider and adapt the bundle behind it, so the vendor namespace appears in exactly one file:

// App\Providers\GeocodingServiceProvider
$this->app->bind(Geocoder::class, function () {
    return new BundleGeocoderAdapter(/* manually constructed deps */);
});

This is non-negotiable given the package's maturity — you want the option to swap to raw Http::get() later without touching call sites.

Return a value object, not the vendor DTO. Map to your own readonly class with the fields you actually store: latitude, longitude, label (the normalized address), score, citycode, postcode. The BAN response uses GeoJSON (features[].geometry.coordinates is [lng, lat] — longitude first, which is the single most common bug in this integration).

Geocode on save via an observer, guarded so you don't re-hit the API when nothing address-related changed:

static::saving(function ($model) {
    if ($model->isDirty(['street', 'postcode', 'city'])) {
        $model->geocode(); // sets lat/lng, handles null result
    }
});

Cache aggressively. Addresses are effectively immutable; the only thing that changes is the API's own data. Key on a normalized hash of the address and cache for weeks:

Cache::remember('geo:'.md5(Str::lower(trim($address))), now()->addDays(30), fn () => ...);

This turns a bulk import from thousands of network calls into a handful.

Bulk work belongs in a job, and probably belongs in the CSV endpoint. The BAN API has a dedicated CSV upload endpoint for batch geocoding. If you're backfilling 10k rows, do not loop the single-search endpoint from a job — upload a CSV, get results back, or at minimum dispatch a rate-limited job and accept that it's slow. A backfill artisan command with ->chunkById(200) and Bus::batch() is the workable middle ground.

Normalize input before sending. Concatenate street . ' ' . postcode . ' ' . city, collapse whitespace, and strip trailing punctuation. The BAN matcher is good but not magic; sloppy input is the main cause of low match scores.

Treat the API as unreliable at the edges. Always ->timeout(5)->retry(2, 200) — and in tests, Http::fake() only works if the package goes through Laravel's client. If it ships its own Guzzle instance, you'll need to inject a mocked handler or you lose testability entirely.

Gotchas and Tips

The framework mismatch is the headline risk. A Symfony bundle in a Laravel app means manual instantiation, no auto-configuration, and a hard dependency on Symfony components you may not otherwise have. Symfony's http-client and Guzzle will both end up in vendor/ if you're unlucky. Check composer why after installing.

France only. The BAN covers French addresses including overseas territories, but nothing outside France. If your product is international, this package is the wrong abstraction and you want a provider-agnostic interface from day one — Nominatim, Mapbox, or Google behind the same Geocoder contract.

Never trust a low score. The API returns a confidence value (0–1) per feature. Below ~0.5 the match is usually wrong — a different street, a different commune. Store the score alongside the coordinates and surface it in your admin UI so someone can review the bad ones. Silently accepting a 0.3 match produces customer-visible map pins in the wrong town.

Handle the empty case explicitly. No match returns features: [], not an error. Your code must distinguish "address not found" from "API down." These need different handling — one is a data problem, the other is a retry.

Postal code alone is ambiguous. Several communes share postcodes in France. Always include the city name in the query string; a bare 75002 will resolve to somewhere in the 2nd arrondissement but often not where you meant.

[lng, lat] ordering — mentioned above, repeated here because it's the bug everyone ships once and then spends an afternoon debugging on a map that's rendering off the coast of Somalia.

Zero stars, zero dependents, one release. That means no security advisories filed against it, no Stack Overflow answers, and no guarantee the maintainer responds to an issue. Pin the exact version in composer.json, and write an integration test that hits the real API once (marked @group external) so you find out when it breaks rather than your users.

Extension point worth taking: fork it. Replace the transport with Laravel's Http facade so Http::fake() works, drop the Symfony DI wiring, and add a GeolocationServiceProvider. That's a half-day of work that converts an opaque dependency into something you own — and given the package's size, you may find you've reimplemented it in the process, which is itself the answer to whether you needed it.

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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager