├── phpstan.dist.neon ├── config ├── profiling.php ├── orm.php └── services.php ├── src ├── Mapping │ ├── Exception │ │ └── MappingException.php │ ├── Attributes │ │ ├── Latitude.php │ │ ├── Longitude.php │ │ ├── Address.php │ │ └── Geocodeable.php │ ├── Driver │ │ ├── DriverInterface.php │ │ ├── ChainDriver.php │ │ └── AttributeDriver.php │ └── ClassMetadata.php ├── BazingaGeocoderBundle.php ├── Validator │ └── Constraint │ │ ├── Address.php │ │ └── AddressValidator.php ├── DependencyInjection │ ├── Compiler │ │ ├── ProfilerPass.php │ │ ├── AddProvidersPass.php │ │ └── FactoryValidatorPass.php │ ├── Configuration.php │ └── BazingaGeocoderExtension.php ├── ProviderFactory │ ├── HostIpFactory.php │ ├── IpInfoFactory.php │ ├── GeoPluginFactory.php │ ├── LocationIQFactory.php │ ├── TomTomFactory.php │ ├── IpstackFactory.php │ ├── BingMapsFactory.php │ ├── ChainFactory.php │ ├── OpenCageFactory.php │ ├── GeonamesFactory.php │ ├── PickPointFactory.php │ ├── PluginProviderFactory.php │ ├── FreeGeoIpFactory.php │ ├── ArcGISOnlineFactory.php │ ├── GoogleMapsPlacesFactory.php │ ├── OpenRouteServiceFactory.php │ ├── YandexFactory.php │ ├── MapQuestFactory.php │ ├── IpInfoDbFactory.php │ ├── GoogleMapsFactory.php │ ├── AlgoliaFactory.php │ ├── MaxMindFactory.php │ ├── MapboxFactory.php │ ├── NominatimFactory.php │ ├── ProviderFactoryInterface.php │ ├── HereFactory.php │ ├── GeoIP2Factory.php │ └── AbstractFactory.php ├── Plugin │ ├── FakeIpPlugin.php │ └── ProfilingPlugin.php ├── Command │ └── GeocodeCommand.php ├── DataCollector │ └── GeocoderDataCollector.php └── Doctrine │ └── ORM │ └── GeocodeEntityListener.php ├── templates └── Collector │ ├── icon.svg │ └── geocoder.html.twig ├── LICENSE ├── README.md ├── composer.json ├── CHANGELOG.md └── phpstan-baseline.php /phpstan.dist.neon: -------------------------------------------------------------------------------- 1 | includes: 2 | - phpstan-baseline.php 3 | 4 | parameters: 5 | bootstrapFiles: 6 | - vendor/bin/.phpunit/phpunit/vendor/autoload.php 7 | paths: 8 | - src/ 9 | #- tests/ 10 | level: 9 11 | treatPhpDocTypesAsCertain: false 12 | -------------------------------------------------------------------------------- /config/profiling.php: -------------------------------------------------------------------------------- 1 | services(); 11 | 12 | $services->set(GeocoderDataCollector::class) 13 | ->tag('data_collector', ['template' => '@BazingaGeocoder/Collector/geocoder.html.twig', 'id' => 'geocoder']); 14 | }; 15 | -------------------------------------------------------------------------------- /src/Mapping/Exception/MappingException.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | class MappingException extends \Exception 19 | { 20 | } 21 | -------------------------------------------------------------------------------- /src/Mapping/Attributes/Latitude.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | #[\Attribute(\Attribute::TARGET_PROPERTY)] 19 | class Latitude 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Mapping/Attributes/Longitude.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | #[\Attribute(\Attribute::TARGET_PROPERTY)] 19 | class Longitude 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Mapping/Attributes/Address.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | #[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD)] 19 | class Address 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /templates/Collector/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /config/orm.php: -------------------------------------------------------------------------------- 1 | services(); 12 | 13 | $services 14 | ->set(GeocodeEntityListener::class) 15 | ->args([ 16 | tagged_locator('bazinga_geocoder.provider'), 17 | service(DriverInterface::class), 18 | ]) 19 | ->tag('doctrine.event_listener', ['event' => 'onFlush']) 20 | ; 21 | }; 22 | -------------------------------------------------------------------------------- /src/Mapping/Attributes/Geocodeable.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | #[\Attribute(\Attribute::TARGET_CLASS)] 19 | class Geocodeable 20 | { 21 | /** 22 | * @param non-empty-string $provider 23 | */ 24 | public function __construct( 25 | public readonly string $provider, 26 | ) { 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Mapping/Driver/DriverInterface.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | final class ClassMetadata 19 | { 20 | /** 21 | * @param non-empty-string $provider 22 | */ 23 | public function __construct( 24 | public readonly string $provider, 25 | public readonly ?\ReflectionProperty $addressProperty = null, 26 | public readonly ?\ReflectionProperty $latitudeProperty = null, 27 | public readonly ?\ReflectionProperty $longitudeProperty = null, 28 | public readonly ?\ReflectionMethod $addressGetter = null, 29 | ) { 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Since 2011 — William Durand 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/BazingaGeocoderBundle.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | final class BazingaGeocoderBundle extends Bundle 25 | { 26 | public function build(ContainerBuilder $container): void 27 | { 28 | parent::build($container); 29 | 30 | $container->addCompilerPass(new ProfilerPass()); 31 | $container->addCompilerPass(new AddProvidersPass()); 32 | $container->addCompilerPass(new FactoryValidatorPass()); 33 | } 34 | 35 | public function getPath(): string 36 | { 37 | return \dirname(__DIR__); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Validator/Constraint/Address.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | #[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] 24 | class Address extends Constraint 25 | { 26 | public const INVALID_ADDRESS_ERROR = '2243aa07-2ea7-4eb7-962c-6a9586593f2c'; 27 | 28 | protected const ERROR_NAMES = [ 29 | self::INVALID_ADDRESS_ERROR => 'INVALID_ADDRESS_ERROR', 30 | ]; 31 | 32 | public function __construct(public string $service = AddressValidator::class, public string $message = 'Address {{ address }} is not valid.', ?array $groups = null, $payload = null) 33 | { 34 | parent::__construct(null, $groups, $payload); 35 | } 36 | 37 | public function validatedBy(): string 38 | { 39 | return $this->service; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/DependencyInjection/Compiler/ProfilerPass.php: -------------------------------------------------------------------------------- 1 | 24 | */ 25 | class ProfilerPass implements CompilerPassInterface 26 | { 27 | public function process(ContainerBuilder $container): void 28 | { 29 | if (!$container->hasDefinition(GeocoderDataCollector::class)) { 30 | return; 31 | } 32 | 33 | $dataCollector = $container->getDefinition(GeocoderDataCollector::class); 34 | 35 | foreach ($container->findTaggedServiceIds('bazinga_geocoder.profiling_plugin') as $providerId => $attributes) { 36 | $dataCollector->addMethodCall('addInstance', [new Reference($providerId)]); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/ProviderFactory/HostIpFactory.php: -------------------------------------------------------------------------------- 1 | HostIp::class, 'packageName' => 'geocoder-php/host-ip-provider'], 25 | ]; 26 | 27 | /** 28 | * @param array{http_client: ?ClientInterface} $config 29 | */ 30 | protected function getProvider(array $config): Provider 31 | { 32 | $httpClient = $config['http_client'] ?? $this->httpClient ?? Psr18ClientDiscovery::find(); 33 | 34 | return new HostIp($httpClient); 35 | } 36 | 37 | protected static function configureOptionResolver(OptionsResolver $resolver): void 38 | { 39 | $resolver->setDefaults([ 40 | 'http_client' => null, 41 | ]); 42 | 43 | $resolver->setAllowedTypes('http_client', ['object', 'null']); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/ProviderFactory/IpInfoFactory.php: -------------------------------------------------------------------------------- 1 | IpInfo::class, 'packageName' => 'geocoder-php/ip-info-provider'], 25 | ]; 26 | 27 | /** 28 | * @param array{http_client: ?ClientInterface} $config 29 | */ 30 | protected function getProvider(array $config): Provider 31 | { 32 | $httpClient = $config['http_client'] ?? $this->httpClient ?? Psr18ClientDiscovery::find(); 33 | 34 | return new IpInfo($httpClient); 35 | } 36 | 37 | protected static function configureOptionResolver(OptionsResolver $resolver): void 38 | { 39 | $resolver->setDefaults([ 40 | 'http_client' => null, 41 | ]); 42 | 43 | $resolver->setAllowedTypes('http_client', ['object', 'null']); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/ProviderFactory/GeoPluginFactory.php: -------------------------------------------------------------------------------- 1 | GeoPlugin::class, 'packageName' => 'geocoder-php/geo-plugin-provider'], 25 | ]; 26 | 27 | /** 28 | * @param array{http_client: ?ClientInterface} $config 29 | */ 30 | protected function getProvider(array $config): Provider 31 | { 32 | $httpClient = $config['http_client'] ?? $this->httpClient ?? Psr18ClientDiscovery::find(); 33 | 34 | return new GeoPlugin($httpClient); 35 | } 36 | 37 | protected static function configureOptionResolver(OptionsResolver $resolver): void 38 | { 39 | $resolver->setDefaults([ 40 | 'http_client' => null, 41 | ]); 42 | 43 | $resolver->setAllowedTypes('http_client', ['object', 'null']); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/DependencyInjection/Compiler/AddProvidersPass.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | class AddProvidersPass implements CompilerPassInterface 24 | { 25 | /** 26 | * Get all providers based on their tag (`bazinga_geocoder.provider`) and 27 | * register them. 28 | */ 29 | public function process(ContainerBuilder $container): void 30 | { 31 | if (!$container->hasDefinition(ProviderAggregator::class)) { 32 | return; 33 | } 34 | 35 | $providers = []; 36 | foreach ($container->findTaggedServiceIds('bazinga_geocoder.provider') as $providerId => $attributes) { 37 | $providers[] = new Reference($providerId); 38 | } 39 | 40 | $geocoderDefinition = $container->getDefinition(ProviderAggregator::class); 41 | $geocoderDefinition->addMethodCall('registerProviders', [$providers]); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Mapping/Driver/ChainDriver.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | final class ChainDriver implements DriverInterface 22 | { 23 | /** 24 | * @param iterable $drivers 25 | */ 26 | public function __construct( 27 | private iterable $drivers, 28 | ) { 29 | } 30 | 31 | public function isGeocodeable(object $object): bool 32 | { 33 | foreach ($this->drivers as $driver) { 34 | if ($driver->isGeocodeable($object)) { 35 | return true; 36 | } 37 | } 38 | 39 | return false; 40 | } 41 | 42 | public function loadMetadataFromObject(object $object): ClassMetadata 43 | { 44 | foreach ($this->drivers as $driver) { 45 | try { 46 | return $driver->loadMetadataFromObject($object); 47 | } catch (MappingException) { 48 | continue; 49 | } 50 | } 51 | 52 | throw new MappingException(sprintf('The class "%s" is not geocodeable.', $object::class)); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/DependencyInjection/Compiler/FactoryValidatorPass.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class FactoryValidatorPass implements CompilerPassInterface 25 | { 26 | /** 27 | * @var list 28 | */ 29 | private static array $factoryServiceIds = []; 30 | 31 | public function process(ContainerBuilder $container): void 32 | { 33 | foreach (self::$factoryServiceIds as $id) { 34 | if (!$container->hasAlias($id) && !$container->hasDefinition($id)) { 35 | throw new ServiceNotFoundException(sprintf('Factory with ID "%s" could not be found', $id)); 36 | } 37 | } 38 | } 39 | 40 | /** 41 | * @param non-empty-string $factoryServiceId 42 | */ 43 | public static function addFactoryServiceId(string $factoryServiceId): void 44 | { 45 | self::$factoryServiceIds[] = $factoryServiceId; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/ProviderFactory/LocationIQFactory.php: -------------------------------------------------------------------------------- 1 | LocationIQ::class, 'packageName' => 'geocoder-php/locationiq-provider'], 23 | ]; 24 | 25 | /** 26 | * @param array{api_key: string, http_client: ?ClientInterface} $config 27 | */ 28 | protected function getProvider(array $config): Provider 29 | { 30 | $httpClient = $config['http_client'] ?? $this->httpClient ?? Psr18ClientDiscovery::find(); 31 | 32 | return new LocationIQ($httpClient, $config['api_key']); 33 | } 34 | 35 | protected static function configureOptionResolver(OptionsResolver $resolver): void 36 | { 37 | $resolver->setDefaults([ 38 | 'http_client' => null, 39 | ]); 40 | 41 | $resolver->setAllowedTypes('http_client', ['object', 'null']); 42 | $resolver->setRequired(['api_key']); 43 | $resolver->setAllowedTypes('api_key', ['string']); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/ProviderFactory/TomTomFactory.php: -------------------------------------------------------------------------------- 1 | TomTom::class, 'packageName' => 'geocoder-php/tomtom-provider'], 25 | ]; 26 | 27 | /** 28 | * @param array{api_key: string, http_client: ?ClientInterface} $config 29 | */ 30 | protected function getProvider(array $config): Provider 31 | { 32 | $httpClient = $config['http_client'] ?? $this->httpClient ?? Psr18ClientDiscovery::find(); 33 | 34 | return new TomTom($httpClient, $config['api_key']); 35 | } 36 | 37 | protected static function configureOptionResolver(OptionsResolver $resolver): void 38 | { 39 | $resolver->setDefaults([ 40 | 'http_client' => null, 41 | ]); 42 | 43 | $resolver->setRequired('api_key'); 44 | $resolver->setAllowedTypes('http_client', ['object', 'null']); 45 | $resolver->setAllowedTypes('api_key', ['string']); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/ProviderFactory/IpstackFactory.php: -------------------------------------------------------------------------------- 1 | Ipstack::class, 'packageName' => 'geocoder-php/ipstack-provider'], 25 | ]; 26 | 27 | /** 28 | * @param array{api_key: string, http_client: ?ClientInterface} $config 29 | */ 30 | protected function getProvider(array $config): Provider 31 | { 32 | $httpClient = $config['http_client'] ?? $this->httpClient ?? Psr18ClientDiscovery::find(); 33 | 34 | return new Ipstack($httpClient, $config['api_key']); 35 | } 36 | 37 | protected static function configureOptionResolver(OptionsResolver $resolver): void 38 | { 39 | $resolver->setDefaults([ 40 | 'http_client' => null, 41 | ]); 42 | 43 | $resolver->setRequired('api_key'); 44 | $resolver->setAllowedTypes('http_client', ['object', 'null']); 45 | $resolver->setAllowedTypes('api_key', ['string']); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/ProviderFactory/BingMapsFactory.php: -------------------------------------------------------------------------------- 1 | BingMaps::class, 'packageName' => 'geocoder-php/bing-maps-provider'], 25 | ]; 26 | 27 | /** 28 | * @param array{api_key: string, http_client: ?ClientInterface} $config 29 | */ 30 | protected function getProvider(array $config): Provider 31 | { 32 | $httpClient = $config['http_client'] ?? $this->httpClient ?? Psr18ClientDiscovery::find(); 33 | 34 | return new BingMaps($httpClient, $config['api_key']); 35 | } 36 | 37 | protected static function configureOptionResolver(OptionsResolver $resolver): void 38 | { 39 | $resolver->setDefaults([ 40 | 'http_client' => null, 41 | ]); 42 | 43 | $resolver->setRequired('api_key'); 44 | $resolver->setAllowedTypes('http_client', ['object', 'null']); 45 | $resolver->setAllowedTypes('api_key', ['string']); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/ProviderFactory/ChainFactory.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | final class ChainFactory extends AbstractFactory implements LoggerAwareInterface 25 | { 26 | use LoggerAwareTrait; 27 | 28 | protected static array $dependencies = [ 29 | ['requiredClass' => Chain::class, 'packageName' => 'geocoder-php/chain-provider'], 30 | ]; 31 | 32 | /** 33 | * @param array{services: Provider[]} $config 34 | */ 35 | protected function getProvider(array $config): Provider 36 | { 37 | $provider = new Chain($config['services']); 38 | if (null !== $this->logger) { 39 | $provider->setLogger($this->logger); 40 | } 41 | 42 | return $provider; 43 | } 44 | 45 | protected static function configureOptionResolver(OptionsResolver $resolver): void 46 | { 47 | parent::configureOptionResolver($resolver); 48 | 49 | $resolver->setRequired('services'); 50 | $resolver->setAllowedTypes('services', ['array']); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/ProviderFactory/OpenCageFactory.php: -------------------------------------------------------------------------------- 1 | OpenCage::class, 'packageName' => 'geocoder-php/open-cage-provider'], 25 | ]; 26 | 27 | /** 28 | * @param array{api_key: string, http_client: ?ClientInterface} $config 29 | */ 30 | protected function getProvider(array $config): Provider 31 | { 32 | $httpClient = $config['http_client'] ?? $this->httpClient ?? Psr18ClientDiscovery::find(); 33 | 34 | return new OpenCage($httpClient, $config['api_key']); 35 | } 36 | 37 | protected static function configureOptionResolver(OptionsResolver $resolver): void 38 | { 39 | $resolver->setDefaults([ 40 | 'http_client' => null, 41 | ]); 42 | 43 | $resolver->setRequired('api_key'); 44 | $resolver->setAllowedTypes('http_client', ['object', 'null']); 45 | $resolver->setAllowedTypes('api_key', ['string']); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/ProviderFactory/GeonamesFactory.php: -------------------------------------------------------------------------------- 1 | Geonames::class, 'packageName' => 'geocoder-php/geonames-provider'], 25 | ]; 26 | 27 | /** 28 | * @param array{username: string, http_client: ?ClientInterface} $config 29 | */ 30 | protected function getProvider(array $config): Provider 31 | { 32 | $httpClient = $config['http_client'] ?? $this->httpClient ?? Psr18ClientDiscovery::find(); 33 | 34 | return new Geonames($httpClient, $config['username']); 35 | } 36 | 37 | protected static function configureOptionResolver(OptionsResolver $resolver): void 38 | { 39 | $resolver->setDefaults([ 40 | 'http_client' => null, 41 | ]); 42 | 43 | $resolver->setRequired('username'); 44 | $resolver->setAllowedTypes('http_client', ['object', 'null']); 45 | $resolver->setAllowedTypes('username', ['string']); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/ProviderFactory/PickPointFactory.php: -------------------------------------------------------------------------------- 1 | PickPoint::class, 'packageName' => 'geocoder-php/pickpoint-provider'], 25 | ]; 26 | 27 | /** 28 | * @param array{api_key: string, http_client: ?ClientInterface} $config 29 | */ 30 | protected function getProvider(array $config): Provider 31 | { 32 | $httpClient = $config['http_client'] ?? $this->httpClient ?? Psr18ClientDiscovery::find(); 33 | 34 | return new PickPoint($httpClient, $config['api_key']); 35 | } 36 | 37 | protected static function configureOptionResolver(OptionsResolver $resolver): void 38 | { 39 | $resolver->setDefaults([ 40 | 'http_client' => null, 41 | ]); 42 | 43 | $resolver->setRequired('api_key'); 44 | $resolver->setAllowedTypes('http_client', ['object', 'null']); 45 | $resolver->setAllowedTypes('api_key', ['string']); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/ProviderFactory/PluginProviderFactory.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | final class PluginProviderFactory 25 | { 26 | /** 27 | * @param Plugin[] $plugins 28 | * @param callable(array):Provider|ProviderFactoryInterface $factory 29 | * @param array $config config to the client factory 30 | * @param array{max_restarts?: int<0, max>} $pluginProviderOptions config forwarded to the PluginProvider 31 | */ 32 | public static function createPluginProvider(array $plugins, callable|ProviderFactoryInterface $factory, array $config, array $pluginProviderOptions = []): PluginProvider 33 | { 34 | if ($factory instanceof ProviderFactoryInterface) { 35 | $client = $factory->createProvider($config); 36 | } else { 37 | $client = $factory($config); 38 | } 39 | 40 | return new PluginProvider($client, $plugins, $pluginProviderOptions); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/ProviderFactory/FreeGeoIpFactory.php: -------------------------------------------------------------------------------- 1 | FreeGeoIp::class, 'packageName' => 'geocoder-php/free-geoip-provider'], 25 | ]; 26 | 27 | /** 28 | * @param array{base_url: string, http_client: ?ClientInterface} $config 29 | */ 30 | protected function getProvider(array $config): Provider 31 | { 32 | $httpClient = $config['http_client'] ?? $this->httpClient ?? Psr18ClientDiscovery::find(); 33 | 34 | return new FreeGeoIp($httpClient, $config['base_url']); 35 | } 36 | 37 | protected static function configureOptionResolver(OptionsResolver $resolver): void 38 | { 39 | $resolver->setDefaults([ 40 | 'http_client' => null, 41 | 'base_url' => 'https://freegeoip.app/json/%s', 42 | ]); 43 | 44 | $resolver->setAllowedTypes('http_client', ['object', 'null']); 45 | $resolver->setAllowedTypes('base_url', ['string']); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/ProviderFactory/ArcGISOnlineFactory.php: -------------------------------------------------------------------------------- 1 | ArcGISOnline::class, 'packageName' => 'geocoder-php/arcgis-online-provider'], 25 | ]; 26 | 27 | /** 28 | * @param array{source_country: ?string, http_client: ?ClientInterface} $config 29 | */ 30 | protected function getProvider(array $config): Provider 31 | { 32 | $httpClient = $config['http_client'] ?? $this->httpClient ?? Psr18ClientDiscovery::find(); 33 | 34 | return new ArcGISOnline($httpClient, $config['source_country']); 35 | } 36 | 37 | protected static function configureOptionResolver(OptionsResolver $resolver): void 38 | { 39 | $resolver->setDefaults([ 40 | 'http_client' => null, 41 | 'source_country' => null, 42 | ]); 43 | 44 | $resolver->setAllowedTypes('http_client', ['object', 'null']); 45 | $resolver->setAllowedTypes('source_country', ['string', 'null']); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/ProviderFactory/GoogleMapsPlacesFactory.php: -------------------------------------------------------------------------------- 1 | GoogleMapsPlaces::class, 'packageName' => 'geocoder-php/google-maps-places-provider'], 25 | ]; 26 | 27 | /** 28 | * @param array{api_key: string, http_client: ?ClientInterface} $config 29 | */ 30 | protected function getProvider(array $config): Provider 31 | { 32 | $httpClient = $config['http_client'] ?? $this->httpClient ?? Psr18ClientDiscovery::find(); 33 | 34 | return new GoogleMapsPlaces($httpClient, $config['api_key']); 35 | } 36 | 37 | protected static function configureOptionResolver(OptionsResolver $resolver): void 38 | { 39 | $resolver->setDefaults([ 40 | 'http_client' => null, 41 | ]); 42 | 43 | $resolver->setAllowedTypes('http_client', ['object', 'null']); 44 | $resolver->setRequired(['api_key']); 45 | $resolver->setAllowedTypes('api_key', ['string']); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/ProviderFactory/OpenRouteServiceFactory.php: -------------------------------------------------------------------------------- 1 | OpenRouteService::class, 'packageName' => 'geocoder-php/openrouteservice-provider'], 25 | ]; 26 | 27 | /** 28 | * @param array{api_key: string, http_client: ?ClientInterface} $config 29 | */ 30 | protected function getProvider(array $config): Provider 31 | { 32 | $httpClient = $config['http_client'] ?? $this->httpClient ?? Psr18ClientDiscovery::find(); 33 | 34 | return new OpenRouteService($httpClient, $config['api_key']); 35 | } 36 | 37 | protected static function configureOptionResolver(OptionsResolver $resolver): void 38 | { 39 | $resolver->setDefaults([ 40 | 'http_client' => null, 41 | 'api_key' => null, 42 | ]); 43 | 44 | $resolver->setRequired('api_key'); 45 | $resolver->setAllowedTypes('http_client', ['object', 'null']); 46 | $resolver->setAllowedTypes('api_key', ['string']); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/ProviderFactory/YandexFactory.php: -------------------------------------------------------------------------------- 1 | Yandex::class, 'packageName' => 'geocoder-php/yandex-provider'], 25 | ]; 26 | 27 | /** 28 | * @param array{toponym: ?string, api_key: ?string, http_client: ?ClientInterface} $config 29 | */ 30 | protected function getProvider(array $config): Provider 31 | { 32 | $httpClient = $config['http_client'] ?? $this->httpClient ?? Psr18ClientDiscovery::find(); 33 | 34 | return new Yandex($httpClient, $config['toponym'], $config['api_key']); 35 | } 36 | 37 | protected static function configureOptionResolver(OptionsResolver $resolver): void 38 | { 39 | $resolver->setDefaults([ 40 | 'http_client' => null, 41 | 'toponym' => null, 42 | 'api_key' => null, 43 | ]); 44 | 45 | $resolver->setAllowedTypes('http_client', ['object', 'null']); 46 | $resolver->setAllowedTypes('toponym', ['string', 'null']); 47 | $resolver->setAllowedTypes('api_key', ['string', 'null']); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/ProviderFactory/MapQuestFactory.php: -------------------------------------------------------------------------------- 1 | MapQuest::class, 'packageName' => 'geocoder-php/mapquest-provider'], 25 | ]; 26 | 27 | /** 28 | * @param array{api_key: string, licensed: bool, http_client: ?ClientInterface} $config 29 | */ 30 | protected function getProvider(array $config): Provider 31 | { 32 | $httpClient = $config['http_client'] ?? $this->httpClient ?? Psr18ClientDiscovery::find(); 33 | 34 | return new MapQuest($httpClient, $config['api_key'], $config['licensed']); 35 | } 36 | 37 | protected static function configureOptionResolver(OptionsResolver $resolver): void 38 | { 39 | $resolver->setDefaults([ 40 | 'http_client' => null, 41 | 'licensed' => false, 42 | ]); 43 | 44 | $resolver->setRequired('api_key'); 45 | $resolver->setAllowedTypes('http_client', ['object', 'null']); 46 | $resolver->setAllowedTypes('api_key', ['string']); 47 | $resolver->setAllowedTypes('licensed', ['boolean']); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/ProviderFactory/IpInfoDbFactory.php: -------------------------------------------------------------------------------- 1 | IpInfoDb::class, 'packageName' => 'geocoder-php/ip-info-db-provider'], 25 | ]; 26 | 27 | /** 28 | * @param array{api_key: string, precision: string, http_client: ?ClientInterface} $config 29 | */ 30 | protected function getProvider(array $config): Provider 31 | { 32 | $httpClient = $config['http_client'] ?? $this->httpClient ?? Psr18ClientDiscovery::find(); 33 | 34 | return new IpInfoDb($httpClient, $config['api_key'], $config['precision']); 35 | } 36 | 37 | protected static function configureOptionResolver(OptionsResolver $resolver): void 38 | { 39 | $resolver->setDefaults([ 40 | 'http_client' => null, 41 | 'precision' => 'city', 42 | ]); 43 | 44 | $resolver->setRequired('api_key'); 45 | $resolver->setAllowedTypes('http_client', ['object', 'null']); 46 | $resolver->setAllowedTypes('api_key', ['string']); 47 | $resolver->setAllowedTypes('precision', ['string']); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/ProviderFactory/GoogleMapsFactory.php: -------------------------------------------------------------------------------- 1 | GoogleMaps::class, 'packageName' => 'geocoder-php/google-maps-provider'], 25 | ]; 26 | 27 | /** 28 | * @param array{api_key: ?string, region: ?string, http_client: ?ClientInterface} $config 29 | */ 30 | protected function getProvider(array $config): Provider 31 | { 32 | $httpClient = $config['http_client'] ?? $this->httpClient ?? Psr18ClientDiscovery::find(); 33 | 34 | return new GoogleMaps($httpClient, $config['region'], $config['api_key']); 35 | } 36 | 37 | protected static function configureOptionResolver(OptionsResolver $resolver): void 38 | { 39 | $resolver->setDefaults([ 40 | 'http_client' => null, 41 | 'region' => null, 42 | 'api_key' => null, 43 | ]); 44 | 45 | $resolver->setAllowedTypes('http_client', ['object', 'null']); 46 | $resolver->setAllowedTypes('region', ['string', 'null']); 47 | $resolver->setAllowedTypes('api_key', ['string', 'null']); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/ProviderFactory/AlgoliaFactory.php: -------------------------------------------------------------------------------- 1 | AlgoliaPlaces::class, 'packageName' => 'geocoder-php/algolia-places-provider'], 25 | ]; 26 | 27 | /** 28 | * @param array{api_key: ?string, app_id: ?string, http_client: ?ClientInterface} $config 29 | */ 30 | protected function getProvider(array $config): Provider 31 | { 32 | $httpClient = $config['http_client'] ?? $this->httpClient ?? Psr18ClientDiscovery::find(); 33 | 34 | return new AlgoliaPlaces($httpClient, $config['api_key'], $config['app_id']); 35 | } 36 | 37 | protected static function configureOptionResolver(OptionsResolver $resolver): void 38 | { 39 | $resolver->setDefaults([ 40 | 'http_client' => null, 41 | 'api_key' => null, 42 | 'app_id' => null, 43 | ]); 44 | 45 | $resolver->setAllowedTypes('http_client', ['object', 'null']); 46 | $resolver->setAllowedTypes('api_key', ['string', 'null']); 47 | $resolver->setAllowedTypes('app_id', ['string', 'null']); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/ProviderFactory/MaxMindFactory.php: -------------------------------------------------------------------------------- 1 | MaxMind::class, 'packageName' => 'geocoder-php/maxmind-provider'], 25 | ]; 26 | 27 | /** 28 | * @param array{api_key: string, endpoint: string, http_client: ?ClientInterface} $config 29 | */ 30 | protected function getProvider(array $config): Provider 31 | { 32 | $httpClient = $config['http_client'] ?? $this->httpClient ?? Psr18ClientDiscovery::find(); 33 | 34 | return new MaxMind($httpClient, $config['api_key'], $config['endpoint']); 35 | } 36 | 37 | protected static function configureOptionResolver(OptionsResolver $resolver): void 38 | { 39 | $resolver->setDefaults([ 40 | 'http_client' => null, 41 | 'endpoint' => MaxMind::CITY_EXTENDED_SERVICE, 42 | ]); 43 | 44 | $resolver->setRequired('api_key'); 45 | $resolver->setAllowedTypes('http_client', ['object', 'null']); 46 | $resolver->setAllowedTypes('api_key', ['string']); 47 | $resolver->setAllowedValues('endpoint', [MaxMind::CITY_EXTENDED_SERVICE, MaxMind::OMNI_SERVICE]); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/ProviderFactory/MapboxFactory.php: -------------------------------------------------------------------------------- 1 | Mapbox::class, 'packageName' => 'geocoder-php/mapbox-provider'], 23 | ]; 24 | 25 | /** 26 | * @param array{api_key: string, country: ?string, mode: string, http_client: ?ClientInterface} $config 27 | */ 28 | protected function getProvider(array $config): Provider 29 | { 30 | $httpClient = $config['http_client'] ?? $this->httpClient ?? Psr18ClientDiscovery::find(); 31 | 32 | return new Mapbox($httpClient, $config['api_key'], $config['country'], $config['mode']); 33 | } 34 | 35 | protected static function configureOptionResolver(OptionsResolver $resolver): void 36 | { 37 | $resolver->setDefaults([ 38 | 'http_client' => null, 39 | 'country' => null, 40 | 'mode' => Mapbox::GEOCODING_MODE_PLACES, 41 | ]); 42 | 43 | $resolver->setRequired('api_key'); 44 | $resolver->setAllowedTypes('api_key', ['string']); 45 | $resolver->setAllowedTypes('mode', ['string']); 46 | $resolver->setAllowedTypes('http_client', ['object', 'null']); 47 | $resolver->setAllowedTypes('country', ['string', 'null']); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/ProviderFactory/NominatimFactory.php: -------------------------------------------------------------------------------- 1 | Nominatim::class, 'packageName' => 'geocoder-php/nominatim-provider'], 25 | ]; 26 | 27 | /** 28 | * @param array{root_url: string, user_agent: string, http_client: ?ClientInterface} $config 29 | */ 30 | protected function getProvider(array $config): Provider 31 | { 32 | $httpClient = $config['http_client'] ?? $this->httpClient ?? Psr18ClientDiscovery::find(); 33 | 34 | return new Nominatim($httpClient, $config['root_url'], $config['user_agent']); 35 | } 36 | 37 | protected static function configureOptionResolver(OptionsResolver $resolver): void 38 | { 39 | $resolver->setDefaults([ 40 | 'http_client' => null, 41 | 'root_url' => 'https://nominatim.openstreetmap.org', 42 | 'user_agent' => 'BazingaGeocoderBundle', 43 | ]); 44 | 45 | $resolver->setAllowedTypes('http_client', ['object', 'null']); 46 | $resolver->setAllowedTypes('root_url', ['string']); 47 | $resolver->setAllowedTypes('user_agent', ['string']); 48 | $resolver->setRequired('user_agent'); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Plugin/FakeIpPlugin.php: -------------------------------------------------------------------------------- 1 | 26 | */ 27 | final class FakeIpPlugin implements Plugin 28 | { 29 | private ?Generator $faker = null; 30 | 31 | public function __construct( 32 | private readonly ?string $needle, 33 | private readonly ?string $replacement = null, 34 | bool $useFaker = false, 35 | ) { 36 | if ($useFaker) { 37 | $this->faker = new Generator(); 38 | $this->faker->addProvider(new Internet($this->faker)); 39 | } 40 | } 41 | 42 | /** 43 | * @return Promise 44 | */ 45 | public function handleQuery(Query $query, callable $next, callable $first) 46 | { 47 | if (!$query instanceof GeocodeQuery) { 48 | return $next($query); 49 | } 50 | 51 | $replacement = $this->replacement; 52 | 53 | if (null !== $this->faker) { 54 | $replacement = $this->faker->ipv4(); 55 | } 56 | 57 | if (null !== $this->needle && '' !== $this->needle && $replacement) { 58 | $text = str_replace($this->needle, $replacement, $query->getText(), $count); 59 | 60 | if ($count > 0) { 61 | $query = $query->withText($text); 62 | } 63 | } else { 64 | $query = $query->withText($replacement); 65 | } 66 | 67 | return $next($query); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/ProviderFactory/ProviderFactoryInterface.php: -------------------------------------------------------------------------------- 1 | 25 | */ 26 | interface ProviderFactoryInterface 27 | { 28 | /** 29 | * @param array $options 30 | */ 31 | public function createProvider(array $options = []): Provider; 32 | 33 | /** 34 | * Make sure the options are valid and the dependencies are met. 35 | * 36 | * @param array $options the options the user has provided 37 | * @param non-empty-string $providerName the name the user has chosen for this provider 38 | * 39 | * @throws \LogicException If the factory has missing dependencies 40 | * @throws UndefinedOptionsException If an option name is undefined 41 | * @throws InvalidOptionsException If an option doesn't fulfill the specified validation rules 42 | * @throws MissingOptionsException If a required option is missing 43 | * @throws OptionDefinitionException If there is a cyclic dependency between lazy options and/or normalizers 44 | * @throws NoSuchOptionException If a lazy option reads an unavailable option 45 | * @throws AccessException If called from a lazy option or normalizer 46 | */ 47 | public static function validate(array $options, string $providerName): void; 48 | } 49 | -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | services(); 24 | $services->instanceof(Dumper::class) 25 | ->public(); 26 | 27 | $services 28 | ->set(GeoArray::class) 29 | ->set(GeoJson::class) 30 | ->set(Gpx::class) 31 | ->set(Kml::class) 32 | ->set(Wkb::class) 33 | ->set(Wkt::class) 34 | 35 | ->load('Bazinga\\GeocoderBundle\\ProviderFactory\\', __DIR__.'/../src/ProviderFactory') 36 | ->autowire() 37 | ->autoconfigure() 38 | 39 | ->set(ProviderAggregator::class) 40 | 41 | ->set(FakeIpPlugin::class) 42 | ->args([null, null, false]) 43 | 44 | ->set(GeocodeCommand::class) 45 | ->args([ 46 | service(ProviderAggregator::class), 47 | ]) 48 | ->tag('console.command') 49 | 50 | ->set(AddressValidator::class) 51 | ->args([ 52 | service(ProviderAggregator::class), 53 | ]) 54 | ->tag('validator.constraint_validator') 55 | 56 | ->set(ChainDriver::class) 57 | ->args([ 58 | tagged_iterator('bazinga_geocoder.metadata.driver', exclude: [ChainDriver::class]), 59 | ]) 60 | ->tag('bazinga_geocoder.metadata.driver') 61 | ->alias(DriverInterface::class, ChainDriver::class) 62 | 63 | ->set(AttributeDriver::class) 64 | ->tag('bazinga_geocoder.metadata.driver') 65 | ; 66 | }; 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | BazingaGeocoderBundle 2 | ===================== 3 | 4 | [![Build Status](https://github.com/geocoder-php/BazingaGeocoderBundle/actions/workflows/ci.yml/badge.svg)](https://github.com/geocoder-php/BazingaGeocoderBundle/actions/workflows/ci.yml) 5 | [![Latest Stable Version](https://poser.pugx.org/willdurand/geocoder-bundle/v/stable)](https://packagist.org/packages/willdurand/geocoder-bundle) 6 | [![Total Downloads](https://poser.pugx.org/willdurand/geocoder-bundle/downloads)](https://packagist.org/packages/willdurand/geocoder-bundle) 7 | [![Monthly Downloads](https://poser.pugx.org/willdurand/geocoder-bundle/d/monthly.png)](https://packagist.org/packages/willdurand/geocoder-bundle) 8 | [![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/geocoder-php/BazingaGeocoderBundle.svg?style=flat-square)](https://scrutinizer-ci.com/g/geocoder-php/BazingaGeocoderBundle) 9 | [![Quality Score](https://img.shields.io/scrutinizer/g/geocoder-php/BazingaGeocoderBundle.svg?style=flat-square)](https://scrutinizer-ci.com/g/geocoder-php/BazingaGeocoderBundle) 10 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) 11 | 12 | Integration of the [**Geocoder**](http://github.com/geocoder-php/Geocoder) library 13 | into Symfony. It integrates seamlessly with the Symfony profiler. You can 14 | check number of queries executed by each provider, total execution time 15 | and geocoding results. 16 | 17 | ![Example Toolbar](doc/toolbar.png) 18 | 19 | 20 | ![Example Profiler page](doc/profiler-page.png) 21 | 22 | 23 | 24 | Documentation 25 | ------------- 26 | 27 | For documentation, see: 28 | 29 | doc/ 30 | 31 | [Read the documentation](doc/index.md) 32 | 33 | 34 | Contributing 35 | ------------ 36 | 37 | See 38 | [CONTRIBUTING](CONTRIBUTING.md) 39 | file. 40 | 41 | 42 | Credits 43 | ------- 44 | 45 | * William Durand 46 | * Tobias Nyholm 47 | * [All contributors](https://github.com/geocoder-php/BazingaGeocoderBundle/contributors) 48 | 49 | 50 | Contributor Code of Conduct 51 | --------------------------- 52 | 53 | Please note that this project is released with a Contributor Code of Conduct. 54 | By participating in this project you agree to abide by its terms. 55 | 56 | 57 | License 58 | ------- 59 | 60 | This bundle is released under the MIT license. See the complete license [here](LICENSE). 61 | -------------------------------------------------------------------------------- /src/Validator/Constraint/AddressValidator.php: -------------------------------------------------------------------------------- 1 | 25 | */ 26 | final class AddressValidator extends ConstraintValidator 27 | { 28 | public function __construct( 29 | private readonly Provider $addressGeocoder, 30 | ) { 31 | } 32 | 33 | public function validate(mixed $value, Constraint $constraint): void 34 | { 35 | if (!$constraint instanceof Address) { 36 | throw new UnexpectedTypeException($constraint, Address::class); 37 | } 38 | 39 | if (null === $value || '' === $value) { 40 | return; 41 | } 42 | 43 | if (!is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) { 44 | throw new UnexpectedValueException($value, 'string'); 45 | } 46 | 47 | $value = (string) $value; 48 | 49 | try { 50 | $collection = $this->addressGeocoder->geocodeQuery(GeocodeQuery::create($value)); 51 | 52 | if ($collection->isEmpty()) { 53 | $this->buildViolation($constraint, $value); 54 | } 55 | } catch (Exception $e) { 56 | $this->buildViolation($constraint, $value); 57 | } 58 | } 59 | 60 | private function buildViolation(Address $constraint, string $address): void 61 | { 62 | $this->context->buildViolation($constraint->message) 63 | ->setParameter('{{ address }}', $this->formatValue($address)) 64 | ->setInvalidValue($address) 65 | ->setCode(Address::INVALID_ADDRESS_ERROR) 66 | ->addViolation(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/ProviderFactory/HereFactory.php: -------------------------------------------------------------------------------- 1 | Here::class, 'packageName' => 'geocoder-php/here-provider'], 25 | ]; 26 | 27 | /** 28 | * @param array{app_key: ?string, app_id: ?string, app_code: ?string, use_cit: bool, http_client: ?ClientInterface} $config 29 | */ 30 | protected function getProvider(array $config): Provider 31 | { 32 | if (empty($config['app_key']) && empty($config['app_id']) && empty($config['app_code'])) { 33 | throw new \InvalidArgumentException('No authentication key provided. Here requires app_key or app_code and app_id.'); 34 | } 35 | 36 | $httpClient = $config['http_client'] ?? $this->httpClient ?? Psr18ClientDiscovery::find(); 37 | 38 | if (!empty($config['app_key'])) { 39 | return Here::createUsingApiKey($httpClient, $config['app_key'], $config['use_cit']); 40 | } 41 | 42 | return new Here($httpClient, $config['app_id'], $config['app_code'], $config['use_cit']); 43 | } 44 | 45 | protected static function configureOptionResolver(OptionsResolver $resolver): void 46 | { 47 | $resolver->setDefaults([ 48 | 'http_client' => null, 49 | 'use_cit' => false, 50 | 'app_key' => null, 51 | 'app_id' => null, 52 | 'app_code' => null, 53 | ]); 54 | 55 | $resolver->setAllowedTypes('http_client', ['object', 'null']); 56 | $resolver->setAllowedTypes('app_key', ['string', 'null']); 57 | $resolver->setAllowedTypes('app_id', ['string', 'null']); 58 | $resolver->setAllowedTypes('app_code', ['string', 'null']); 59 | $resolver->setAllowedTypes('use_cit', ['bool', 'false']); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Command/GeocodeCommand.php: -------------------------------------------------------------------------------- 1 | 26 | */ 27 | #[AsCommand( 28 | name: 'geocoder:geocode', 29 | description: 'Geocode an address or an IP address', 30 | )] 31 | final class GeocodeCommand extends Command 32 | { 33 | public function __construct( 34 | private readonly ProviderAggregator $geocoder, 35 | ) { 36 | parent::__construct(); 37 | } 38 | 39 | protected function configure(): void 40 | { 41 | $this 42 | ->addArgument('address', InputArgument::REQUIRED, 'The address') 43 | ->addOption('provider', null, InputOption::VALUE_OPTIONAL) 44 | ->setHelp(<<<'HELP' 45 | The geocoder:geocoder command will fetch the latitude 46 | and longitude from the given address. 47 | 48 | You can force a provider with the "provider" option. 49 | 50 | php bin/console geocoder:geocoder "Eiffel Tower" --provider=yahoo 51 | HELP); 52 | } 53 | 54 | protected function execute(InputInterface $input, OutputInterface $output): int 55 | { 56 | if ($input->getOption('provider')) { 57 | $this->geocoder->using($input->getOption('provider')); 58 | } 59 | 60 | $results = $this->geocoder->geocodeQuery(GeocodeQuery::create($input->getArgument('address'))); 61 | $data = $results->first()->toArray(); 62 | 63 | $max = 0; 64 | 65 | foreach ($data as $key => $value) { 66 | $length = strlen($key); 67 | if ($max < $length) { 68 | $max = $length; 69 | } 70 | } 71 | 72 | $max += 2; 73 | 74 | foreach ($data as $key => $value) { 75 | $key = $this->humanize($key); 76 | 77 | $output->writeln(sprintf( 78 | '%s: %s', 79 | str_pad($key, $max, ' ', STR_PAD_RIGHT), 80 | is_array($value) ? json_encode($value) : $value 81 | )); 82 | } 83 | 84 | return 0; 85 | } 86 | 87 | private function humanize(string $text): string 88 | { 89 | $text = preg_replace('/([A-Z][a-z]+)|([A-Z][A-Z]+)|([^A-Za-z ]+)/', ' \1', $text); 90 | 91 | return ucfirst(strtolower($text)); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Plugin/ProfilingPlugin.php: -------------------------------------------------------------------------------- 1 | 26 | */ 27 | final class ProfilingPlugin implements Plugin 28 | { 29 | /** 30 | * @var list 31 | */ 32 | private array $queries = []; 33 | 34 | /** 35 | * @param non-empty-string $name service id of the provider 36 | */ 37 | public function __construct( 38 | private readonly string $name, 39 | ) { 40 | } 41 | 42 | public function handleQuery(Query $query, callable $next, callable $first): Promise 43 | { 44 | $startTime = microtime(true); 45 | 46 | return $next($query)->then(function (Collection $result) use ($query, $startTime) { 47 | $duration = (microtime(true) - $startTime) * 1000; 48 | $this->logQuery($query, $duration, $result); 49 | 50 | return $result; 51 | }, function (Exception $exception) use ($query, $startTime) { 52 | $duration = (microtime(true) - $startTime) * 1000; 53 | $this->logQuery($query, $duration, $exception); 54 | 55 | throw $exception; 56 | }); 57 | } 58 | 59 | private function logQuery(Query $query, float $duration, mixed $result = null): void 60 | { 61 | if ($query instanceof GeocodeQuery) { 62 | $queryString = $query->getText(); 63 | } elseif ($query instanceof ReverseQuery) { 64 | $queryString = sprintf('(%s, %s)', $query->getCoordinates()->getLongitude(), $query->getCoordinates()->getLatitude()); 65 | } else { 66 | throw new LogicException('First parameter to ProfilingProvider::logQuery must be a Query'); 67 | } 68 | 69 | $this->queries[] = [ 70 | 'query' => $query, 71 | 'queryString' => $queryString, 72 | 'duration' => $duration, 73 | 'providerName' => $this->getName(), 74 | 'result' => $result, 75 | 'resultCount' => $result instanceof Collection ? $result->count() : 0, 76 | ]; 77 | } 78 | 79 | /** 80 | * @return list 81 | */ 82 | public function getQueries(): array 83 | { 84 | return $this->queries; 85 | } 86 | 87 | /** 88 | * @return non-empty-string 89 | */ 90 | public function getName(): string 91 | { 92 | return $this->name; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/ProviderFactory/GeoIP2Factory.php: -------------------------------------------------------------------------------- 1 | GeoIP2::class, 'packageName' => 'geocoder-php/geoip2-provider'], 28 | ]; 29 | 30 | /** 31 | * @param array{provider: string, provider_service: ?ProviderInterface, model: string, user_id: string|int|null, license_key: string|null, locales: list, webservice_options: array, database_filename: ?string} $config 32 | * 33 | * @throws InvalidDatabaseException 34 | */ 35 | protected function getProvider(array $config): Provider 36 | { 37 | $provider = $config['provider']; 38 | if ('webservice' === $provider) { 39 | $userId = isset($config['user_id']) ? (int) $config['user_id'] : null; 40 | 41 | $provider = new Client($userId, $config['license_key'], $config['locales'], $config['webservice_options']); 42 | } elseif ('database' === $provider) { 43 | $provider = new Reader($config['database_filename'], $config['locales']); 44 | } else { 45 | $provider = $config['provider_service']; 46 | } 47 | 48 | $adapter = new GeoIP2Adapter($provider, $config['model']); 49 | 50 | return new GeoIP2($adapter); 51 | } 52 | 53 | protected static function configureOptionResolver(OptionsResolver $resolver): void 54 | { 55 | $resolver->setDefaults([ 56 | 'model' => GeoIP2Adapter::GEOIP2_MODEL_CITY, 57 | 'database_filename' => null, 58 | 'user_id' => null, 59 | 'license_key' => null, 60 | 'webservice_options' => [], 61 | 'locales' => ['en'], 62 | 'provider_service' => null, 63 | ]); 64 | 65 | $resolver->setRequired('provider'); 66 | $resolver->setAllowedTypes('provider', ['string']); 67 | $resolver->setAllowedTypes('provider_service', [ProviderInterface::class, 'null']); 68 | $resolver->setAllowedTypes('model', ['string']); 69 | $resolver->setAllowedTypes('user_id', ['string', 'int', 'null']); 70 | $resolver->setAllowedTypes('license_key', ['string', 'null']); 71 | $resolver->setAllowedTypes('locales', ['array']); 72 | $resolver->setAllowedTypes('webservice_options', ['array']); 73 | $resolver->setAllowedTypes('database_filename', ['string', 'null']); 74 | 75 | $resolver->setAllowedValues('model', [GeoIP2Adapter::GEOIP2_MODEL_CITY, GeoIP2Adapter::GEOIP2_MODEL_COUNTRY]); 76 | $resolver->setAllowedValues('provider', ['webservice', 'database', 'service']); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/ProviderFactory/AbstractFactory.php: -------------------------------------------------------------------------------- 1 | 25 | */ 26 | abstract class AbstractFactory implements ProviderFactoryInterface 27 | { 28 | /** 29 | * @var list 30 | */ 31 | protected static array $dependencies = []; 32 | 33 | public function __construct( 34 | protected ?ClientInterface $httpClient = null, 35 | ) { 36 | } 37 | 38 | /** 39 | * @param array $config 40 | */ 41 | abstract protected function getProvider(array $config): Provider; 42 | 43 | public function createProvider(array $options = []): Provider 44 | { 45 | $this->verifyDependencies(); 46 | 47 | $resolver = new OptionsResolver(); 48 | static::configureOptionResolver($resolver); 49 | $config = $resolver->resolve($options); 50 | 51 | return $this->getProvider($config); 52 | } 53 | 54 | public static function validate(array $options, string $providerName): void 55 | { 56 | static::verifyDependencies(); 57 | 58 | $resolver = new OptionsResolver(); 59 | static::configureOptionResolver($resolver); 60 | 61 | try { 62 | $resolver->resolve($options); 63 | } catch (\Exception $e) { 64 | $message = sprintf( 65 | 'Error while configure provider "%s". Verify your configuration at "bazinga_geocoder.providers.%s.options". %s', 66 | $providerName, 67 | $providerName, 68 | $e->getMessage() 69 | ); 70 | 71 | throw new InvalidConfigurationException($message, $e->getCode(), $e); 72 | } 73 | } 74 | 75 | /** 76 | * Make sure that we have the required class and throw and exception if we don't. 77 | * 78 | * @throws \LogicException 79 | */ 80 | protected static function verifyDependencies(): void 81 | { 82 | foreach (static::$dependencies as $dependency) { 83 | if (!class_exists($dependency['requiredClass'])) { 84 | throw new \LogicException(sprintf('You must install the "%s" package to use the "%s" factory.', $dependency['packageName'], static::class)); 85 | } 86 | } 87 | } 88 | 89 | /** 90 | * By default, we do not have any options to configure. A factory should override this function and configure 91 | * the options resolver. 92 | */ 93 | protected static function configureOptionResolver(OptionsResolver $resolver): void 94 | { 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Mapping/Driver/AttributeDriver.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | final class AttributeDriver implements DriverInterface 25 | { 26 | public function isGeocodeable(object $object): bool 27 | { 28 | $reflection = self::getReflection($object); 29 | 30 | return [] !== $reflection->getAttributes(Attributes\Geocodeable::class); 31 | } 32 | 33 | /** 34 | * @throws MappingException|\ReflectionException 35 | */ 36 | public function loadMetadataFromObject(object $object): ClassMetadata 37 | { 38 | $reflection = self::getReflection($object); 39 | 40 | $attributes = $reflection->getAttributes(Attributes\Geocodeable::class); 41 | 42 | if ([] === $attributes) { 43 | throw new MappingException(sprintf('The class "%s" is not geocodeable', $object::class)); 44 | } 45 | 46 | $args = ['provider' => $attributes[0]->newInstance()->provider]; 47 | 48 | foreach ($reflection->getProperties() as $property) { 49 | foreach ($property->getAttributes() as $attribute) { 50 | if (Attributes\Latitude::class === $attribute->getName()) { 51 | $args['latitudeProperty'] = $property; 52 | } elseif (Attributes\Longitude::class === $attribute->getName()) { 53 | $args['longitudeProperty'] = $property; 54 | } elseif (Attributes\Address::class === $attribute->getName()) { 55 | $args['addressProperty'] = $property; 56 | } 57 | } 58 | } 59 | 60 | foreach ($reflection->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { 61 | if ([] !== $method->getAttributes(Attributes\Address::class)) { 62 | if (0 !== $method->getNumberOfRequiredParameters()) { 63 | throw new MappingException('You can not use a method requiring parameters with #[Address] attribute!'); 64 | } 65 | 66 | $args['addressGetter'] = $method; 67 | } 68 | } 69 | 70 | return new ClassMetadata(...$args); 71 | } 72 | 73 | /** 74 | * @template T of object 75 | * 76 | * @param T $object 77 | * 78 | * @return \ReflectionClass 79 | * 80 | * @throws \ReflectionException 81 | */ 82 | private static function getReflection(object $object): \ReflectionClass 83 | { 84 | if (class_exists(ClassUtils::class)) { 85 | /** @var \ReflectionClass */ 86 | return ClassUtils::newReflectionObject($object); 87 | } 88 | 89 | if (PHP_VERSION_ID < 80400) { 90 | /** @var \ReflectionClass */ 91 | return new \ReflectionClass(DefaultProxyClassNameResolver::getClass($object)); 92 | } 93 | 94 | /** @var \ReflectionClass */ 95 | return new \ReflectionClass($object); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/DataCollector/GeocoderDataCollector.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | final class GeocoderDataCollector extends DataCollector 25 | { 26 | /** 27 | * @var ProfilingPlugin[] 28 | */ 29 | private array $instances = []; 30 | 31 | public function __construct() 32 | { 33 | $this->data['queries'] = []; 34 | $this->data['providers'] = []; 35 | } 36 | 37 | public function reset(): void 38 | { 39 | $this->instances = []; 40 | $this->data['queries'] = []; 41 | $this->data['providers'] = []; 42 | } 43 | 44 | public function collect(Request $request, Response $response, ?\Throwable $exception = null): void 45 | { 46 | if ([] !== $this->data['queries']) { 47 | // To avoid collection more that once. 48 | return; 49 | } 50 | 51 | $instances = $this->instances; 52 | 53 | foreach ($instances as $instance) { 54 | foreach ($instance->getQueries() as $query) { 55 | $query['query'] = $this->cloneVar($query['query']); 56 | $query['result'] = $this->cloneVar($query['result']); 57 | $this->data['queries'][] = $query; 58 | } 59 | } 60 | } 61 | 62 | /** 63 | * Returns an array of collected requests. 64 | * 65 | * @return list 66 | */ 67 | public function getQueries(): array 68 | { 69 | return $this->data['queries']; 70 | } 71 | 72 | /** 73 | * Returns the execution time of all collected requests in seconds. 74 | */ 75 | public function getTotalDuration(): float 76 | { 77 | $time = 0; 78 | foreach ($this->data['queries'] as $command) { 79 | $time += $command['duration']; 80 | } 81 | 82 | return $time; 83 | } 84 | 85 | /** 86 | * @return string[] 87 | */ 88 | public function getProviders(): array 89 | { 90 | return $this->data['providers']; 91 | } 92 | 93 | /** 94 | * @return list 95 | */ 96 | public function getProviderQueries(string $provider): array 97 | { 98 | return array_filter($this->data['queries'], static function ($data) use ($provider) { 99 | return $data['providerName'] === $provider; 100 | }); 101 | } 102 | 103 | public function addInstance(ProfilingPlugin $instance): void 104 | { 105 | $this->instances[] = $instance; 106 | $this->data['providers'][] = $instance->getName(); 107 | } 108 | 109 | public function getName(): string 110 | { 111 | return 'geocoder'; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "willdurand/geocoder-bundle", 3 | "description": "Integration of Geocoder into Symfony", 4 | "keywords": [ 5 | "geocoding", 6 | "geocoder" 7 | ], 8 | "type": "symfony-bundle", 9 | "license": "MIT", 10 | "authors": [ 11 | { 12 | "name": "William Durand", 13 | "email": "will+git@drnd.me" 14 | } 15 | ], 16 | "require": { 17 | "php": "^8.1", 18 | "geocoder-php/plugin": "^1.6", 19 | "php-http/discovery": "^1.20", 20 | "symfony/console": "^6.4 || ^7.0 || ^8.0", 21 | "symfony/framework-bundle": "^6.4 || ^7.0 || ^8.0", 22 | "symfony/options-resolver": "^6.4 || ^7.0 || ^8.0", 23 | "willdurand/geocoder": "^4.6|^5.0" 24 | }, 25 | "require-dev": { 26 | "doctrine/doctrine-bundle": "^2.18 || ^3.0", 27 | "doctrine/orm": "^2.20 || ^3.0", 28 | "fakerphp/faker": "^1.24", 29 | "friendsofphp/php-cs-fixer": "^3.92", 30 | "geocoder-php/algolia-places-provider": "^0.5", 31 | "geocoder-php/arcgis-online-provider": "^4.5", 32 | "geocoder-php/bing-maps-provider": "^4.4", 33 | "geocoder-php/cache-provider": "^4.5.0", 34 | "geocoder-php/chain-provider": "^4.6", 35 | "geocoder-php/free-geoip-provider": "^4.6", 36 | "geocoder-php/geo-plugin-provider": "^4.4", 37 | "geocoder-php/geoip2-provider": "^4.4", 38 | "geocoder-php/geonames-provider": "^4.5", 39 | "geocoder-php/google-maps-places-provider": "^1.5", 40 | "geocoder-php/google-maps-provider": "^4.8", 41 | "geocoder-php/here-provider": "^0.8", 42 | "geocoder-php/host-ip-provider": "^4.5", 43 | "geocoder-php/ip-info-db-provider": "^4.4", 44 | "geocoder-php/ip-info-provider": "^0.5", 45 | "geocoder-php/ipstack-provider": "^0.5", 46 | "geocoder-php/locationiq-provider": "^1.5", 47 | "geocoder-php/mapbox-provider": "^1.5", 48 | "geocoder-php/mapquest-provider": "^4.4", 49 | "geocoder-php/maxmind-provider": "^4.5", 50 | "geocoder-php/nominatim-provider": "^5.8", 51 | "geocoder-php/open-cage-provider": "^4.7", 52 | "geocoder-php/openrouteservice-provider": "^1.4", 53 | "geocoder-php/pickpoint-provider": "^4.4", 54 | "geocoder-php/tomtom-provider": "^4.5", 55 | "geocoder-php/yandex-provider": "^4.6", 56 | "nyholm/nsa": "^1.3", 57 | "nyholm/psr7": "^1.8", 58 | "nyholm/symfony-bundle-test": "^3.1", 59 | "phpstan/phpstan": "^2.1", 60 | "psr/http-client": "^1.0", 61 | "psr/simple-cache": "^1.0 || ^2.0 || ^3.0", 62 | "symfony/cache": "^6.4 || ^7.0 || ^8.0", 63 | "symfony/config": "^6.4 || ^7.0 || ^8.0", 64 | "symfony/http-client": "^6.4 || ^7.0 || ^8.0", 65 | "symfony/phpunit-bridge": "^6.4 || ^7.0 || ^8.0", 66 | "symfony/validator": "^6.4 || ^7.0 || ^8.0", 67 | "symfony/var-exporter": "^6.4 || ^7.0 || ^8.0", 68 | "symfony/yaml": "^6.4 || ^7.0 || ^8.0" 69 | }, 70 | "autoload": { 71 | "psr-4": { 72 | "Bazinga\\GeocoderBundle\\": "src/" 73 | } 74 | }, 75 | "autoload-dev": { 76 | "psr-4": { 77 | "Bazinga\\GeocoderBundle\\Tests\\": "tests" 78 | } 79 | }, 80 | "scripts": { 81 | "test": "SYMFONY_DEPRECATIONS_HELPER=ignoreFile='./tests/baseline-ignore' vendor/bin/simple-phpunit --testsuite main", 82 | "phpstan": "vendor/bin/phpstan analyse --memory-limit=1024m", 83 | "phpstan-baseline": "vendor/bin/phpstan analyse --generate-baseline phpstan-baseline.php --memory-limit=1024m" 84 | }, 85 | "config": { 86 | "allow-plugins": { 87 | "symfony/flex": true, 88 | "php-http/discovery": false 89 | }, 90 | "sort-packages": true 91 | }, 92 | "minimum-stability": "dev", 93 | "prefer-stable": true, 94 | "extra": { 95 | "branch-alias": { 96 | "dev-master": "6.0-dev" 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Doctrine/ORM/GeocodeEntityListener.php: -------------------------------------------------------------------------------- 1 | 25 | * @author Pierre du Plessis 26 | */ 27 | final class GeocodeEntityListener 28 | { 29 | /** 30 | * @param ServiceLocator $providerLocator 31 | * @param DriverInterface $driver 32 | */ 33 | public function __construct( 34 | private readonly ServiceLocator $providerLocator, 35 | private readonly DriverInterface $driver, 36 | ) { 37 | } 38 | 39 | public function onFlush(OnFlushEventArgs $args): void 40 | { 41 | $em = $args->getObjectManager(); 42 | $uow = $em->getUnitOfWork(); 43 | 44 | foreach ($uow->getScheduledEntityInsertions() as $entity) { 45 | if (!$this->driver->isGeocodeable($entity)) { 46 | continue; 47 | } 48 | 49 | $metadata = $this->driver->loadMetadataFromObject($entity); 50 | 51 | $this->geocodeEntity($metadata, $entity); 52 | 53 | $uow->recomputeSingleEntityChangeSet( 54 | $em->getClassMetadata($entity::class), 55 | $entity, 56 | ); 57 | } 58 | 59 | foreach ($uow->getScheduledEntityUpdates() as $entity) { 60 | if (!$this->driver->isGeocodeable($entity)) { 61 | continue; 62 | } 63 | 64 | $metadata = $this->driver->loadMetadataFromObject($entity); 65 | 66 | if (!$this->shouldGeocode($metadata, $uow, $entity)) { 67 | continue; 68 | } 69 | 70 | $this->geocodeEntity($metadata, $entity); 71 | 72 | $uow->recomputeSingleEntityChangeSet( 73 | $em->getClassMetadata($entity::class), 74 | $entity, 75 | ); 76 | } 77 | } 78 | 79 | private function geocodeEntity(ClassMetadata $metadata, object $entity): void 80 | { 81 | if (null !== $metadata->addressGetter) { 82 | $address = $metadata->addressGetter->invoke($entity); 83 | } elseif (null !== $metadata->addressProperty) { 84 | $address = $metadata->addressProperty->getValue($entity); 85 | } else { 86 | $address = ''; 87 | } 88 | 89 | if (!is_string($address) && !$address instanceof \Stringable) { 90 | return; 91 | } 92 | 93 | $addressString = (string) $address; 94 | if ('' === $addressString) { 95 | return; 96 | } 97 | 98 | $serviceId = \sprintf('bazinga_geocoder.provider.%s', $metadata->provider); 99 | 100 | if (!$this->providerLocator->has($serviceId)) { 101 | throw new \RuntimeException(\sprintf('The provider "%s" is invalid for object "%s".', $metadata->provider, $entity::class)); 102 | } 103 | 104 | $results = $this->providerLocator->get($serviceId)->geocodeQuery(GeocodeQuery::create($addressString)); 105 | 106 | if (!$results->isEmpty()) { 107 | $result = $results->first(); 108 | $metadata->latitudeProperty?->setValue($entity, $result->getCoordinates()->getLatitude()); 109 | $metadata->longitudeProperty?->setValue($entity, $result->getCoordinates()->getLongitude()); 110 | } 111 | } 112 | 113 | private function shouldGeocode(ClassMetadata $metadata, UnitOfWork $unitOfWork, object $entity): bool 114 | { 115 | if (null !== $metadata->addressGetter) { 116 | return true; 117 | } 118 | 119 | $changeSet = $unitOfWork->getEntityChangeSet($entity); 120 | 121 | return isset($changeSet[$metadata->addressProperty?->getName() ?? '']); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /templates/Collector/geocoder.html.twig: -------------------------------------------------------------------------------- 1 | {% extends '@WebProfiler/Profiler/layout.html.twig' %} 2 | 3 | {% block toolbar %} 4 | {% set queryLabel = collector.queries|length == 1 ? 'query' : 'queries' %} 5 | {% if collector.queries|length > 0 %} 6 | {% set icon %} 7 | {{ include('@BazingaGeocoder/Collector/icon.svg') }} 8 | {{ collector.queries|length }} 9 | {{ queryLabel }} in 10 | {{ collector.totalDuration|number_format }} 11 | ms 12 | {% endset %} 13 | {% set text %} 14 |
15 | {{ collector.queries|length }} {{ queryLabel }} 16 |
17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | {% for query in collector.queries %} 28 | 29 | 30 | 31 | 32 | 33 | {% endfor %} 34 | 35 |
ProviderQueryTime
{{ query.providerName }}{{ query.queryString }}{{ query.duration == 0 ? 'n/a' : query.duration|number_format ~ ' ms'}}
36 |
37 | {% endset %} 38 | 39 | {% include '@WebProfiler/Profiler/toolbar_item.html.twig' with { 'link': profiler_url } %} 40 | {% endif %} 41 | {% endblock %} 42 | 43 | {% block menu %} 44 | {# This left-hand menu appears when using the full-screen profiler. #} 45 | 46 | 47 | {{ include('@BazingaGeocoder/Collector/icon.svg') }} 48 | 49 | Geocoder 50 | 51 | {% endblock %} 52 | 53 | {% block panel %} 54 | 59 |

Geocoder

60 | 61 |
62 | {% for provider in collector.providers %} 63 |
64 |

{{ provider }} {{ collector.providerQueries(provider)|length }}

65 | 66 |
67 |

68 | These queries are executed by a provider named "{{ provider }}". 69 |

70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | {% for query in collector.providerQueries(provider) %} 83 | 84 | 87 | 92 | 97 | 109 | 112 | 113 | 114 | 115 | 116 | 117 | {% if query.result.message is defined %} 118 | 119 | {% else %} 120 | 121 | {% endif %} 122 | 123 | 124 | {% endfor %} 125 | 126 |
QueryLocaleResultDuration
85 | # {{ loop.index }} 86 | 88 | 89 | {{ query.queryString }} 90 | 91 | 93 | 94 | {{ query.query.locale is not null ? query.query.locale : 'null' }} 95 | 96 | 98 | 99 | {% if query.result.message is defined %} 100 | Exception 101 | {% else %} 102 | {{ query.resultCount }} Result(s) 103 | {% endif %} 104 | 105 | 108 | 110 | {{ query.duration|number_format }} ms 111 |
{{ profiler_dump(query.query, maxDepth=1) }}{{ profiler_dump(query.result, maxDepth=1) }}{{ profiler_dump(query.result, maxDepth=3) }}
127 | 128 |
129 |
130 | {% else %} 131 |
132 |

No queries were executed.

133 |
134 | {% endfor %} 135 |
136 | {% endblock %} 137 | -------------------------------------------------------------------------------- /src/DependencyInjection/Configuration.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | final class Configuration implements ConfigurationInterface 23 | { 24 | public function __construct( 25 | private readonly bool $debug, 26 | ) { 27 | } 28 | 29 | /** 30 | * Generates the configuration tree builder. 31 | */ 32 | public function getConfigTreeBuilder(): TreeBuilder 33 | { 34 | $treeBuilder = new TreeBuilder('bazinga_geocoder'); 35 | $rootNode = $treeBuilder->getRootNode(); 36 | assert($rootNode instanceof ArrayNodeDefinition); 37 | 38 | $rootNode 39 | ->children() 40 | ->append($this->getProvidersNode()) 41 | ->arrayNode('profiling') 42 | ->addDefaultsIfNotSet() 43 | ->treatFalseLike(['enabled' => false]) 44 | ->treatTrueLike(['enabled' => true]) 45 | ->treatNullLike(['enabled' => $this->debug]) 46 | ->info('Extend the debug profiler with information about requests.') 47 | ->children() 48 | ->booleanNode('enabled') 49 | ->info('Turn the toolbar on or off. Defaults to kernel debug mode.') 50 | ->defaultValue($this->debug) 51 | ->end() 52 | ->end() 53 | ->end() 54 | ->arrayNode('fake_ip') 55 | ->beforeNormalization() 56 | ->ifString() 57 | ->then(function ($value) { 58 | return ['ip' => $value]; 59 | }) 60 | ->end() 61 | ->canBeEnabled() 62 | ->children() 63 | ->scalarNode('local_ip') 64 | ->defaultValue('127.0.0.1') 65 | ->end() 66 | ->scalarNode('ip')->defaultNull()->end() 67 | ->booleanNode('use_faker')->defaultFalse()->end() 68 | ->end() 69 | ->end() 70 | ->arrayNode('orm') 71 | ->addDefaultsIfNotSet() 72 | ->treatFalseLike(['enabled' => false]) 73 | ->treatTrueLike(['enabled' => true]) 74 | ->treatNullLike(['enabled' => true]) 75 | ->children() 76 | ->booleanNode('enabled') 77 | ->info('Turn the Doctrine ORM listener on or off.') 78 | ->defaultValue(false) 79 | ->end() 80 | ->end() 81 | ->end(); 82 | 83 | return $treeBuilder; 84 | } 85 | 86 | private function getProvidersNode(): ArrayNodeDefinition 87 | { 88 | $treeBuilder = new TreeBuilder('providers'); 89 | $rootNode = $treeBuilder->getRootNode(); 90 | assert($rootNode instanceof ArrayNodeDefinition); 91 | 92 | $rootNode 93 | ->requiresAtLeastOneElement() 94 | ->useAttributeAsKey('name') 95 | ->arrayPrototype() 96 | ->fixXmlConfig('plugin') 97 | ->children() 98 | ->scalarNode('factory')->isRequired()->cannotBeEmpty()->end() 99 | ->variableNode('options')->defaultValue([])->end() 100 | ->scalarNode('cache')->defaultNull()->end() 101 | ->scalarNode('cache_lifetime')->defaultNull()->end() 102 | ->scalarNode('cache_precision') 103 | ->defaultNull() 104 | ->info('Precision of the coordinates to cache.') 105 | ->end() 106 | ->scalarNode('limit')->defaultNull()->end() 107 | ->scalarNode('locale')->defaultNull()->end() 108 | ->scalarNode('logger')->defaultNull()->end() 109 | ->arrayNode('aliases') 110 | ->scalarPrototype()->end() 111 | ->end() 112 | ->append($this->createClientPluginNode()) 113 | ->end() 114 | ->end(); 115 | 116 | return $rootNode; 117 | } 118 | 119 | /** 120 | * Create plugin node of a client. 121 | */ 122 | private function createClientPluginNode(): ArrayNodeDefinition 123 | { 124 | $treeBuilder = new TreeBuilder('plugins'); 125 | $rootNode = $treeBuilder->getRootNode(); 126 | assert($rootNode instanceof ArrayNodeDefinition); 127 | 128 | $pluginList = $rootNode 129 | ->info('A list of plugin service ids. The order is important.') 130 | ->arrayPrototype() 131 | ; 132 | $pluginList 133 | // support having just a service id in the list 134 | ->beforeNormalization() 135 | ->always(function ($plugin) { 136 | if (is_string($plugin)) { 137 | return [ 138 | 'reference' => [ 139 | 'enabled' => true, 140 | 'id' => $plugin, 141 | ], 142 | ]; 143 | } 144 | 145 | return $plugin; 146 | }) 147 | ->end() 148 | ; 149 | 150 | $pluginList 151 | ->children() 152 | ->arrayNode('reference') 153 | ->canBeEnabled() 154 | ->info('Reference to a plugin service') 155 | ->children() 156 | ->scalarNode('id') 157 | ->info('Service id of a plugin') 158 | ->isRequired() 159 | ->cannotBeEmpty() 160 | ->end() 161 | ->end() 162 | ->end() 163 | ->end() 164 | ->end(); 165 | 166 | return $rootNode; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | The changelog describes what have been "Added", "Changed", "Removed" or "Fixed" between versions. 4 | 5 | ## Version 5.19.0 6 | 7 | ### Changed 8 | 9 | - Allow PSR-18 client 10 | - Updated Geocoder Provider dependency requirements to the latest versions 11 | - Deprecate `httplug_client` option in most provider factories in favour of `http_client`. 12 | 13 | ### Fixed 14 | 15 | - Load fakeip plugin before cache 16 | 17 | ## Version 5.18.0 18 | 19 | ### Added 20 | 21 | - Add OpenRouteService factory 22 | 23 | ## Version 5.17.0 24 | 25 | ### Changed 26 | 27 | - Updated PHP requirement to ^7.4 28 | - Updated minimum required version for some dependencies 29 | - Deprecate `Address::$errorNames` in favour of `Address::ERROR_NAMES`. 30 | 31 | ## Version 5.16.2 32 | 33 | ### Fixed 34 | 35 | - Fix deprecations with Symfony 6.1 36 | 37 | ## Version 5.16.1 38 | 39 | ### Fixed 40 | 41 | - Allow Symfony 6 42 | 43 | ## Version 5.16.0 44 | 45 | ### Added 46 | 47 | - Allow Address validation constraint as php8 attribute usage 48 | - Allow fake IP works for all IPs 49 | 50 | ## Version 5.15.0 51 | 52 | ### Added 53 | 54 | - Add locationIQ provider 55 | - Add support for PHP 8 attributes 56 | 57 | ### Fixed 58 | 59 | - Fix annotation metadata driver with Doctrine proxies 60 | - Fix ORM tests 61 | 62 | ### Removed 63 | 64 | - Remove unnecessary version check for registerAliasForArgument 65 | 66 | ## Version 5.14.0 67 | 68 | ### Added 69 | 70 | - Add support for PHP 8.0 71 | 72 | ### Changed 73 | 74 | - Upgrade dependencies to up to date versions 75 | 76 | ### Removed 77 | 78 | - Remove PHP 7.2 support 79 | - Remove Symfony 3.4 support 80 | 81 | ## Version 5.13.0 82 | 83 | ### Added 84 | 85 | - Add support for api-key based authentication in here factory 86 | 87 | ## Version 5.12.0 88 | 89 | ### Added 90 | 91 | - Allow configuring local IP when using FakeIpPlugin 92 | 93 | ### Changed 94 | 95 | - Skip geocoding if address has not changed 96 | 97 | ## Version 5.11.0 98 | 99 | ### Added 100 | 101 | - Allow an HTTP client to be injected via the constructor of providers 102 | - Add MapboxFactory to docs 103 | 104 | ### Fixed 105 | 106 | - replace empty() check 107 | 108 | ## Version 5.10.0 109 | 110 | ### Added 111 | 112 | - Add MapboxFactory 113 | - Add GoogleMapsPlacesFactory 114 | 115 | ### Fixed 116 | 117 | - Fix tests/deprecations and Symfony 5 support 118 | 119 | ## Version 5.9.2 120 | 121 | ### Fixed 122 | 123 | - Only trigger provider deprecation notices when they are explicitly configured 124 | 125 | ## Version 5.9.1 126 | 127 | ### Fixed 128 | 129 | - Fix extension cannot replace non-defined argument 130 | 131 | ## Version 5.9.0 132 | 133 | ### Added 134 | 135 | - Auto tag dumpers with `bazinga_geocoder.dumper` 136 | - Add api_key for the YandexFactory 137 | - Add option to use Faker library for FakeIpPlugin 138 | 139 | ## Version 5.8.0 140 | 141 | ### Added 142 | 143 | - Add logger support to ChainFactory 144 | 145 | ### Changed 146 | 147 | - Update FreeGeoIpFactory with freegeoip.app 148 | 149 | ## Version 5.7.0 150 | 151 | ### Added 152 | 153 | - Add Algolia provider factory 154 | 155 | ### Changed 156 | 157 | - Skip empty address values in listener 158 | - Allow cache lifetime as null 159 | - Update dev dependencies 160 | - Make sure we run action on PRs 161 | 162 | ## Version 5.6.0 163 | 164 | ### Added 165 | 166 | - Added missing step in Doctrine documentation 167 | - Address annotation can be used over a getter 168 | - Add docs about autowiring 169 | - Integrate phpstan at level 2 170 | - Adding github actions 171 | 172 | ### Changed 173 | 174 | - Deprecate MapzenFactory 175 | - Deprecate GeoIPsFactory 176 | - Rename Changelog.md to CHANGELOG.md 177 | 178 | ### Removed 179 | 180 | - Remove useless phpdocs 181 | 182 | ## Version 5.5.0 183 | 184 | ### Added 185 | 186 | - Add autowiring bindings by Provider interface + providerName 187 | - Exposes Here provider to the bundle 188 | 189 | ### Fixed 190 | 191 | - Add missing tag for AddressValidator constraint 192 | 193 | ### Changed 194 | 195 | - Update readme 196 | - Fix method name 197 | - Drop unmaintained Symfony versions support 198 | 199 | ## Version 5.4.0 200 | 201 | ### Added 202 | 203 | - Add address validator constraint 204 | 205 | ### Fixed 206 | 207 | - SF 4.2 Compliance 208 | - Fix another SF 4.2 deprecation 209 | - Doc fixes 210 | - Custom vendor location symfony 4 211 | 212 | ## Version 5.3.0 213 | 214 | ### Added 215 | 216 | - Support for Ipstack provider 217 | - Support for adding precision argument for the Cache plugin. 218 | 219 | ## Version 5.2.0 220 | 221 | ### Added 222 | 223 | - Support for Nominatim 5.0 224 | 225 | ### Fixed 226 | 227 | - Issue when defining plugins. 228 | - Fixed invalid HTML profiler details table. 229 | 230 | ## Version 5.1.2 231 | 232 | ### Fixed 233 | 234 | - Make sure commands not using the container. 235 | - Fixed issue with using custom factories. We do not validate custom factories better. 236 | - We are more relaxed in our requirements for HTTPClients. You may now use the option `http_client`. 237 | 238 | ## Version 5.1.1 239 | 240 | ### Fixed 241 | 242 | - Adding commands as services 243 | - Fixed twig paths for webprofiler 244 | 245 | ## Version 5.1.0 246 | 247 | ### Added 248 | 249 | - `Collector::clear` to be compatible with Symfony 4. 250 | - Added support for IpInfo. 251 | 252 | ## Version 5.0.0 253 | 254 | Version 5 does only support Symfony 3.3+ and PHP7. We dropped some complexity and added plenty of type hints. 255 | 256 | ### Added 257 | 258 | - Support for Geocoder 4.0 259 | - Provider factories 260 | - Support for plugins 261 | 262 | ### Changed 263 | 264 | - Namespace changed from `Bazinga\Bundle\GeocoderBundle` to `Bazinga\GeocoderBundle` 265 | - The "fake IP" feature does not change any environment or Symfony variables. 266 | - Configuration for providers has been changed. We now use factories. 267 | 268 | Before: 269 | 270 | ```yaml 271 | bazinga_geocoder: 272 | providers: 273 | bing_maps: 274 | api_key: "Foo" 275 | locale: 'sv' 276 | ``` 277 | After: 278 | 279 | ```yaml 280 | bazinga_geocoder: 281 | providers: 282 | acme: 283 | factory: "Bazinga\GeocoderBundle\ProviderFactory\BingMapsFactory" 284 | locale: 'sv' 285 | options: 286 | api_key: "foo" 287 | ``` 288 | 289 | ### Removed 290 | 291 | - `DumperManager` 292 | - `LoggableGeocoder`, use `LoggerPlugin` instead. 293 | - Configuration for default provider (`default_provider`) 294 | - `Bazinga\Bundle\GeocoderBundle\Provider\Cache` was removed, use `CachePlugin` instead. 295 | - All services IDs was removed except `bazinga_geocoder.geocoder` and `geocoder`. 296 | 297 | ## Version 4.1.0 298 | 299 | No changelog before this version 300 | -------------------------------------------------------------------------------- /src/DependencyInjection/BazingaGeocoderExtension.php: -------------------------------------------------------------------------------- 1 | . 39 | */ 40 | class BazingaGeocoderExtension extends Extension 41 | { 42 | /** 43 | * @param array $configs 44 | */ 45 | public function load(array $configs, ContainerBuilder $container): void 46 | { 47 | $processor = new Processor(); 48 | $configuration = $this->getConfiguration($configs, $container); 49 | $config = $processor->processConfiguration($configuration, $configs); 50 | 51 | $loader = new PhpFileLoader($container, new FileLocator(__DIR__.'/../../config')); 52 | $loader->load('services.php'); 53 | 54 | if (true === $config['profiling']['enabled']) { 55 | $loader->load('profiling.php'); 56 | } 57 | 58 | if (\array_key_exists('DoctrineBundle', $container->getParameter('kernel.bundles'))) { 59 | if (true === $config['orm']['enabled']) { 60 | $loader->load('orm.php'); 61 | } 62 | } elseif (true === $config['orm']['enabled']) { 63 | throw new \LogicException('Doctrine ORM listener cannot be enabled when `doctrine/doctrine-bundle` is not installed.'); 64 | } 65 | 66 | if ($config['fake_ip']['enabled']) { 67 | $definition = $container->getDefinition(FakeIpPlugin::class); 68 | $definition->replaceArgument(0, $config['fake_ip']['local_ip']); 69 | $definition->replaceArgument(1, $config['fake_ip']['ip']); 70 | $definition->replaceArgument(2, $config['fake_ip']['use_faker']); 71 | 72 | if ($config['fake_ip']['use_faker'] && !class_exists(Generator::class)) { 73 | throw new \LogicException('To enable this option, you must install fakerphp/faker package.'); 74 | } 75 | } else { 76 | $container->removeDefinition(FakeIpPlugin::class); 77 | } 78 | 79 | $this->loadProviders($container, $config); 80 | 81 | $container->registerForAutoconfiguration(Dumper::class) 82 | ->addTag('bazinga_geocoder.dumper'); 83 | } 84 | 85 | /** 86 | * @param array $config 87 | */ 88 | private function loadProviders(ContainerBuilder $container, array $config): void 89 | { 90 | foreach ($config['providers'] as $providerName => $providerConfig) { 91 | try { 92 | $factoryService = $container->getDefinition($providerConfig['factory']); 93 | $factoryClass = $factoryService->getClass() ?: $providerConfig['factory']; 94 | if (!$this->implementsProviderFactory($factoryClass)) { 95 | throw new \LogicException(sprintf('Provider factory "%s" must implement ProviderFactoryInterface', $providerConfig['factory'])); 96 | } 97 | // See if any option has a service reference 98 | $providerConfig['options'] = $this->findReferences($providerConfig['options']); 99 | $factoryClass::validate($providerConfig['options'], $providerName); 100 | } catch (ServiceNotFoundException) { 101 | // Assert: We are using a custom factory. If invalid config, it will be caught in FactoryValidatorPass 102 | $providerConfig['options'] = $this->findReferences($providerConfig['options']); 103 | FactoryValidatorPass::addFactoryServiceId($providerConfig['factory']); 104 | } 105 | 106 | $serviceId = 'bazinga_geocoder.provider.'.$providerName; 107 | $plugins = $this->configureProviderPlugins($container, $providerConfig, $serviceId); 108 | 109 | $def = $container->register($serviceId, PluginProvider::class) 110 | ->setFactory([PluginProviderFactory::class, 'createPluginProvider']) 111 | ->addArgument($plugins) 112 | ->addArgument(new Reference($providerConfig['factory'])) 113 | ->addArgument($providerConfig['options']); 114 | 115 | $def->addTag('bazinga_geocoder.provider'); 116 | foreach ($providerConfig['aliases'] as $alias) { 117 | $container->setAlias($alias, $serviceId); 118 | } 119 | 120 | $container->registerAliasForArgument($serviceId, Provider::class, "{$providerName}Geocoder"); 121 | } 122 | } 123 | 124 | /** 125 | * Configure plugins for a client. 126 | * 127 | * @param array $config 128 | * 129 | * @return list 130 | */ 131 | public function configureProviderPlugins(ContainerBuilder $container, array $config, string $providerServiceId): array 132 | { 133 | $plugins = []; 134 | foreach ($config['plugins'] as $plugin) { 135 | if ($plugin['reference']['enabled']) { 136 | $plugins[] = $plugin['reference']['id']; 137 | } 138 | } 139 | 140 | if ($container->has(FakeIpPlugin::class)) { 141 | $plugins[] = FakeIpPlugin::class; 142 | } 143 | 144 | if (isset($config['cache']) || isset($config['cache_lifetime']) || isset($config['cache_precision'])) { 145 | $cacheLifetime = isset($config['cache_lifetime']) ? (int) $config['cache_lifetime'] : null; 146 | 147 | if (null === $cacheServiceId = $config['cache']) { 148 | if (!$container->has('app.cache')) { 149 | throw new \LogicException('You need to specify a service for cache.'); 150 | } 151 | $cacheServiceId = 'app.cache'; 152 | } 153 | $plugins[] = $providerServiceId.'.cache'; 154 | $container->register($providerServiceId.'.cache', CachePlugin::class) 155 | ->setPublic(false) 156 | ->setArguments([new Reference($cacheServiceId), $cacheLifetime, $config['cache_precision']]); 157 | } 158 | 159 | if (isset($config['limit'])) { 160 | $plugins[] = $providerServiceId.'.limit'; 161 | $container->register($providerServiceId.'.limit', LimitPlugin::class) 162 | ->setPublic(false) 163 | ->setArguments([(int) $config['limit']]); 164 | } 165 | 166 | if (isset($config['locale'])) { 167 | $plugins[] = $providerServiceId.'.locale'; 168 | $container->register($providerServiceId.'.locale', LocalePlugin::class) 169 | ->setPublic(false) 170 | ->setArguments([$config['locale']]); 171 | } 172 | 173 | if (isset($config['logger'])) { 174 | $plugins[] = $providerServiceId.'.logger'; 175 | $container->register($providerServiceId.'.logger', LoggerPlugin::class) 176 | ->setPublic(false) 177 | ->setArguments([new Reference($config['logger'])]); 178 | } 179 | 180 | if ($container->has(GeocoderDataCollector::class)) { 181 | $plugins[] = $providerServiceId.'.profiler'; 182 | $container->register($providerServiceId.'.profiler', ProfilingPlugin::class) 183 | ->setPublic(false) 184 | ->setArguments([substr($providerServiceId, strlen('bazinga_geocoder.provider.'))]) 185 | ->addTag('bazinga_geocoder.profiling_plugin'); 186 | } 187 | 188 | return array_map(static fn (string $id) => new Reference($id), $plugins); 189 | } 190 | 191 | /** 192 | * @param array $config 193 | */ 194 | public function getConfiguration(array $config, ContainerBuilder $container): Configuration 195 | { 196 | /** @var bool $debug */ 197 | $debug = $container->getParameter('kernel.debug'); 198 | 199 | return new Configuration($debug); 200 | } 201 | 202 | /** 203 | * @param array $options 204 | * 205 | * @return array 206 | */ 207 | private function findReferences(array $options): array 208 | { 209 | foreach ($options as $key => $value) { 210 | if (is_array($value)) { 211 | $options[$key] = $this->findReferences($value); 212 | } elseif (str_ends_with((string) $key, '_service') || str_starts_with((string) $value, '@') || 'service' === $key) { 213 | $options[$key] = new Reference(ltrim($value, '@')); 214 | } 215 | } 216 | 217 | return $options; 218 | } 219 | 220 | private function implementsProviderFactory(mixed $factoryClass): bool 221 | { 222 | if (false === $interfaces = class_implements($factoryClass)) { 223 | return false; 224 | } 225 | 226 | return in_array(ProviderFactoryInterface::class, $interfaces, true); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /phpstan-baseline.php: -------------------------------------------------------------------------------- 1 | '#^Parameter \\#1 \\$name of method Geocoder\\\\ProviderAggregator\\:\\:using\\(\\) expects string, mixed given\\.$#', 8 | 'identifier' => 'argument.type', 9 | 'count' => 1, 10 | 'path' => __DIR__.'/src/Command/GeocodeCommand.php', 11 | ]; 12 | $ignoreErrors[] = [ 13 | 'message' => '#^Parameter \\#1 \\$string of function strtolower expects string, string\\|null given\\.$#', 14 | 'identifier' => 'argument.type', 15 | 'count' => 1, 16 | 'path' => __DIR__.'/src/Command/GeocodeCommand.php', 17 | ]; 18 | $ignoreErrors[] = [ 19 | 'message' => '#^Parameter \\#1 \\$text of static method Geocoder\\\\Query\\\\GeocodeQuery\\:\\:create\\(\\) expects string, mixed given\\.$#', 20 | 'identifier' => 'argument.type', 21 | 'count' => 1, 22 | 'path' => __DIR__.'/src/Command/GeocodeCommand.php', 23 | ]; 24 | $ignoreErrors[] = [ 25 | 'message' => '#^Parameter \\#3 \\.\\.\\.\\$values of function sprintf expects bool\\|float\\|int\\|string\\|null, mixed given\\.$#', 26 | 'identifier' => 'argument.type', 27 | 'count' => 1, 28 | 'path' => __DIR__.'/src/Command/GeocodeCommand.php', 29 | ]; 30 | $ignoreErrors[] = [ 31 | 'message' => '#^Method Bazinga\\\\GeocoderBundle\\\\DataCollector\\\\GeocoderDataCollector\\:\\:getProviderQueries\\(\\) should return list\\ but returns array\\.$#', 32 | 'identifier' => 'return.type', 33 | 'count' => 1, 34 | 'path' => __DIR__.'/src/DataCollector/GeocoderDataCollector.php', 35 | ]; 36 | $ignoreErrors[] = [ 37 | 'message' => '#^Argument of an invalid type mixed supplied for foreach, only iterables are supported\\.$#', 38 | 'identifier' => 'foreach.nonIterable', 39 | 'count' => 3, 40 | 'path' => __DIR__.'/src/DependencyInjection/BazingaGeocoderExtension.php', 41 | ]; 42 | $ignoreErrors[] = [ 43 | 'message' => '#^Binary operation "\\." between \'bazinga_geocoder…\' and mixed results in an error\\.$#', 44 | 'identifier' => 'binaryOp.invalid', 45 | 'count' => 1, 46 | 'path' => __DIR__.'/src/DependencyInjection/BazingaGeocoderExtension.php', 47 | ]; 48 | $ignoreErrors[] = [ 49 | 'message' => '#^Cannot access offset \'aliases\' on mixed\\.$#', 50 | 'identifier' => 'offsetAccess.nonOffsetAccessible', 51 | 'count' => 1, 52 | 'path' => __DIR__.'/src/DependencyInjection/BazingaGeocoderExtension.php', 53 | ]; 54 | $ignoreErrors[] = [ 55 | 'message' => '#^Cannot access offset \'enabled\' on mixed\\.$#', 56 | 'identifier' => 'offsetAccess.nonOffsetAccessible', 57 | 'count' => 1, 58 | 'path' => __DIR__.'/src/DependencyInjection/BazingaGeocoderExtension.php', 59 | ]; 60 | $ignoreErrors[] = [ 61 | 'message' => '#^Cannot access offset \'factory\' on mixed\\.$#', 62 | 'identifier' => 'offsetAccess.nonOffsetAccessible', 63 | 'count' => 5, 64 | 'path' => __DIR__.'/src/DependencyInjection/BazingaGeocoderExtension.php', 65 | ]; 66 | $ignoreErrors[] = [ 67 | 'message' => '#^Cannot access offset \'id\' on mixed\\.$#', 68 | 'identifier' => 'offsetAccess.nonOffsetAccessible', 69 | 'count' => 1, 70 | 'path' => __DIR__.'/src/DependencyInjection/BazingaGeocoderExtension.php', 71 | ]; 72 | $ignoreErrors[] = [ 73 | 'message' => '#^Cannot access offset \'options\' on mixed\\.$#', 74 | 'identifier' => 'offsetAccess.nonOffsetAccessible', 75 | 'count' => 4, 76 | 'path' => __DIR__.'/src/DependencyInjection/BazingaGeocoderExtension.php', 77 | ]; 78 | $ignoreErrors[] = [ 79 | 'message' => '#^Cannot access offset \'reference\' on mixed\\.$#', 80 | 'identifier' => 'offsetAccess.nonOffsetAccessible', 81 | 'count' => 2, 82 | 'path' => __DIR__.'/src/DependencyInjection/BazingaGeocoderExtension.php', 83 | ]; 84 | $ignoreErrors[] = [ 85 | 'message' => '#^Cannot call static method validate\\(\\) on mixed\\.$#', 86 | 'identifier' => 'staticMethod.nonObject', 87 | 'count' => 1, 88 | 'path' => __DIR__.'/src/DependencyInjection/BazingaGeocoderExtension.php', 89 | ]; 90 | $ignoreErrors[] = [ 91 | 'message' => '#^Cannot cast mixed to string\\.$#', 92 | 'identifier' => 'cast.string', 93 | 'count' => 1, 94 | 'path' => __DIR__.'/src/DependencyInjection/BazingaGeocoderExtension.php', 95 | ]; 96 | $ignoreErrors[] = [ 97 | 'message' => '#^Parameter \\#1 \\$alias of method Symfony\\\\Component\\\\DependencyInjection\\\\ContainerBuilder\\:\\:setAlias\\(\\) expects string, mixed given\\.$#', 98 | 'identifier' => 'argument.type', 99 | 'count' => 1, 100 | 'path' => __DIR__.'/src/DependencyInjection/BazingaGeocoderExtension.php', 101 | ]; 102 | $ignoreErrors[] = [ 103 | 'message' => '#^Parameter \\#1 \\$callback of function array_map expects \\(callable\\(mixed\\)\\: mixed\\)\\|null, Closure\\(string\\)\\: Symfony\\\\Component\\\\DependencyInjection\\\\Reference given\\.$#', 104 | 'identifier' => 'argument.type', 105 | 'count' => 1, 106 | 'path' => __DIR__.'/src/DependencyInjection/BazingaGeocoderExtension.php', 107 | ]; 108 | $ignoreErrors[] = [ 109 | 'message' => '#^Parameter \\#1 \\$factoryServiceId of static method Bazinga\\\\GeocoderBundle\\\\DependencyInjection\\\\Compiler\\\\FactoryValidatorPass\\:\\:addFactoryServiceId\\(\\) expects non\\-empty\\-string, mixed given\\.$#', 110 | 'identifier' => 'argument.type', 111 | 'count' => 1, 112 | 'path' => __DIR__.'/src/DependencyInjection/BazingaGeocoderExtension.php', 113 | ]; 114 | $ignoreErrors[] = [ 115 | 'message' => '#^Parameter \\#1 \\$id of class Symfony\\\\Component\\\\DependencyInjection\\\\Reference constructor expects string, mixed given\\.$#', 116 | 'identifier' => 'argument.type', 117 | 'count' => 1, 118 | 'path' => __DIR__.'/src/DependencyInjection/BazingaGeocoderExtension.php', 119 | ]; 120 | $ignoreErrors[] = [ 121 | 'message' => '#^Parameter \\#1 \\$id of method Symfony\\\\Component\\\\DependencyInjection\\\\ContainerBuilder\\:\\:getDefinition\\(\\) expects string, mixed given\\.$#', 122 | 'identifier' => 'argument.type', 123 | 'count' => 1, 124 | 'path' => __DIR__.'/src/DependencyInjection/BazingaGeocoderExtension.php', 125 | ]; 126 | $ignoreErrors[] = [ 127 | 'message' => '#^Parameter \\#1 \\$object_or_class of function class_implements expects object\\|string, mixed given\\.$#', 128 | 'identifier' => 'argument.type', 129 | 'count' => 1, 130 | 'path' => __DIR__.'/src/DependencyInjection/BazingaGeocoderExtension.php', 131 | ]; 132 | $ignoreErrors[] = [ 133 | 'message' => '#^Parameter \\#1 \\$options of method Bazinga\\\\GeocoderBundle\\\\DependencyInjection\\\\BazingaGeocoderExtension\\:\\:findReferences\\(\\) expects array\\, mixed given\\.$#', 134 | 'identifier' => 'argument.type', 135 | 'count' => 2, 136 | 'path' => __DIR__.'/src/DependencyInjection/BazingaGeocoderExtension.php', 137 | ]; 138 | $ignoreErrors[] = [ 139 | 'message' => '#^Parameter \\#1 \\$string of function ltrim expects string, mixed given\\.$#', 140 | 'identifier' => 'argument.type', 141 | 'count' => 1, 142 | 'path' => __DIR__.'/src/DependencyInjection/BazingaGeocoderExtension.php', 143 | ]; 144 | $ignoreErrors[] = [ 145 | 'message' => '#^Parameter \\#2 \\$array of function array_key_exists expects array, array\\|bool\\|float\\|int\\|string\\|UnitEnum\\|null given\\.$#', 146 | 'identifier' => 'argument.type', 147 | 'count' => 1, 148 | 'path' => __DIR__.'/src/DependencyInjection/BazingaGeocoderExtension.php', 149 | ]; 150 | $ignoreErrors[] = [ 151 | 'message' => '#^Parameter \\#2 \\$config of method Bazinga\\\\GeocoderBundle\\\\DependencyInjection\\\\BazingaGeocoderExtension\\:\\:configureProviderPlugins\\(\\) expects array\\, mixed given\\.$#', 152 | 'identifier' => 'argument.type', 153 | 'count' => 1, 154 | 'path' => __DIR__.'/src/DependencyInjection/BazingaGeocoderExtension.php', 155 | ]; 156 | $ignoreErrors[] = [ 157 | 'message' => '#^Parameter \\#2 \\.\\.\\.\\$values of function sprintf expects bool\\|float\\|int\\|string\\|null, mixed given\\.$#', 158 | 'identifier' => 'argument.type', 159 | 'count' => 1, 160 | 'path' => __DIR__.'/src/DependencyInjection/BazingaGeocoderExtension.php', 161 | ]; 162 | $ignoreErrors[] = [ 163 | 'message' => '#^Part \\$providerName \\(mixed\\) of encapsed string cannot be cast to string\\.$#', 164 | 'identifier' => 'encapsedStringPart.nonString', 165 | 'count' => 1, 166 | 'path' => __DIR__.'/src/DependencyInjection/BazingaGeocoderExtension.php', 167 | ]; 168 | $ignoreErrors[] = [ 169 | 'message' => '#^Cannot call method getLatitude\\(\\) on Geocoder\\\\Model\\\\Coordinates\\|null\\.$#', 170 | 'identifier' => 'method.nonObject', 171 | 'count' => 1, 172 | 'path' => __DIR__.'/src/Doctrine/ORM/GeocodeEntityListener.php', 173 | ]; 174 | $ignoreErrors[] = [ 175 | 'message' => '#^Cannot call method getLongitude\\(\\) on Geocoder\\\\Model\\\\Coordinates\\|null\\.$#', 176 | 'identifier' => 'method.nonObject', 177 | 'count' => 1, 178 | 'path' => __DIR__.'/src/Doctrine/ORM/GeocodeEntityListener.php', 179 | ]; 180 | $ignoreErrors[] = [ 181 | 'message' => '#^Parameter \\#1 \\$text of method Geocoder\\\\Query\\\\GeocodeQuery\\:\\:withText\\(\\) expects string, string\\|null given\\.$#', 182 | 'identifier' => 'argument.type', 183 | 'count' => 1, 184 | 'path' => __DIR__.'/src/Plugin/FakeIpPlugin.php', 185 | ]; 186 | $ignoreErrors[] = [ 187 | 'message' => '#^Parameter \\#1 \\$accountId of class GeoIp2\\\\WebService\\\\Client constructor expects int, int\\|null given\\.$#', 188 | 'identifier' => 'argument.type', 189 | 'count' => 1, 190 | 'path' => __DIR__.'/src/ProviderFactory/GeoIP2Factory.php', 191 | ]; 192 | $ignoreErrors[] = [ 193 | 'message' => '#^Parameter \\#1 \\$filename of class GeoIp2\\\\Database\\\\Reader constructor expects string, string\\|null given\\.$#', 194 | 'identifier' => 'argument.type', 195 | 'count' => 1, 196 | 'path' => __DIR__.'/src/ProviderFactory/GeoIP2Factory.php', 197 | ]; 198 | $ignoreErrors[] = [ 199 | 'message' => '#^Parameter \\#1 \\$geoIpProvider of class Geocoder\\\\Provider\\\\GeoIP2\\\\GeoIP2Adapter constructor expects GeoIp2\\\\ProviderInterface, GeoIp2\\\\ProviderInterface\\|null given\\.$#', 200 | 'identifier' => 'argument.type', 201 | 'count' => 1, 202 | 'path' => __DIR__.'/src/ProviderFactory/GeoIP2Factory.php', 203 | ]; 204 | $ignoreErrors[] = [ 205 | 'message' => '#^Parameter \\#2 \\$licenseKey of class GeoIp2\\\\WebService\\\\Client constructor expects string, string\\|null given\\.$#', 206 | 'identifier' => 'argument.type', 207 | 'count' => 1, 208 | 'path' => __DIR__.'/src/ProviderFactory/GeoIP2Factory.php', 209 | ]; 210 | 211 | return ['parameters' => ['ignoreErrors' => $ignoreErrors]]; 212 | --------------------------------------------------------------------------------