Skip to content

Commit d3ab1ca

Browse files
pierredupnorkunas
authored andcommitted
Auto register doctrine services, and support multiple metadata drivers
1 parent 3d6bbad commit d3ab1ca

26 files changed

+426
-64
lines changed

Mapping/Driver/AttributeDriver.php

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the BazingaGeocoderBundle package.
7+
* For the full copyright and license information, please view the LICENSE
8+
* file that was distributed with this source code.
9+
*
10+
* @license MIT License
11+
*/
12+
13+
namespace Bazinga\GeocoderBundle\Mapping\Driver;
14+
15+
use Bazinga\GeocoderBundle\Mapping\Attributes;
16+
use Bazinga\GeocoderBundle\Mapping\ClassMetadata;
17+
use Bazinga\GeocoderBundle\Mapping\Exception\MappingException;
18+
use Doctrine\Common\Util\ClassUtils;
19+
20+
/**
21+
* @author Pierre du Plessis <[email protected]>
22+
*/
23+
final class AttributeDriver implements DriverInterface
24+
{
25+
public function isGeocodeable($object): bool
26+
{
27+
if (PHP_VERSION_ID < 80000) {
28+
return false;
29+
}
30+
31+
$reflection = ClassUtils::newReflectionObject($object);
32+
33+
return count($reflection->getAttributes(Annotations\Geocodeable::class)) > 0;
34+
}
35+
36+
/**
37+
* @throws MappingException
38+
*/
39+
public function loadMetadataFromObject($object): ClassMetadata
40+
{
41+
if (PHP_VERSION_ID < 80000) {
42+
throw new MappingException(sprintf('The class %s is not geocodeable', get_class($object)));
43+
}
44+
45+
$reflection = ClassUtils::newReflectionObject($object);
46+
47+
$attributes = $reflection->getAttributes(Attributes\Geocodeable::class);
48+
49+
if (0 === count($attributes)) {
50+
throw new MappingException(sprintf('The class %s is not geocodeable', get_class($object)));
51+
}
52+
53+
$metadata = new ClassMetadata();
54+
$metadata->provider = $attributes[0]->newInstance()->provider;
55+
56+
foreach ($reflection->getProperties() as $property) {
57+
foreach ($property->getAttributes() as $attribute) {
58+
if (Attributes\Latitude::class === $attribute->getName()) {
59+
$property->setAccessible(true);
60+
$metadata->latitudeProperty = $property;
61+
} elseif (Attributes\Longitude::class === $attribute->getName()) {
62+
$property->setAccessible(true);
63+
$metadata->longitudeProperty = $property;
64+
} elseif (Attributes\Address::class === $attribute->getName()) {
65+
$property->setAccessible(true);
66+
$metadata->addressProperty = $property;
67+
}
68+
}
69+
}
70+
71+
foreach ($reflection->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
72+
if (count($method->getAttributes(Annotations\Address::class)) > 0) {
73+
if (0 !== $method->getNumberOfRequiredParameters()) {
74+
throw new MappingException('You can not use a method requiring parameters with #[Address] attribute!');
75+
}
76+
77+
$metadata->addressGetter = $method;
78+
}
79+
}
80+
81+
return $metadata;
82+
}
83+
}

Resources/config/doctrine.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
services:
2+
Bazinga\GeocoderBundle\Mapping\Driver\AnnotationDriver:
3+
class: Bazinga\GeocoderBundle\Mapping\Driver\AnnotationDriver
4+
arguments:
5+
- '@annotations.reader'
6+
tags:
7+
- { name: bazinga_geocoder.metadata.driver }
8+
9+
Bazinga\GeocoderBundle\Doctrine\ORM\GeocoderListener:
10+
class: Bazinga\GeocoderBundle\Doctrine\ORM\GeocoderListener
11+
arguments:
12+
- !tagged_locator 'bazinga_geocoder.provider'
13+
- '@Bazinga\GeocoderBundle\Mapping\Driver\DriverInterface'
14+
tags:
15+
- doctrine.event_subscriber

Resources/config/services.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
services:
2+
_instanceof:
3+
Geocoder\Dumper\Dumper:
4+
tags: ['bazinga_geocoder.dumper']
5+
public: true
6+
7+
Geocoder\Dumper\GeoArray: ~
8+
Geocoder\Dumper\GeoJson: ~
9+
Geocoder\Dumper\Gpx: ~
10+
Geocoder\Dumper\Kml: ~
11+
Geocoder\Dumper\Wkb: ~
12+
Geocoder\Dumper\Wkt: ~
13+
14+
Bazinga\GeocoderBundle\ProviderFactory\:
15+
resource: '../../ProviderFactory'
16+
public: false
17+
autowire: true
18+
autoconfigure: true
19+
20+
Geocoder\ProviderAggregator:
21+
class: Geocoder\ProviderAggregator
22+
23+
Bazinga\GeocoderBundle\Plugin\FakeIpPlugin:
24+
class: Bazinga\GeocoderBundle\Plugin\FakeIpPlugin
25+
arguments: [~, ~, false]
26+
27+
Bazinga\GeocoderBundle\Command\GeocodeCommand:
28+
arguments: ['@Geocoder\ProviderAggregator']
29+
tags: ['console.command']
30+
31+
Bazinga\GeocoderBundle\Validator\Constraint\AddressValidator:
32+
arguments: ['@geocoder']
33+
tags: ['validator.constraint_validator']
34+
35+
# Keep these aliases for BC purpose
36+
bazinga_geocoder.geocoder:
37+
alias: "Geocoder\\ProviderAggregator"
38+
39+
geocoder:
40+
alias: "Geocoder\\ProviderAggregator"
41+
42+
Bazinga\GeocoderBundle\Mapping\Driver\ChainDriver:
43+
class: Bazinga\GeocoderBundle\Mapping\Driver\ChainDriver
44+
arguments:
45+
- !tagged_iterator bazinga_geocoder.metadata.driver
46+
47+
Bazinga\GeocoderBundle\Mapping\Driver\AttributeDriver:
48+
class: Bazinga\GeocoderBundle\Mapping\Driver\AttributeDriver
49+
tags:
50+
- { name: bazinga_geocoder.metadata.driver }
51+
52+
Bazinga\GeocoderBundle\Mapping\Driver\DriverInterface: '@Bazinga\GeocoderBundle\Mapping\Driver\ChainDriver'

Resources/doc/doctrine.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Doctrine annotation support
2+
3+
*[<< Back to documentation index](/Resources/doc/index.md)*
4+
5+
Wouldn't it be great if you could automatically save the coordinates of a users
6+
address every time it is updated? Wait not more here is the feature you been always
7+
wanted.
8+
9+
First of all, update your entity:
10+
11+
```php
12+
13+
use Bazinga\GeocoderBundle\Mapping\Annotations as Geocoder;
14+
15+
/**
16+
* @Geocoder\Geocodeable
17+
*/
18+
class User
19+
{
20+
/**
21+
* @Geocoder\Address
22+
*/
23+
private $address;
24+
25+
/**
26+
* @Geocoder\Latitude
27+
*/
28+
private $latitude;
29+
30+
/**
31+
* @Geocoder\Longitude
32+
*/
33+
private $longitude;
34+
}
35+
```
36+
37+
Instead of annotating a property, you can also annotate a getter:
38+
39+
```php
40+
41+
use Bazinga\GeocoderBundle\Mapping\Annotations as Geocoder;
42+
43+
/**
44+
* @Geocoder\Geocodeable
45+
*/
46+
class User
47+
{
48+
/**
49+
* @Geocoder\Latitude
50+
*/
51+
private $latitude;
52+
53+
/**
54+
* @Geocoder\Longitude
55+
*/
56+
private $longitude;
57+
58+
/**
59+
* @Geocoder\Address
60+
*/
61+
public function getAddress(): string
62+
{
63+
// Your code...
64+
}
65+
}
66+
```
67+
68+
If you are using PHP 8, then you can use [Attributes](https://www.php.net/manual/en/language.attributes.overview.php) in your entity:
69+
70+
```php
71+
72+
use Bazinga\GeocoderBundle\Mapping\Annotations as Geocoder;
73+
74+
#[Geocoder\Geocodeable()]
75+
class User
76+
{
77+
#[Geocoder\Address()]
78+
private $address;
79+
80+
#[Geocoder\Latitude()]
81+
private $latitude;
82+
83+
#[Geocoder\Longitude()]
84+
private $longitude;
85+
}
86+
```
87+
88+
It is done!
89+
Now you can use it:
90+
91+
```php
92+
$user = new User();
93+
$user->setAddress('Brandenburger Tor, Pariser Platz, Berlin');
94+
$em->persist($event);
95+
$em->flush();
96+
97+
echo $user->getLatitude(); // will output 52.516325
98+
echo $user->getLongitude(); // will output 13.377264
99+
```

config/orm.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
6+
7+
use Bazinga\GeocoderBundle\Doctrine\ORM\GeocodeEntityListener;
8+
use Bazinga\GeocoderBundle\Mapping\Driver\DriverInterface;
9+
10+
return static function (ContainerConfigurator $container) {
11+
$services = $container->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+
};

config/services.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
66

77
use Bazinga\GeocoderBundle\Command\GeocodeCommand;
8+
use Bazinga\GeocoderBundle\Mapping\Driver\AttributeDriver;
9+
use Bazinga\GeocoderBundle\Mapping\Driver\ChainDriver;
10+
use Bazinga\GeocoderBundle\Mapping\Driver\DriverInterface;
811
use Bazinga\GeocoderBundle\Plugin\FakeIpPlugin;
912
use Bazinga\GeocoderBundle\Validator\Constraint\AddressValidator;
1013
use Geocoder\Dumper\Dumper;
@@ -49,5 +52,15 @@
4952
service(ProviderAggregator::class),
5053
])
5154
->tag('validator.constraint_validator')
55+
56+
->set(ChainDriver::class)
57+
->args([
58+
tagged_iterator('bazinga_geocoder.metadata.driver'),
59+
])
60+
->tag('bazinga_geocoder.metadata.driver')
61+
->alias(DriverInterface::class, ChainDriver::class)
62+
63+
->set(AttributeDriver::class)
64+
->tag('bazinga_geocoder.metadata.driver')
5265
;
5366
};

phpstan-baseline.php

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,12 @@
129129
'count' => 1,
130130
'path' => __DIR__.'/src/DependencyInjection/BazingaGeocoderExtension.php',
131131
];
132+
$ignoreErrors[] = [
133+
// identifier: argument.type
134+
'message' => '#^Parameter \\#2 \\$array of function array_key_exists expects array, array\\|bool\\|float\\|int\\|string\\|UnitEnum\\|null given\\.$#',
135+
'count' => 1,
136+
'path' => __DIR__.'/src/DependencyInjection/BazingaGeocoderExtension.php',
137+
];
132138
$ignoreErrors[] = [
133139
// identifier: argument.type
134140
'message' => '#^Parameter \\#2 \\$config of method Bazinga\\\\GeocoderBundle\\\\DependencyInjection\\\\BazingaGeocoderExtension\\:\\:configureProviderPlugins\\(\\) expects array, mixed given\\.$#',
@@ -151,13 +157,19 @@
151157
// identifier: method.nonObject
152158
'message' => '#^Cannot call method getLatitude\\(\\) on Geocoder\\\\Model\\\\Coordinates\\|null\\.$#',
153159
'count' => 1,
154-
'path' => __DIR__.'/src/Doctrine/ORM/GeocoderListener.php',
160+
'path' => __DIR__.'/src/Doctrine/ORM/GeocodeEntityListener.php',
155161
];
156162
$ignoreErrors[] = [
157163
// identifier: method.nonObject
158164
'message' => '#^Cannot call method getLongitude\\(\\) on Geocoder\\\\Model\\\\Coordinates\\|null\\.$#',
159165
'count' => 1,
160-
'path' => __DIR__.'/src/Doctrine/ORM/GeocoderListener.php',
166+
'path' => __DIR__.'/src/Doctrine/ORM/GeocodeEntityListener.php',
167+
];
168+
$ignoreErrors[] = [
169+
// identifier: argument.missing
170+
'message' => '#^Missing parameter \\$provider \\(string\\) in call to Bazinga\\\\GeocoderBundle\\\\Mapping\\\\ClassMetadata constructor\\.$#',
171+
'count' => 1,
172+
'path' => __DIR__.'/src/Mapping/Driver/AttributeDriver.php',
161173
];
162174
$ignoreErrors[] = [
163175
// identifier: argument.type

src/DependencyInjection/BazingaGeocoderExtension.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@ public function load(array $configs, ContainerBuilder $container): void
5555
$loader->load('profiling.php');
5656
}
5757

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+
5866
if ($config['fake_ip']['enabled']) {
5967
$definition = $container->getDefinition(FakeIpPlugin::class);
6068
$definition->replaceArgument(0, $config['fake_ip']['local_ip']);

src/DependencyInjection/Configuration.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,18 @@ public function getConfigTreeBuilder(): TreeBuilder
6666
->scalarNode('ip')->defaultNull()->end()
6767
->booleanNode('use_faker')->defaultFalse()->end()
6868
->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()
6981
->end();
7082

7183
return $treeBuilder;

0 commit comments

Comments
 (0)