diff --git a/Mapping/Annotations/Address.php b/Mapping/Annotations/Address.php index 0631e7e1..3dde2ecc 100644 --- a/Mapping/Annotations/Address.php +++ b/Mapping/Annotations/Address.php @@ -12,6 +12,7 @@ namespace Bazinga\GeocoderBundle\Mapping\Annotations; +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD)] /** * @author Markus Bachmann * diff --git a/Mapping/Annotations/Geocodeable.php b/Mapping/Annotations/Geocodeable.php index a641ca4b..fce6311e 100644 --- a/Mapping/Annotations/Geocodeable.php +++ b/Mapping/Annotations/Geocodeable.php @@ -12,6 +12,7 @@ namespace Bazinga\GeocoderBundle\Mapping\Annotations; +#[\Attribute(\Attribute::TARGET_CLASS)] /** * @author Markus Bachmann * diff --git a/Mapping/Annotations/Latitude.php b/Mapping/Annotations/Latitude.php index f066d5fe..356a5778 100644 --- a/Mapping/Annotations/Latitude.php +++ b/Mapping/Annotations/Latitude.php @@ -12,6 +12,7 @@ namespace Bazinga\GeocoderBundle\Mapping\Annotations; +#[\Attribute(\Attribute::TARGET_PROPERTY)] /** * @author Markus Bachmann * diff --git a/Mapping/Annotations/Longitude.php b/Mapping/Annotations/Longitude.php index 0511f452..71dddaef 100644 --- a/Mapping/Annotations/Longitude.php +++ b/Mapping/Annotations/Longitude.php @@ -12,6 +12,7 @@ namespace Bazinga\GeocoderBundle\Mapping\Annotations; +#[\Attribute(\Attribute::TARGET_PROPERTY)] /** * @author Markus Bachmann * diff --git a/Mapping/Driver/AttributeDriver.php b/Mapping/Driver/AttributeDriver.php new file mode 100644 index 00000000..2b1cbaaf --- /dev/null +++ b/Mapping/Driver/AttributeDriver.php @@ -0,0 +1,82 @@ + + */ +final class AttributeDriver implements DriverInterface +{ + public function isGeocodeable($object): bool + { + if (PHP_VERSION_ID < 80000) { + return false; + } + + $reflection = ClassUtils::newReflectionObject($object); + + return count($reflection->getAttributes(Annotations\Geocodeable::class)) > 0; + } + + /** + * @throws MappingException + */ + public function loadMetadataFromObject($object): ClassMetadata + { + if (PHP_VERSION_ID < 80000) { + throw new MappingException(sprintf('The class %s is not geocodeable', get_class($object))); + } + + $reflection = ClassUtils::newReflectionObject($object); + + $attributes = $reflection->getAttributes(Annotations\Geocodeable::class); + + if (0 === count($attributes)) { + throw new MappingException(sprintf('The class %s is not geocodeable', get_class($object))); + } + + $metadata = new ClassMetadata(); + + foreach ($reflection->getProperties() as $property) { + foreach ($property->getAttributes() as $attribute) { + if (Annotations\Latitude::class === $attribute->getName()) { + $property->setAccessible(true); + $metadata->latitudeProperty = $property; + } elseif (Annotations\Longitude::class === $attribute->getName()) { + $property->setAccessible(true); + $metadata->longitudeProperty = $property; + } elseif (Annotations\Address::class === $attribute->getName()) { + $property->setAccessible(true); + $metadata->addressProperty = $property; + } + } + } + + foreach ($reflection->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { + if (count($method->getAttributes(Annotations\Address::class)) > 0) { + if (0 !== $method->getNumberOfRequiredParameters()) { + throw new MappingException('You can not use a method requiring parameters with #[Address] attribute!'); + } + + $metadata->addressGetter = $method; + } + } + + return $metadata; + } +} diff --git a/Resources/doc/doctrine.md b/Resources/doc/doctrine.md index 647ad39c..06580ba1 100644 --- a/Resources/doc/doctrine.md +++ b/Resources/doc/doctrine.md @@ -96,3 +96,40 @@ $em->flush(); echo $user->getLatitude(); // will output 52.516325 echo $user->getLongitude(); // will output 13.377264 ``` + +## PHP 8 + +If you are using PHP 8, you can use [Attributes](https://www.php.net/manual/en/language.attributes.overview.php) in your entity: + +```php + +use Bazinga\GeocoderBundle\Mapping\Annotations as Geocoder; + +#[Geocoder\Geocodeable()] +class User +{ + #[Geocoder\Address()] + private $address; + + #[Geocoder\Latitude()] + private $latitude; + + #[Geocoder\Longitude()] + private $longitude; +} +``` + +Then update your service configuration to register the `AttributeDriver`: + +```yaml + Bazinga\GeocoderBundle\Mapping\Driver\AttributeDriver: + class: 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: + - doctrine.event_subscriber +``` diff --git a/Tests/Mapping/Driver/AttributeDriverTest.php b/Tests/Mapping/Driver/AttributeDriverTest.php new file mode 100644 index 00000000..4bbfd943 --- /dev/null +++ b/Tests/Mapping/Driver/AttributeDriverTest.php @@ -0,0 +1,93 @@ + + */ +final class AttributeDriverTest extends TestCase +{ + use SetUpTearDownTrait; + + /** + * @var AttributeDriver + */ + private $driver; + + /** + * @var Reader + */ + private $reader; + + public static function doSetUpBeforeClass(): void + { + if (PHP_VERSION_ID < 80000) { + self::markTestSkipped(sprintf('"%s" is only supported on PHP 8', AttributeDriver::class)); + } + } + + protected function doSetUp(): void + { + $this->driver = new AttributeDriver(); + } + + public function testLoadMetadata() + { + $obj = new Dummy3(); + $metadata = $this->driver->loadMetadataFromObject($obj); + + $this->assertInstanceOf('ReflectionProperty', $metadata->addressProperty); + $this->assertInstanceOf('ReflectionProperty', $metadata->latitudeProperty); + $this->assertInstanceOf('ReflectionProperty', $metadata->longitudeProperty); + } + + public function testLoadMetadataFromWrongObject() + { + $this->expectException(MappingException::class); + $this->expectExceptionMessage('The class '.Dummy4::class.' is not geocodeable'); + + $this->driver->loadMetadataFromObject(new Dummy4()); + } + + public function testIsGeocodable() + { + $this->assertTrue($this->driver->isGeocodeable(new Dummy3())); + } +} + +#[Geocodeable()] +class Dummy3 +{ + #[Latitude()] + public $latitude; + + #[Longitude()] + public $longitude; + + #[Address()] + public $address; +} + +class Dummy4 +{ +}