Weave Code
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

Chain Provider Laravel Package

geocoder-php/chain-provider

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Install Dependencies:

    composer require geocoder-php/chain-provider geocoder-php/geocoder
    
  2. 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
        ],
    ],
    
  3. 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);
    }
    
  4. 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);
    }
    
  5. First Reverse Geocode Call:

    public function reverseGeocode(Request $request, Geocoder $geocoder)
    {
        $coordinates = [$request->lat, $request->lng];
        $results = $geocoder->reverse($coordinates);
        return response()->json($results);
    }
    

Implementation Patterns

Common Workflows

1. Multi-Provider Fallback

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);

2. Dynamic Provider Loading

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();

3. Caching Results

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);
    });
}

4. Async Processing with Queues

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]);
}

5. Logging Failures

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(),
    ]);
}

Integration Tips

Laravel Facade

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');

Provider-Specific Config

Use Laravel’s config() helper to manage provider settings:

$provider = new MapboxProvider(config('services.mapbox.key'));

Testing

Mock the Geocoder interface in tests:

$mock = Mockery::mock(Geocoder::class);
$mock->shouldReceive('geocode')
     ->once()
     ->andReturn([new Address(...)]);

$this->app->instance(Geocoder::class, $mock);

Event Listeners

Trigger events on geocoding success/failure:

// Listen for geocoding failures
event(new GeocodingFailed($query, $exception));

Gotchas and Tips

Pitfalls

  1. Provider Order Matters:

    • The first provider in the chain is tried first. Reorder for cost/accuracy tradeoffs.
    • Example: Place cheaper providers (e.g., OpenStreetMap) before expensive ones (e.g., Google).
  2. API Key Leaks:

    • Never hardcode API keys. Use Laravel’s .env and config/services.php.
    • Example:
      // ❌ Avoid
      new GoogleMapsProvider('your_key_here');
      
      // ✅ Use
      new GoogleMapsProvider(config('services.google.key'));
      
  3. Rate Limiting:

    • Free providers (e.g., OpenStreetMap) have strict limits. Implement retries with backoff:
      use Symfony\Component\Process\Exception\ProcessFailedException;
      
      try {
          $results = $geocoder->geocode($query);
      } catch (ProcessFailedException $e) {
          sleep(2); // Backoff
          retry();
      }
      
  4. Circular Dependencies:

    • Avoid injecting Geocoder into providers (e.g., OpenStreetMapProvider should not depend on Geocoder).
  5. Timeouts:

    • Slow providers (e.g., Google) may timeout. Configure HTTP client timeouts:
      $client = new \GuzzleHttp\Client([
          'timeout' => 10.0,
      ]);
      new OpenStreetMapProvider($client);
      
  6. Case Sensitivity:

    • Some providers (e.g., OpenStreetMap) are case-sensitive for queries. Normalize input:
      $query = mb_strtolower($request->address);
      

Debugging Tips

  1. 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)),
    ]);
    
  2. Inspect Provider Responses: Use dd() to debug raw responses:

    $results = $geocoder->geocode($query);
    dd($results->first()->rawData); // Raw API response
    
  3. Check HTTP Client: Verify the PSR-18 client is configured correctly:

    $client = new \GuzzleHttp\Client();
    $provider = new OpenStreetMapProvider($client);
    
  4. Validate Coordinates: Ensure reverse geocoding coordinates are valid:

    if (!is_array($coordinates) || count($coordinates) !== 2) {
        throw new \InvalidArgumentException('Coordinates must be [lat, lng]');
    }
    

Extension Points

  1. Custom Provider: Extend AbstractProvider to create a custom provider:

    class CustomProvider extends AbstractProvider
    {
        public function geocodeQuery($query)
        {
            // Custom logic
        }
    }
    
  2. Middleware for Geocoding: Add middleware to validate geocoding results:

    public function handle($request, Closure $next)
    
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.
sentix/ai-chatbot
terminal42/code-quality-tools
codifyo/ts-generator-bundle
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky