Skip to content

Commit c8f64a4

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

27 files changed

+438
-84
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', 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')
5265
;
5366
};

doc/doctrine.md

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
# Doctrine annotation support
1+
# Doctrine support
22

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

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.
5+
Wouldn't it be great if you could automatically save the coordinates of a user's
6+
address every time it is updated? Well, wait no more—here is the feature you've
7+
always wanted!
88

99
First of all, update your entity:
1010

1111
```php
1212

1313
use Bazinga\GeocoderBundle\Mapping\Attributes as Geocoder;
1414

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

3333
use Bazinga\GeocoderBundle\Mapping\Attributes as Geocoder;
3434

35-
#[Geocoder\Geocodeable()]
35+
#[Geocoder\Geocodeable(provider: 'acme')]
3636
class User
3737
{
3838
#[Geocoder\Latitude()]
@@ -42,30 +42,22 @@ class User
4242
private $longitude;
4343

4444
#[Geocoder\Address()]
45-
public function getAddress(): string
45+
public function getAddress(): \Stringable|string
4646
{
4747
// Your code...
4848
}
4949
}
5050
```
5151

52-
Secondly, register the Doctrine event listener and its dependencies in your `config/services.yaml` or `config/services.php` file.
53-
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.
52+
Secondly, enable Doctrine ORM listener in the configuration:
5453

5554
```yaml
56-
Bazinga\GeocoderBundle\Mapping\Driver\AttributeDriver: ~
57-
58-
Bazinga\GeocoderBundle\Doctrine\ORM\GeocoderListener:
59-
class: Bazinga\GeocoderBundle\Doctrine\ORM\GeocoderListener
60-
arguments:
61-
- '@bazinga_geocoder.provider.acme'
62-
- '@Bazinga\GeocoderBundle\Mapping\Driver\AttributeDriver'
63-
tags:
64-
- { name: doctrine.event_listener, event: onFlush }
55+
bazinga_geocoder:
56+
orm:
57+
enabled: true
6558
```
6659
67-
It is done!
68-
Now you can use it:
60+
That's it! Now you can use it:
6961
7062
```php
7163
$user = new User();

0 commit comments

Comments
 (0)