Install Dependencies:
composer require geocoder-php/chain-provider geocoder-php/geocoder
Configure a Chain Provider:
In config/services.php or a custom config file (e.g., config/geocoder.php):
'geocoder' => [
'providers' => [
'openstreetmap' => [
'http_client' => fn() => new \GuzzleHttp\Client(),
],
'mapbox' => [
'key' => env('MAPBOX_API_KEY'),
'http_client' => fn() => new \GuzzleHttp\Client(),
],
],
'chain' => [
'providers' => ['openstreetmap', 'mapbox'], // Fallback order
'fail_silent' => false, // Log errors if true
],
],
Bind to Laravel Container:
In AppServiceProvider.php:
use Geocoder\Geocoder;
use Geocoder\Provider\ChainProvider;
use Geocoder\Provider\OpenStreetMapProvider;
use Geocoder\Provider\MapboxProvider;
public function boot()
{
$providers = [
new OpenStreetMapProvider(),
new MapboxProvider(config('services.mapbox.key')),
];
$geocoder = new Geocoder($providers);
$this->app->singleton(Geocoder::class, fn() => $geocoder);
}
First Geocode Call: In a controller or service:
use Geocoder\Geocoder;
public function geocodeAddress(Request $request, Geocoder $geocoder)
{
$results = $geocoder->geocode($request->address);
return response()->json($results);
}
First Reverse Geocode Call:
public function reverseGeocode(Request $request, Geocoder $geocoder)
{
$coordinates = [$request->lat, $request->lng];
$results = $geocoder->reverse($coordinates);
return response()->json($results);
}
Chain providers by priority (e.g., free → paid):
$chain = new ChainProvider([
new OpenStreetMapProvider(),
new MapboxProvider(env('MAPBOX_KEY')),
new GoogleMapsProvider(env('GOOGLE_KEY')),
]);
$geocoder = new Geocoder($chain);
Load providers from config dynamically:
$providers = collect(config('geocoder.providers'))
->map(fn($config) => match($config['type']) {
'openstreetmap' => new OpenStreetMapProvider(),
'mapbox' => new MapboxProvider($config['key']),
default => throw new \InvalidArgumentException("Unknown provider: {$config['type']}"),
})
->values()
->toArray();
Cache geocoding results for 1 hour:
public function geocodeWithCache(string $query, Geocoder $geocoder)
{
return Cache::remember("geocode_{$query}", 3600, function() use ($geocoder, $query) {
return $geocoder->geocode($query);
});
}
Dispatch a job for background geocoding:
use App\Jobs\GeocodeAddress;
public function queueGeocode(Request $request)
{
GeocodeAddress::dispatch($request->address, $request->user());
}
Job implementation:
public function handle()
{
$results = app(Geocoder::class)->geocode($this->address);
$this->user->update(['address' => $results->first()->formatted]);
}
Log provider-specific errors:
try {
$results = $geocoder->geocode($query);
} catch (\Geocoder\Exception\UnsupportedProvider $e) {
Log::warning("Unsupported provider in chain: {$e->getProvider()}", [
'query' => $query,
'chain' => $geocoder->getProviders(),
]);
}
Create a facade for cleaner syntax:
// app/Facades/Geocoder.php
public static function geocode(string $query)
{
return app(Geocoder::class)->geocode($query);
}
Usage:
$results = Geocoder::geocode('1600 Amphitheatre Parkway');
Use Laravel’s config() helper to manage provider settings:
$provider = new MapboxProvider(config('services.mapbox.key'));
Mock the Geocoder interface in tests:
$mock = Mockery::mock(Geocoder::class);
$mock->shouldReceive('geocode')
->once()
->andReturn([new Address(...)]);
$this->app->instance(Geocoder::class, $mock);
Trigger events on geocoding success/failure:
// Listen for geocoding failures
event(new GeocodingFailed($query, $exception));
Provider Order Matters:
API Key Leaks:
.env and config/services.php.// ❌ Avoid
new GoogleMapsProvider('your_key_here');
// ✅ Use
new GoogleMapsProvider(config('services.google.key'));
Rate Limiting:
use Symfony\Component\Process\Exception\ProcessFailedException;
try {
$results = $geocoder->geocode($query);
} catch (ProcessFailedException $e) {
sleep(2); // Backoff
retry();
}
Circular Dependencies:
Geocoder into providers (e.g., OpenStreetMapProvider should not depend on Geocoder).Timeouts:
$client = new \GuzzleHttp\Client([
'timeout' => 10.0,
]);
new OpenStreetMapProvider($client);
Case Sensitivity:
$query = mb_strtolower($request->address);
Enable Debug Logging:
Configure Monolog in config/logging.php:
'channels' => [
'geocoder' => [
'driver' => 'single',
'path' => storage_path('logs/geocoder.log'),
'level' => 'debug',
],
],
Log provider responses:
Log::debug('Geocoding attempt', [
'query' => $query,
'providers' => collect($geocoder->getProviders())->map(fn($p) => get_class($p)),
]);
Inspect Provider Responses:
Use dd() to debug raw responses:
$results = $geocoder->geocode($query);
dd($results->first()->rawData); // Raw API response
Check HTTP Client: Verify the PSR-18 client is configured correctly:
$client = new \GuzzleHttp\Client();
$provider = new OpenStreetMapProvider($client);
Validate Coordinates: Ensure reverse geocoding coordinates are valid:
if (!is_array($coordinates) || count($coordinates) !== 2) {
throw new \InvalidArgumentException('Coordinates must be [lat, lng]');
}
Custom Provider:
Extend AbstractProvider to create a custom provider:
class CustomProvider extends AbstractProvider
{
public function geocodeQuery($query)
{
// Custom logic
}
}
Middleware for Geocoding: Add middleware to validate geocoding results:
public function handle($request, Closure $next)
How can I help you explore Laravel packages today?