diff --git a/src/Doctrine/TranslatableClassMetadata.php b/src/Doctrine/TranslatableClassMetadata.php index e943ac6..4080cac 100644 --- a/src/Doctrine/TranslatableClassMetadata.php +++ b/src/Doctrine/TranslatableClassMetadata.php @@ -187,16 +187,23 @@ private function findTranslatedProperties(ClassMetadataInfo $cm, Reader $reader, return; } + $reflectionService = $classMetadataFactory->getReflectionService(); $translationClassMetadata = $classMetadataFactory->getMetadataFor($this->translationClass->getName()); - foreach ($cm->fieldMappings as $fieldName => $mapping) { - if (isset($mapping['declared'])) { - // The association is inherited from a parent class + /* Iterate all properties of the class, not only those mapped by Doctrine */ + foreach ($cm->getReflectionClass()->getProperties() as $reflectionProperty) { + $propertyName = $reflectionProperty->name; + + /* + If the property is inherited from a parent class, and our parent entity class + already contains that declaration, we need not include it. + */ + $declaringClass = $reflectionProperty->getDeclaringClass()->name; + if ($declaringClass !== $cm->name && $cm->parentClasses && is_a($cm->parentClasses[0], $declaringClass, true)) { continue; } $foundAttributeOrAnnotation = null; - $reflectionProperty = $cm->getReflectionProperty($fieldName); $attributes = $reflectionProperty->getAttributes(Attribute\Translatable::class); if ($attributes) { @@ -210,11 +217,9 @@ private function findTranslatedProperties(ClassMetadataInfo $cm, Reader $reader, } if ($foundAttributeOrAnnotation) { - $translationFieldname = $foundAttributeOrAnnotation->getTranslationFieldname() ?: $fieldName; - $translationFieldReflectionProperty = $translationClassMetadata->getReflectionProperty($translationFieldname); - - $this->translatedProperties[$fieldName] = $reflectionProperty; - $this->translationFieldMapping[$fieldName] = $translationFieldReflectionProperty; + $this->translatedProperties[$propertyName] = $reflectionService->getAccessibleProperty($cm->name, $propertyName); + $translationFieldname = $foundAttributeOrAnnotation->getTranslationFieldname() ?: $propertyName; + $this->translationFieldMapping[$propertyName] = $reflectionService->getAccessibleProperty($translationClassMetadata->name, $translationFieldname); } } } diff --git a/tests/Functional/TranslatingUnmappedPropertiesTest.php b/tests/Functional/TranslatingUnmappedPropertiesTest.php new file mode 100644 index 0000000..b681c6d --- /dev/null +++ b/tests/Functional/TranslatingUnmappedPropertiesTest.php @@ -0,0 +1,141 @@ +setupOrmInfrastructure([ + TranslatingUnmappedPropertiesTest_Entity::class, + TranslatingUnmappedPropertiesTest_Translation::class, + ]); + } + + public function testPersistAndReloadEntity(): void + { + $entity = new TranslatingUnmappedPropertiesTest_Entity(); + $entity->text = new Translatable('base text'); + $entity->text->setTranslation('Basistext', 'de_DE'); + + $this->infrastructure->import($entity); + + $loaded = $this->infrastructure->getEntityManager()->find(TranslatingUnmappedPropertiesTest_Entity::class, $entity->id); + + self::assertSame('Basistext', $loaded->text->translate('de_DE')); + self::assertSame('base text', $loaded->text->translate('en_GB')); + } +} + +/** + * @ORM\Entity + * + * @ORM\HasLifecycleCallbacks + */ +#[Polyglot\Locale(primary: 'en_GB')] +class TranslatingUnmappedPropertiesTest_Entity +{ + /** + * @ORM\Column(type="integer") + * + * @ORM\Id + * + * @ORM\GeneratedValue + */ + public ?int $id = null; + + /** + * @ORM\OneToMany(targetEntity="TranslatingUnmappedPropertiesTest_Translation", mappedBy="entity") + */ + #[Polyglot\TranslationCollection] + protected Collection $translations; + + /** + * @ORM\Column(type="string") + */ + public $mappedText; + + // (!) This field is unmapped from the ORM point of view + #[Polyglot\Translatable] + public $text; + + public function __construct() + { + $this->translations = new ArrayCollection(); + $this->text = new Translatable(); + } + + /** @ORM\PreFlush() */ + public function copyToMappedField(): void + { + $this->mappedText = $this->text; + } + + /** @ORM\PostLoad() */ + public function copyFromMappedField(): void + { + $this->text = $this->mappedText; + } +} + +/** + * @ORM\Entity + * + * @ORM\HasLifecycleCallbacks + */ +class TranslatingUnmappedPropertiesTest_Translation +{ + /** + * @ORM\Id + * + * @ORM\GeneratedValue + * + * @ORM\Column(type="integer") + */ + private ?int $id = null; + + /** + * @ORM\Column + */ + #[Polyglot\Locale] + private string $locale; + + /** + * @ORM\ManyToOne(targetEntity="TranslatingUnmappedPropertiesTest_Entity", inversedBy="translations") + */ + private TranslatingUnmappedPropertiesTest_Entity $entity; + + /** + * @ORM\Column + */ + private $mappedText; + + // (!) This field is unmapped from the ORM point of view + private $text; + + /** @ORM\PreFlush() */ + public function copyToMappedField(): void + { + $this->mappedText = $this->text; + } + + /** @ORM\PostLoad() */ + public function copyFromMappedField(): void + { + $this->text = $this->mappedText; + } +}