Installation
composer require bordeux/geoname-bundle
Add to config/bundles.php:
return [
// ...
Bordeux\GeonameBundle\BordeuxGeonameBundle::class => ['all' => true],
];
Configuration
Create a config/packages/bordeux_geoname.yaml:
bordeux_geoname:
api_key: 'your_geonames_api_key' # Get from https://www.geonames.org/login
cache_dir: '%kernel.cache_dir%/geoname'
import:
countries: true
states: true
cities: true
First Use Case Fetch a country by ISO code:
use Bordeux\GeonameBundle\Entity\Country;
$country = $this->getDoctrine()
->getRepository(Country::class)
->findOneBy(['isoCode' => 'US']);
One-Time Setup Use the CLI command to import data (run once):
php bin/console bordeux:geoname:import
--update flag.Lazy Loading For large datasets, configure Doctrine to lazy-load:
# config/packages/doctrine.yaml
orm:
dql:
string_functions:
GEONAME_DISTANCE: Bordeux\GeonameBundle\DQL\GeonameDistance
Integration with Forms Use the bundle’s entities in Symfony forms:
use Bordeux\GeonameBundle\Form\Type\CountryType;
$builder->add('country', CountryType::class, [
'label' => 'Select Country',
'required' => true,
]);
$geonameService = $this->get('bordeux_geoname.service');
$location = $geonameService->findNearbyPlace(52.5200, 13.4050, 10); // Berlin, radius=10km
$validator = $this->get('validator');
$errors = $validator->validate($userInput, [
new \Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity(['entityClass' => City::class, 'fields' => ['name', 'geonameId']]),
]);
cache_dir to persist API calls.$this->get('bordeux_geoname.cache')->clear();
API Rate Limits
import command for offline access.Doctrine Mismatches
geonameId as primary key).Symfony 5+ Compatibility
composer.json for symfony/* version constraints.import command ran successfully:
php bin/console bordeux:geoname:import --update
api_key and validate it on GeoNames.DISTINCT or IN clauses for large queries:
$qb->select('DISTINCT c')
->from(Country::class, 'c')
->where('c.name LIKE :name')
->setParameter('name', '%USA%');
Custom Data Imports Extend the importer to fetch additional GeoNames datasets (e.g., airports):
// src/Command/CustomGeonameImportCommand.php
use Bordeux\GeonameBundle\Command\AbstractGeonameImportCommand;
class CustomGeonameImportCommand extends AbstractGeonameImportCommand {
protected function configure() {
$this->setName('app:geoname:import:airports');
}
protected function getDataUrl() { return 'http://download.geonames.org/export/dump/airport.txt'; }
}
Event Listeners Hook into entity lifecycle events (e.g., validate geoname IDs):
// src/EventListener/GeonameValidationListener.php
use Bordeux\GeonameBundle\Entity\City;
use Doctrine\Common\EventSubscriber;
class GeonameValidationListener implements EventSubscriber {
public function prePersist($entity) {
if ($entity instanceof City && empty($entity->getGeonameId())) {
throw new \RuntimeException('Geoname ID required for City entity.');
}
}
// ...
}
Custom DQL Functions Add distance calculations between locations:
// src/DQL/Haversine.php
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
class Haversine extends FunctionNode {
public function parse(\Doctrine\ORM\Query\Parser $parser) { /* ... */ }
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) { /* ... */ }
}
Register in doctrine.yaml:
dql:
string_functions:
HAVERSINE: App\DQL\Haversine
How can I help you explore Laravel packages today?