Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions config/orm.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use Bazinga\GeocoderBundle\Doctrine\ORM\GeocodeEntityListener;
use Bazinga\GeocoderBundle\Mapping\Driver\DriverInterface;

return static function (ContainerConfigurator $container) {
$services = $container->services();

$services
->set(GeocodeEntityListener::class)
->args([
tagged_locator('bazinga_geocoder.provider'),
service(DriverInterface::class),
])
->tag('doctrine.event_listener', ['event' => 'onFlush'])
;
};
13 changes: 13 additions & 0 deletions config/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use Bazinga\GeocoderBundle\Command\GeocodeCommand;
use Bazinga\GeocoderBundle\Mapping\Driver\AttributeDriver;
use Bazinga\GeocoderBundle\Mapping\Driver\ChainDriver;
use Bazinga\GeocoderBundle\Mapping\Driver\DriverInterface;
use Bazinga\GeocoderBundle\Plugin\FakeIpPlugin;
use Bazinga\GeocoderBundle\Validator\Constraint\AddressValidator;
use Geocoder\Dumper\Dumper;
Expand Down Expand Up @@ -49,5 +52,15 @@
service(ProviderAggregator::class),
])
->tag('validator.constraint_validator')

->set(ChainDriver::class)
->args([
tagged_iterator('bazinga_geocoder.metadata.driver', exclude: [ChainDriver::class]),
])
->tag('bazinga_geocoder.metadata.driver')
->alias(DriverInterface::class, ChainDriver::class)

->set(AttributeDriver::class)
->tag('bazinga_geocoder.metadata.driver')
;
};
34 changes: 13 additions & 21 deletions doc/doctrine.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
# Doctrine annotation support
# Doctrine support

*[<< Back to documentation index](/doc/index.md)*

Wouldn't it be great if you could automatically save the coordinates of a users
address every time it is updated? Wait not more here is the feature you been always
wanted.
Wouldn't it be great if you could automatically save the coordinates of a user's
address every time it is updated? Well, wait no more—here is the feature you've
always wanted!

First of all, update your entity:

```php

use Bazinga\GeocoderBundle\Mapping\Attributes as Geocoder;

#[Geocoder\Geocodeable()]
#[Geocoder\Geocodeable(provider: 'acme')]
class User
{
#[Geocoder\Address()]
Expand All @@ -32,7 +32,7 @@ Instead of annotating a property, you can also annotate a getter:

use Bazinga\GeocoderBundle\Mapping\Attributes as Geocoder;

#[Geocoder\Geocodeable()]
#[Geocoder\Geocodeable(provider: 'acme')]
class User
{
#[Geocoder\Latitude()]
Expand All @@ -42,36 +42,28 @@ class User
private $longitude;

#[Geocoder\Address()]
public function getAddress(): string
public function getAddress(): \Stringable|string
{
// Your code...
}
}
```

Secondly, register the Doctrine event listener and its dependencies in your `config/services.yaml` or `config/services.php` file.
You have to indicate which provider to use to reverse geocode the address. Here we use `acme` provider we declared in bazinga_geocoder configuration earlier.
Secondly, enable Doctrine ORM listener in the configuration:

```yaml
Bazinga\GeocoderBundle\Mapping\Driver\AttributeDriver: ~

Bazinga\GeocoderBundle\Doctrine\ORM\GeocoderListener:
class: Bazinga\GeocoderBundle\Doctrine\ORM\GeocoderListener
arguments:
- '@bazinga_geocoder.provider.acme'
- '@Bazinga\GeocoderBundle\Mapping\Driver\AttributeDriver'
tags:
- { name: doctrine.event_listener, event: onFlush }
bazinga_geocoder:
orm:
enabled: true
```

It is done!
Now you can use it:
That's it! Now you can use it:

```php
$user = new User();
$user->setAddress('Brandenburger Tor, Pariser Platz, Berlin');

$em->persist($event);
$em->persist($user);
$em->flush();

echo $user->getLatitude(); // will output 52.516325
Expand Down
16 changes: 14 additions & 2 deletions phpstan-baseline.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,12 @@
'count' => 1,
'path' => __DIR__.'/src/DependencyInjection/BazingaGeocoderExtension.php',
];
$ignoreErrors[] = [
// identifier: argument.type
'message' => '#^Parameter \\#2 \\$array of function array_key_exists expects array, array\\|bool\\|float\\|int\\|string\\|UnitEnum\\|null given\\.$#',
'count' => 1,
'path' => __DIR__.'/src/DependencyInjection/BazingaGeocoderExtension.php',
];
$ignoreErrors[] = [
// identifier: argument.type
'message' => '#^Parameter \\#2 \\$config of method Bazinga\\\\GeocoderBundle\\\\DependencyInjection\\\\BazingaGeocoderExtension\\:\\:configureProviderPlugins\\(\\) expects array, mixed given\\.$#',
Expand All @@ -151,13 +157,19 @@
// identifier: method.nonObject
'message' => '#^Cannot call method getLatitude\\(\\) on Geocoder\\\\Model\\\\Coordinates\\|null\\.$#',
'count' => 1,
'path' => __DIR__.'/src/Doctrine/ORM/GeocoderListener.php',
'path' => __DIR__.'/src/Doctrine/ORM/GeocodeEntityListener.php',
];
$ignoreErrors[] = [
// identifier: method.nonObject
'message' => '#^Cannot call method getLongitude\\(\\) on Geocoder\\\\Model\\\\Coordinates\\|null\\.$#',
'count' => 1,
'path' => __DIR__.'/src/Doctrine/ORM/GeocoderListener.php',
'path' => __DIR__.'/src/Doctrine/ORM/GeocodeEntityListener.php',
];
$ignoreErrors[] = [
// identifier: argument.missing
'message' => '#^Missing parameter \\$provider \\(string\\) in call to Bazinga\\\\GeocoderBundle\\\\Mapping\\\\ClassMetadata constructor\\.$#',
'count' => 1,
'path' => __DIR__.'/src/Mapping/Driver/AttributeDriver.php',
];
$ignoreErrors[] = [
// identifier: argument.type
Expand Down
8 changes: 8 additions & 0 deletions src/DependencyInjection/BazingaGeocoderExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ public function load(array $configs, ContainerBuilder $container): void
$loader->load('profiling.php');
}

if (\array_key_exists('DoctrineBundle', $container->getParameter('kernel.bundles'))) {
if (true === $config['orm']['enabled']) {
$loader->load('orm.php');
}
} elseif (true === $config['orm']['enabled']) {
throw new \LogicException('Doctrine ORM listener cannot be enabled when `doctrine/doctrine-bundle` is not installed.');
}

if ($config['fake_ip']['enabled']) {
$definition = $container->getDefinition(FakeIpPlugin::class);
$definition->replaceArgument(0, $config['fake_ip']['local_ip']);
Expand Down
12 changes: 12 additions & 0 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,18 @@ public function getConfigTreeBuilder(): TreeBuilder
->scalarNode('ip')->defaultNull()->end()
->booleanNode('use_faker')->defaultFalse()->end()
->end()
->end()
->arrayNode('orm')
->addDefaultsIfNotSet()
->treatFalseLike(['enabled' => false])
->treatTrueLike(['enabled' => true])
->treatNullLike(['enabled' => true])
->children()
->booleanNode('enabled')
->info('Turn the Doctrine ORM listener on or off.')
->defaultValue(false)
->end()
->end()
->end();

return $treeBuilder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,34 +14,28 @@

use Bazinga\GeocoderBundle\Mapping\ClassMetadata;
use Bazinga\GeocoderBundle\Mapping\Driver\DriverInterface;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\OnFlushEventArgs;
use Doctrine\ORM\Events;
use Doctrine\ORM\UnitOfWork;
use Geocoder\Provider\Provider;
use Geocoder\Query\GeocodeQuery;
use Symfony\Component\DependencyInjection\ServiceLocator;

/**
* @author Markus Bachmann <markus.bachmann@bachi.biz>
* @author Pierre du Plessis <pdples@gmail.com>
*/
final class GeocoderListener implements EventSubscriber
final class GeocodeEntityListener
{
/**
* @param ServiceLocator<Provider> $providerLocator
* @param DriverInterface $driver
*/
public function __construct(
private readonly Provider $geocoder,
private readonly ServiceLocator $providerLocator,
private readonly DriverInterface $driver,
) {
}

/**
* @return list<string>
*/
public function getSubscribedEvents(): array
{
return [
Events::onFlush,
];
}

public function onFlush(OnFlushEventArgs $args): void
{
$em = $args->getObjectManager();
Expand All @@ -58,7 +52,7 @@ public function onFlush(OnFlushEventArgs $args): void

$uow->recomputeSingleEntityChangeSet(
$em->getClassMetadata($entity::class),
$entity
$entity,
);
}

Expand All @@ -77,7 +71,7 @@ public function onFlush(OnFlushEventArgs $args): void

$uow->recomputeSingleEntityChangeSet(
$em->getClassMetadata($entity::class),
$entity
$entity,
);
}
}
Expand All @@ -101,7 +95,13 @@ private function geocodeEntity(ClassMetadata $metadata, object $entity): void
return;
}

$results = $this->geocoder->geocodeQuery(GeocodeQuery::create($addressString));
$serviceId = \sprintf('bazinga_geocoder.provider.%s', $metadata->provider);

if (!$this->providerLocator->has($serviceId)) {
throw new \RuntimeException(\sprintf('The provider "%s" is invalid for object "%s".', $metadata->provider, $entity::class));
}

$results = $this->providerLocator->get($serviceId)->geocodeQuery(GeocodeQuery::create($addressString));

if (!$results->isEmpty()) {
$result = $results->first();
Expand Down
7 changes: 7 additions & 0 deletions src/Mapping/Attributes/Geocodeable.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,11 @@
#[\Attribute(\Attribute::TARGET_CLASS)]
class Geocodeable
{
/**
* @param non-empty-string $provider
*/
public function __construct(
public readonly string $provider,
) {
}
}
4 changes: 4 additions & 0 deletions src/Mapping/ClassMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@
*/
final class ClassMetadata
{
/**
* @param non-empty-string $provider
*/
public function __construct(
public readonly string $provider,
public readonly ?\ReflectionProperty $addressProperty = null,
public readonly ?\ReflectionProperty $latitudeProperty = null,
public readonly ?\ReflectionProperty $longitudeProperty = null,
Expand Down
2 changes: 1 addition & 1 deletion src/Mapping/Driver/AttributeDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public function loadMetadataFromObject(object $object): ClassMetadata
throw new MappingException(sprintf('The class "%s" is not geocodeable', $object::class));
}

$args = [];
$args = ['provider' => $attributes[0]->newInstance()->provider];

foreach ($reflection->getProperties() as $property) {
foreach ($property->getAttributes() as $attribute) {
Expand Down
54 changes: 54 additions & 0 deletions src/Mapping/Driver/ChainDriver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);

/*
* This file is part of the BazingaGeocoderBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/

namespace Bazinga\GeocoderBundle\Mapping\Driver;

use Bazinga\GeocoderBundle\Mapping\ClassMetadata;
use Bazinga\GeocoderBundle\Mapping\Exception\MappingException;

/**
* @author Pierre du Plessis <pdples@gmail.com>
*/
final class ChainDriver implements DriverInterface
{
/**
* @param iterable<DriverInterface> $drivers
*/
public function __construct(
private iterable $drivers,
) {
}

public function isGeocodeable(object $object): bool
{
foreach ($this->drivers as $driver) {
if ($driver->isGeocodeable($object)) {
return true;
}
}

return false;
}

public function loadMetadataFromObject(object $object): ClassMetadata
{
foreach ($this->drivers as $driver) {
try {
return $driver->loadMetadataFromObject($object);
} catch (MappingException) {
continue;
}
}

throw new MappingException(sprintf('The class "%s" is not geocodeable.', $object::class));
}
}
4 changes: 4 additions & 0 deletions src/Mapping/Driver/DriverInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@
namespace Bazinga\GeocoderBundle\Mapping\Driver;

use Bazinga\GeocoderBundle\Mapping\ClassMetadata;
use Bazinga\GeocoderBundle\Mapping\Exception\MappingException;

interface DriverInterface
{
public function isGeocodeable(object $object): bool;

/**
* @throws MappingException
*/
public function loadMetadataFromObject(object $object): ClassMetadata;
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
use Doctrine\ORM\Mapping\Id;

#[Entity]
#[Geocodeable]
#[Geocodeable(provider: 'acme')]
class DummyWithEmptyProperty
{
#[Id]
Expand Down
2 changes: 1 addition & 1 deletion tests/Functional/Fixtures/Entity/DummyWithGetter.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
use Doctrine\ORM\Mapping\Id;

#[Entity]
#[Geocodeable]
#[Geocodeable(provider: 'acme')]
class DummyWithGetter
{
#[Id]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
use Doctrine\ORM\Mapping\Id;

#[Entity]
#[Geocodeable]
#[Geocodeable(provider: 'acme')]
class DummyWithInvalidGetter
{
#[Id]
Expand Down
Loading