Installation:
composer require willdurand/geocoder-bundle
Enable the bundle in config/bundles.php:
return [
// ...
WillDurand\GeocoderBundle\WillDurandGeocoderBundle::class => ['all' => true],
];
Configuration:
Define providers in config/packages/willdurand_geocoder.yaml:
willdurand_geocoder:
default_provider: google_maps
providers:
google_maps:
key: '%env(GOOGLE_MAPS_API_KEY)%'
http_client: http_client
bing:
key: '%env(BING_MAPS_API_KEY)%'
First Use Case:
Inject the GeocoderInterface into a service/controller:
use Geocoder\Geocoder;
use Geocoder\Provider\GoogleMapsProvider;
public function __construct(private Geocoder $geocoder) {}
public function findCoordinates(string $address): array
{
$coordinates = $this->geocoder->geocode($address)->first();
return $coordinates ? $coordinates->getCoordinates() : null;
}
Symfony Profiler Integration: Automatically enabled. Check the profiler toolbar for geocoding metrics (queries, execution time, results).
public function getAddressFromCoordinates(float $latitude, float $longitude): ?string
{
$coordinates = new \Geocoder\Model\Coordinates($latitude, $longitude);
$addresses = $this->geocoder->reverse($coordinates)->first();
return $addresses ? $addresses->getStreet() : null;
}
Useful for bulk operations (e.g., importing addresses):
public function batchGeocode(array $addresses): array
{
$results = [];
foreach ($addresses as $address) {
$results[$address] = $this->geocoder->geocode($address)->first()?->getCoordinates();
}
return $results;
}
Configure multiple providers and let the bundle handle fallbacks:
willdurand_geocoder:
providers:
primary:
key: '%env(PRIMARY_API_KEY)%'
fallback:
key: '%env(FALLBACK_API_KEY)%'
In code:
$this->geocoder->geocode($address); // Automatically falls back if primary fails.
Extend the bundle with custom providers (e.g., internal databases):
willdurand_geocoder:
providers:
custom_db:
class: App\Geocoder\CustomDbProvider
Implement Geocoder\Provider\ProviderInterface:
class CustomDbProvider implements ProviderInterface {
public function geocode($query, $options = []): iterable {
// Fetch from DB or other source.
}
// ... other required methods.
}
Cache results to reduce API calls (e.g., using Symfony Cache component):
willdurand_geocoder:
providers:
google_maps:
key: '%env(GOOGLE_MAPS_API_KEY)%'
cache: cache.app
Requires symfony/cache bundle.
Use with Symfony Forms for address validation:
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Validator\Constraints\Geocoder;
$builder->add('address', TextType::class, [
'constraints' => [
new Geocoder(['provider' => 'google_maps']),
],
]);
Trigger geocoding on entity events (e.g., prePersist):
public function onPrePersist(User $user)
{
if (!$user->getCoordinates() && $user->getAddress()) {
$coordinates = $this->geocoder->geocode($user->getAddress())->first()?->getCoordinates();
$user->setCoordinates($coordinates);
}
}
Create a console command for bulk operations:
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class GeocodeCommand extends Command {
protected function execute(InputInterface $input, OutputInterface $output): int
{
$addresses = $this->getAddressesFromCsv();
foreach ($addresses as $address) {
$coordinates = $this->geocoder->geocode($address)->first()?->getCoordinates();
$this->saveToDatabase($address, $coordinates);
}
return Command::SUCCESS;
}
}
%env(KEY)%) and restrict bundle access to sensitive keys.$this->geocoder->geocode($address, ['timeout' => 2, 'retry_on_failure' => 3]);
https:// URLs and may block non-commercial use.openstreetmap provider with caution in production.$result = $this->geocoder->geocode($address)->first();
$timeZone = $result->getTimezone(); // Requires provider support.
config/packages/dev/willdurand_geocoder.yaml:
willdurand_geocoder:
profiler: false
Configure Monolog to log geocoding attempts:
monolog:
handlers:
main:
level: debug
channels: ["geocoder"]
Then, in code:
$this->geocoder->geocode($address, ['logger' => true]);
Use a custom provider to log raw API responses:
class DebugProvider implements ProviderInterface {
public function geocode($query, $options = []): iterable {
$response = $this->httpClient->request('GET', $this->url($query));
$this->logger->debug('Raw response:', ['data' => $response->getContent()]);
// Parse and return results.
}
}
Some providers (e.g., Bing) may return partial results. Validate responses:
$results = $this->geocoder->geocode($address);
if ($results->count() === 0) {
throw new \RuntimeException('No results found for address: ' . $address);
}
null addresses.NaN or null values).Override provider creation in a compiler pass:
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use WillDurand\GeocoderBundle\DependencyInjection\Compiler\GeocoderPass;
class CustomGeocoderPass implements CompilerPassInterface {
public function process(ContainerBuilder $container) {
$definition = $container->findDefinition('geocoder.provider.custom');
$definition->setClass('App\Geocoder\CustomProvider');
}
}
How can I help you explore Laravel packages today?