From 39b7a035ce77a4e246cfce62bda7311955279f05 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Mon, 11 May 2026 16:25:19 +0200 Subject: [PATCH 1/3] Also parse class-level `TranslatedProperty` properties from parent classes --- src/Doctrine/TranslatableClassMetadata.php | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Doctrine/TranslatableClassMetadata.php b/src/Doctrine/TranslatableClassMetadata.php index 3e6b77c..6bbd50f 100644 --- a/src/Doctrine/TranslatableClassMetadata.php +++ b/src/Doctrine/TranslatableClassMetadata.php @@ -207,7 +207,7 @@ private function findTranslatedProperties(ClassMetadata $cm, ClassMetadataFactor } /* Class-level #[TranslatedProperty] attributes */ - foreach ($reflectionClass->getAttributes(Attribute\TranslatedProperty::class) as $classAttribute) { + foreach (self::getAttributesIncludingParents($reflectionClass, Attribute\TranslatedProperty::class) as $classAttribute) { $attribute = $classAttribute->newInstance(); $propertyName = $attribute->getPropertyName(); @@ -229,6 +229,19 @@ private function findTranslatedProperties(ClassMetadata $cm, ClassMetadataFactor } } + private static function getAttributesIncludingParents(ReflectionClass $rc, ?string $attributeName = null, int $flags = 0): array { + $attributes = []; + + do { + $attributes = array_merge( + $attributes, + $rc->getAttributes($attributeName, $flags) + ); + } while ($rc = $rc->getParentClass()); + + return $attributes; + } + private function findTranslationsCollection(ClassMetadata $cm, ClassMetadataFactory $classMetadataFactory): void { foreach ($cm->associationMappings as $fieldName => $mapping) { From 304472eccad14e1cacf9a84be409bf7ea3eebb88 Mon Sep 17 00:00:00 2001 From: mpdude <1202333+mpdude@users.noreply.github.com> Date: Mon, 11 May 2026 14:26:17 +0000 Subject: [PATCH 2/3] Fix CS with PHP-CS-Fixer --- src/Doctrine/TranslatableClassMetadata.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Doctrine/TranslatableClassMetadata.php b/src/Doctrine/TranslatableClassMetadata.php index 6bbd50f..f786978 100644 --- a/src/Doctrine/TranslatableClassMetadata.php +++ b/src/Doctrine/TranslatableClassMetadata.php @@ -229,7 +229,8 @@ private function findTranslatedProperties(ClassMetadata $cm, ClassMetadataFactor } } - private static function getAttributesIncludingParents(ReflectionClass $rc, ?string $attributeName = null, int $flags = 0): array { + private static function getAttributesIncludingParents(ReflectionClass $rc, ?string $attributeName = null, int $flags = 0): array + { $attributes = []; do { From 83d5d9e5655f8a761cc4b41ac622d130df991803 Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Mon, 11 May 2026 17:12:25 +0200 Subject: [PATCH 3/3] Add tests --- src/Doctrine/TranslatableClassMetadata.php | 12 +-- .../TranslatableClassMetadataTest.php | 16 ++++ ...ntityInheritance_MappedSuperclassChain.php | 35 ++++++++ ...nheritance_MappedSuperclassChainEntity.php | 16 ++++ ...pedSuperclassChainEntityLocaleOverride.php | 17 ++++ ...MappedSuperclassChainEntityTranslation.php | 25 ++++++ .../MappedSuperclassChainInheritanceTest.php | 89 +++++++++++++++++++ 7 files changed, 202 insertions(+), 8 deletions(-) create mode 100644 tests/Fixtures/Entity/EntityInheritance/EntityInheritance_MappedSuperclassChain.php create mode 100644 tests/Fixtures/Entity/EntityInheritance/EntityInheritance_MappedSuperclassChainEntity.php create mode 100644 tests/Fixtures/Entity/EntityInheritance/EntityInheritance_MappedSuperclassChainEntityLocaleOverride.php create mode 100644 tests/Fixtures/Entity/EntityInheritance/EntityInheritance_MappedSuperclassChainEntityTranslation.php create mode 100644 tests/Functional/MappedSuperclassChainInheritanceTest.php diff --git a/src/Doctrine/TranslatableClassMetadata.php b/src/Doctrine/TranslatableClassMetadata.php index f786978..6ec22db 100644 --- a/src/Doctrine/TranslatableClassMetadata.php +++ b/src/Doctrine/TranslatableClassMetadata.php @@ -219,7 +219,7 @@ private function findTranslatedProperties(ClassMetadata $cm, ClassMetadataFactor continue; } - $candidates[$propertyName] = $attribute->getTranslationFieldname(); + $candidates[$propertyName] ??= $attribute->getTranslationFieldname(); } /* Register all collected candidates */ @@ -270,14 +270,10 @@ private function findTranslationsCollection(ClassMetadata $cm, ClassMetadataFact private function findPrimaryLocale(ClassMetadata $cm): void { - foreach (array_merge([$cm->name], $cm->parentClasses) as $class) { - $reflectionClass = new ReflectionClass($class); + $attributes = self::getAttributesIncludingParents($cm->getReflectionClass(), Attribute\Locale::class); - foreach ($reflectionClass->getAttributes(Attribute\Locale::class) as $attribute) { - $this->primaryLocale = $attribute->newInstance()->getPrimary(); - - return; - } + if ($attributes) { + $this->primaryLocale = $attributes[0]->newInstance()->getPrimary(); } } diff --git a/tests/Doctrine/TranslatableClassMetadataTest.php b/tests/Doctrine/TranslatableClassMetadataTest.php index 0b010cf..a21d9c2 100644 --- a/tests/Doctrine/TranslatableClassMetadataTest.php +++ b/tests/Doctrine/TranslatableClassMetadataTest.php @@ -4,6 +4,7 @@ use Doctrine\Persistence\Mapping\RuntimeReflectionService; use Webfactory\Bundle\PolyglotBundle\Doctrine\TranslatableClassMetadata; +use Webfactory\Bundle\PolyglotBundle\Tests\Fixtures\Entity\EntityInheritance\EntityInheritance_MappedSuperclassChainEntityLocaleOverride; use Webfactory\Bundle\PolyglotBundle\Tests\Fixtures\Entity\TestEntity; use Webfactory\Bundle\PolyglotBundle\Tests\Functional\DatabaseFunctionalTestCase; @@ -22,6 +23,21 @@ public function can_be_serialized_and_retrieved(): void self::assertEquals($metadata, $unserialized); } + /** + * @test + */ + public function subclass_locale_takes_priority_over_parent_locale(): void + { + $metadata = TranslatableClassMetadata::parseFromClass( + EntityInheritance_MappedSuperclassChainEntityLocaleOverride::class, + $this->entityManager->getMetadataFactory() + ); + + // EntityInheritance_MappedSuperclassChain (parent) declares en_GB, + // EntityInheritance_MappedSuperclassChainEntityLocaleOverride (child) declares de_DE. + self::assertSame('de_DE', $metadata->sleep()->primaryLocale); + } + private function createMetadata(): TranslatableClassMetadata { $entityManager = $this->entityManager; diff --git a/tests/Fixtures/Entity/EntityInheritance/EntityInheritance_MappedSuperclassChain.php b/tests/Fixtures/Entity/EntityInheritance/EntityInheritance_MappedSuperclassChain.php new file mode 100644 index 0000000..8529180 --- /dev/null +++ b/tests/Fixtures/Entity/EntityInheritance/EntityInheritance_MappedSuperclassChain.php @@ -0,0 +1,35 @@ +translations = new ArrayCollection(); + } + + public function setText(TranslatableInterface $text): void + { + $this->text = $text; + } +} diff --git a/tests/Fixtures/Entity/EntityInheritance/EntityInheritance_MappedSuperclassChainEntity.php b/tests/Fixtures/Entity/EntityInheritance/EntityInheritance_MappedSuperclassChainEntity.php new file mode 100644 index 0000000..7bc6e53 --- /dev/null +++ b/tests/Fixtures/Entity/EntityInheritance/EntityInheritance_MappedSuperclassChainEntity.php @@ -0,0 +1,16 @@ +setTranslation('Basistext', 'de_DE'); + $entity->setText($t); + + self::import([$entity]); + + $loaded = $this->entityManager->find(EntityInheritance_MappedSuperclassChainEntity::class, $entity->getId()); + + self::assertSame('base text', $loaded->getText()->translate('en_GB')); + self::assertSame('Basistext', $loaded->getText()->translate('de_DE')); + } + + public function testAddTranslation(): void + { + $entityManager = $this->entityManager; + + $entity = new EntityInheritance_MappedSuperclassChainEntity(); + $entity->setText(new Translatable('base text')); + self::import([$entity]); + + $loaded = $entityManager->find(EntityInheritance_MappedSuperclassChainEntity::class, $entity->getId()); + $loaded->getText()->setTranslation('Basistext', 'de_DE'); + $entityManager->flush(); + + $entityManager->clear(); + $reloaded = $entityManager->find(EntityInheritance_MappedSuperclassChainEntity::class, $entity->getId()); + + self::assertSame('base text', $reloaded->getText()->translate('en_GB')); + self::assertSame('Basistext', $reloaded->getText()->translate('de_DE')); + } + + public function testUpdateTranslations(): void + { + $entityManager = $this->entityManager; + + $entity = new EntityInheritance_MappedSuperclassChainEntity(); + $t = new Translatable('old text'); + $t->setTranslation('alter Text', 'de_DE'); + $entity->setText($t); + self::import([$entity]); + + $loaded = $entityManager->find(EntityInheritance_MappedSuperclassChainEntity::class, $entity->getId()); + $loaded->getText()->setTranslation('new text'); + $loaded->getText()->setTranslation('neuer Text', 'de_DE'); + $entityManager->flush(); + + $entityManager->clear(); + $reloaded = $entityManager->find(EntityInheritance_MappedSuperclassChainEntity::class, $entity->getId()); + + self::assertSame('new text', $reloaded->getText()->translate('en_GB')); + self::assertSame('neuer Text', $reloaded->getText()->translate('de_DE')); + } +}