diff --git a/composer.json b/composer.json index 25372e4..8bd11c2 100644 --- a/composer.json +++ b/composer.json @@ -20,15 +20,15 @@ "source": "https://github.com/prezent/doctrine-translatable" }, "require": { - "php": ">=7.4.0", + "php": ">=8.2", "doctrine/common": "^2.3|^3.0", "doctrine/orm": ">=2.12.0", "jms/metadata": "^1.1|^2.0", - "symfony/cache": "^5.0|^6.0", - "doctrine/annotations": "^1.13" + "symfony/cache": "^5.0|^6.0|^7.0", + "doctrine/annotations": "^2.0" }, "require-dev": { - "symfony/yaml": "^5.0|6.0", + "symfony/yaml": "^5.0|^6.0|^7.0", "phpunit/phpunit": "^8.5" }, "autoload": { diff --git a/src/Annotation/CurrentLocale.php b/src/Annotation/CurrentLocale.php index 3015446..d49f105 100644 --- a/src/Annotation/CurrentLocale.php +++ b/src/Annotation/CurrentLocale.php @@ -8,6 +8,7 @@ */ namespace Prezent\Doctrine\Translatable\Annotation; +use Doctrine\ORM\Mapping\MappingAttribute; /** * CurrentTranslation annotation @@ -18,6 +19,7 @@ * @Annotation * @Target("PROPERTY") */ -class CurrentLocale +#[\Attribute(\Attribute::TARGET_PROPERTY)] +class CurrentLocale implements MappingAttribute { } diff --git a/src/Annotation/FallbackLocale.php b/src/Annotation/FallbackLocale.php index efd35c2..3aee3bb 100644 --- a/src/Annotation/FallbackLocale.php +++ b/src/Annotation/FallbackLocale.php @@ -8,6 +8,7 @@ */ namespace Prezent\Doctrine\Translatable\Annotation; +use Doctrine\ORM\Mapping\MappingAttribute; /** * CurrentTranslation annotation @@ -18,6 +19,7 @@ * @Annotation * @Target("PROPERTY") */ -class FallbackLocale +#[\Attribute(\Attribute::TARGET_PROPERTY)] +class FallbackLocale implements MappingAttribute { } diff --git a/src/Annotation/Locale.php b/src/Annotation/Locale.php index 75a5bef..ad4abf9 100644 --- a/src/Annotation/Locale.php +++ b/src/Annotation/Locale.php @@ -9,6 +9,9 @@ namespace Prezent\Doctrine\Translatable\Annotation; +use Doctrine\ORM\Mapping\MappingAttribute; +use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; + /** * CurrentTranslation annotation * @@ -17,7 +20,9 @@ * * @Annotation * @Target("PROPERTY") + * @NamedArgumentConstructor */ -class Locale +#[\Attribute(\Attribute::TARGET_PROPERTY)] +class Locale implements MappingAttribute { } diff --git a/src/Annotation/Translatable.php b/src/Annotation/Translatable.php index 5ae9140..204f9b0 100644 --- a/src/Annotation/Translatable.php +++ b/src/Annotation/Translatable.php @@ -8,6 +8,8 @@ */ namespace Prezent\Doctrine\Translatable\Annotation; +use Doctrine\ORM\Mapping\MappingAttribute; +use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; /** * Translatable annotation @@ -16,8 +18,10 @@ * * @Annotation * @Target("PROPERTY") + * @NamedArgumentConstructor */ -class Translatable +#[\Attribute(\Attribute::TARGET_PROPERTY)] +class Translatable implements MappingAttribute { /** * @var string @@ -28,4 +32,13 @@ class Translatable * @var string */ public $referencedColumnName = 'id'; + + + public function __construct( + ?string $targetEntity, + ?string $referencedColumnName = 'id' + ) { + $this->targetEntity = $targetEntity; + $this->referencedColumnName = $referencedColumnName; + } } diff --git a/src/Annotation/Translations.php b/src/Annotation/Translations.php index 1771a1a..b6b663c 100644 --- a/src/Annotation/Translations.php +++ b/src/Annotation/Translations.php @@ -9,6 +9,12 @@ namespace Prezent\Doctrine\Translatable\Annotation; +use Attribute; +use Doctrine\ORM\Mapping\MappingAttribute; +use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; +use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; +use Doctrine\ODM\MongoDB\Utility\CollectionHelper; + /** * Translations annotation * @@ -16,11 +22,19 @@ * * @Annotation * @Target("PROPERTY") + * @NamedArgumentConstructor */ -class Translations +#[Attribute(Attribute::TARGET_PROPERTY)] +class Translations implements MappingAttribute { /** * @var string */ public $targetEntity; + + public function __construct( + ?string $targetEntity = null + ) { + $this->targetEntity = $targetEntity; + } } diff --git a/src/Entity/TranslationTrait.php b/src/Entity/TranslationTrait.php index c73c078..3899ef8 100644 --- a/src/Entity/TranslationTrait.php +++ b/src/Entity/TranslationTrait.php @@ -15,17 +15,15 @@ trait TranslationTrait { - /** - * @ORM\Id - * @ORM\GeneratedValue(strategy="IDENTITY") - * @ORM\Column(name="id", type="integer") - */ + + #[ORM\Column(name: 'id', type: 'integer', nullable: false)] + #[ORM\Id] + #[ORM\GeneratedValue(strategy: 'IDENTITY')] protected $id; - /** - * @ORM\Column(name="locale", type="string") - * @Prezent\Locale - */ + + #[ORM\Column(name: 'locale', type: 'string')] + #[Prezent\Locale] protected $locale; /** diff --git a/src/Mapping/Driver/AttributeDriver.php b/src/Mapping/Driver/AttributeDriver.php new file mode 100644 index 0000000..7cc1c91 --- /dev/null +++ b/src/Mapping/Driver/AttributeDriver.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Prezent\Doctrine\Translatable\Mapping\Driver; + +use Doctrine\ORM\Mapping\Driver\AttributeReader; +use Doctrine\Persistence\Mapping\ClassMetadataFactory; +use Metadata\ClassMetadata; +use Metadata\Driver\DriverInterface; +use Prezent\Doctrine\Translatable\Mapping\PropertyMetadata; +use Prezent\Doctrine\Translatable\Mapping\TranslatableMetadata; +use Prezent\Doctrine\Translatable\Mapping\TranslationMetadata; + +/** + * Load translation metadata from annotations + */ +class AttributeDriver implements DriverInterface +{ + /** + * @var AttributeReader + */ + private $reader; + + /** + * Constructor + * + * @param AttributeReader $reader + * @param ClassMetadataFactory $factory Doctrine's metadata factory + */ + public function __construct(AttributeReader $reader) + { + $this->reader = $reader; + } + + /** + * {@inheritdoc} + */ + public function loadMetadataForClass(\ReflectionClass $class): ?ClassMetadata + { + if ($class->implementsInterface('Prezent\\Doctrine\\Translatable\\TranslatableInterface')) { + return $this->loadTranslatableMetadata($class); + } + + if ($class->implementsInterface('Prezent\\Doctrine\\Translatable\\TranslationInterface')) { + return $this->loadTranslationMetadata($class); + } + + return null; + } + + /** + * Load metadata for a translatable class + * + * @param \ReflectionClass $class + * @return TranslatableMetadata + */ + private function loadTranslatableMetadata(\ReflectionClass $class) + { + $classMetadata = new TranslatableMetadata($class->name); + + foreach ($class->getProperties() as $property) { + + if ($property->class !== $class->name) { + continue; + } + + $propertyMetadata = new PropertyMetadata($class->name, $property->getName()); + $targetEntity = $class->name . 'Translation'; + + if ($this->reader->getPropertyAttribute($property, 'Prezent\\Doctrine\\Translatable\\Annotation\\CurrentLocale')) { + $classMetadata->currentLocale = $propertyMetadata; + $classMetadata->addPropertyMetadata($propertyMetadata); + } + + if ($this->reader->getPropertyAttribute($property, 'Prezent\\Doctrine\\Translatable\\Annotation\\FallbackLocale')) { + $classMetadata->fallbackLocale = $propertyMetadata; + $classMetadata->addPropertyMetadata($propertyMetadata); + } + + if ($annot = $this->reader->getPropertyAttribute($property, 'Prezent\\Doctrine\\Translatable\\Annotation\\Translations')) { + $classMetadata->targetEntity = $annot->targetEntity ?? $targetEntity; + $classMetadata->translations = $propertyMetadata; + $classMetadata->addPropertyMetadata($propertyMetadata); + } + } + + return $classMetadata; + } + + /** + * Load metadata for a translation class + * + * @param \ReflectionClass $class + * @return TranslationMetadata + */ + private function loadTranslationMetadata(\ReflectionClass $class) + { + $classMetadata = new TranslationMetadata($class->name); + + foreach ($class->getProperties() as $property) { + if ($property->class !== $class->name) { + continue; + } + + $propertyMetadata = new PropertyMetadata($class->name, $property->getName()); + $targetEntity = 'Translation' === substr($class->name, -11) ? substr($class->name, 0, -11) : null; + + if ($annot = $this->reader->getPropertyAttribute($property, 'Prezent\\Doctrine\\Translatable\\Annotation\\Translatable')) { + $classMetadata->targetEntity = $annot->targetEntity ?? $targetEntity; + $classMetadata->referencedColumnName = $annot->referencedColumnName; + $classMetadata->translatable = $propertyMetadata; + $classMetadata->addPropertyMetadata($propertyMetadata); + } + + if ($this->reader->getPropertyAttribute($property, 'Prezent\\Doctrine\\Translatable\\Annotation\\Locale')) { + $classMetadata->locale = $propertyMetadata; + $classMetadata->addPropertyMetadata($propertyMetadata); + } + } + + return $classMetadata; + } +} diff --git a/src/Mapping/Driver/DoctrineAdapter.php b/src/Mapping/Driver/DoctrineAdapter.php index 0294aec..faa3ba4 100644 --- a/src/Mapping/Driver/DoctrineAdapter.php +++ b/src/Mapping/Driver/DoctrineAdapter.php @@ -12,7 +12,8 @@ use Doctrine\ORM\Mapping\Driver\SimplifiedXmlDriver; use Doctrine\ORM\Mapping\Driver\SimplifiedYamlDriver; use Doctrine\Persistence\ManagerRegistry; -use Doctrine\ORM\Mapping\Driver\AnnotationDriver as DoctrineAnnotationDriver; +use Doctrine\ORM\Mapping\Driver\AttributeDriver as DoctrineAttributeDriver; +use Doctrine\Persistence\Mapping\Driver\AnnotationDriver as DoctrineAnnotationDriver; use Doctrine\Persistence\Mapping\Driver\FileDriver as DoctrineFileDriver; use Doctrine\Persistence\Mapping\Driver\MappingDriver; use Doctrine\Persistence\Mapping\Driver\MappingDriverChain; @@ -88,6 +89,9 @@ public static function fromMetadataDriver(MappingDriver $omDriver) if ($omDriver instanceof DoctrineAnnotationDriver) { return new AnnotationDriver($omDriver->getReader()); } + if ($omDriver instanceof DoctrineAttributeDriver) { + return new AttributeDriver($omDriver->getReader()); + } if ($omDriver instanceof DoctrineFileDriver) { $reflClass = new \ReflectionClass($omDriver);