diff --git a/.gitignore b/.gitignore index aafb16976..4d6f73e87 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ coverage.xml phpstan.neon .phpunit.result.cache phpunit.xml +.idea diff --git a/CHANGELOG.md b/CHANGELOG.md index 735c5de2b..8a9db2b11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ a release. ## [Unreleased] ### Changed - All: Removed the dollar sign from the generated cache ID for extension metadata to ensure only characters mandated by [PSR-6](https://www.php-fig.org/psr/psr-6/#definitions) are used, improving compatibility with caching implementations with strict character requirements (#2978) +- Translatable: Introduce a method to remove translations for a specific entity. Either all at once, or by field and/or locale. ## [3.22.0] - 2025-12-13 ### Added diff --git a/src/Translatable/Entity/Repository/TranslationRepository.php b/src/Translatable/Entity/Repository/TranslationRepository.php index 54c309c95..22f1eb22d 100644 --- a/src/Translatable/Entity/Repository/TranslationRepository.php +++ b/src/Translatable/Entity/Repository/TranslationRepository.php @@ -226,6 +226,44 @@ public function findTranslationsByObjectId($id) return $result; } + public function removeTranslations($entity, $field = null, $locale = null) + { + $wrapped = new EntityWrapper($entity, $this->getEntityManager()); + if ($wrapped->hasValidIdentifier()) { + $entityId = $wrapped->getIdentifier(); + $config = $this + ->getTranslatableListener() + ->getConfiguration($this->getEntityManager(), $wrapped->getMetadata()->getName()); + + if (!$config) { + return; + } + + $entityClass = $config['useObjectClass']; + $translationMeta = $this->getClassMetadata(); // table inheritance support + + $translationClass = $config['translationClass'] ?? $translationMeta->rootEntityName; + + $qb = $this->getEntityManager()->createQueryBuilder(); + $qb->delete() + ->from($translationClass, 'trans') + ->where('trans.foreignKey = :entityId', 'trans.objectClass = :entityClass') + ->setParameter('entityId', $entityId) + ->setParameter('entityClass', $entityClass); + + if ($field) { + $qb->andWhere('trans.field = :field') + ->setParameter('field', $field); + } + if ($locale) { + $qb->andWhere('trans.locale = :locale') + ->setParameter('locale', $locale); + } + + $qb->getQuery()->execute(); + } + } + /** * Get the currently used TranslatableListener * diff --git a/tests/Gedmo/Translatable/TranslatableEntityCollectionTest.php b/tests/Gedmo/Translatable/TranslatableEntityCollectionTest.php index 78196bf92..49ecadb65 100644 --- a/tests/Gedmo/Translatable/TranslatableEntityCollectionTest.php +++ b/tests/Gedmo/Translatable/TranslatableEntityCollectionTest.php @@ -150,6 +150,144 @@ public function testShouldUpdateMultipleTranslations(): void static::assertSame('content lt', $translations['lt_lt']['content']); } + public function testShouldRemoveAllTranslations(): void + { + $this->populate(); + $repo = $this->em->getRepository(Translation::class); + $sport = $this->em->getRepository(Article::class)->find(1); + $repo + ->translate($sport, 'title', 'lt_lt', 'sport lt') + ->translate($sport, 'content', 'lt_lt', 'content lt') + ->translate($sport, 'title', 'ru_ru', 'sport ru change') + ->translate($sport, 'content', 'ru_ru', 'content ru change') + ->translate($sport, 'title', 'en_us', 'sport en update') + ->translate($sport, 'content', 'en_us', 'content en update') + ; + $this->em->flush(); + $repo->removeTranslations($sport); + + $translations = $repo->findTranslations($sport); + static::assertCount(0, $translations); + } + + public function testShouldRemoveTitleTranslations(): void + { + $this->populate(); + $repo = $this->em->getRepository(Translation::class); + $sport = $this->em->getRepository(Article::class)->find(1); + $repo + ->translate($sport, 'title', 'lt_lt', 'sport lt') + ->translate($sport, 'content', 'lt_lt', 'content lt') + ->translate($sport, 'title', 'ru_ru', 'sport ru change') + ->translate($sport, 'content', 'ru_ru', 'content ru change') + ->translate($sport, 'title', 'en_us', 'sport en update') + ->translate($sport, 'content', 'en_us', 'content en update') + ; + $this->em->flush(); + $repo->removeTranslations($sport, 'title'); + + $translations = $repo->findTranslations($sport); + static::assertCount(3, $translations); + + static::assertArrayHasKey('de_de', $translations); + static::assertArrayNotHasKey('title', $translations['de_de']); + static::assertArrayHasKey('content', $translations['de_de']); + static::assertSame('sport de', $translations['de_de']['title']); + static::assertSame('content de', $translations['de_de']['content']); + + static::assertArrayHasKey('ru_ru', $translations); + static::assertArrayNotHasKey('title', $translations['ru_ru']); + static::assertArrayHasKey('content', $translations['ru_ru']); + static::assertSame('sport ru change', $translations['ru_ru']['title']); + static::assertSame('content ru change', $translations['ru_ru']['content']); + + static::assertArrayHasKey('lt_lt', $translations); + static::assertArrayNotHasKey('title', $translations['lt_lt']); + static::assertArrayHasKey('content', $translations['lt_lt']); + static::assertSame('sport lt', $translations['lt_lt']['title']); + static::assertSame('content lt', $translations['lt_lt']['content']); + } + + public function testShouldRemoveLocaleTranslations(): void + { + $this->populate(); + $repo = $this->em->getRepository(Translation::class); + $sport = $this->em->getRepository(Article::class)->find(1); + $repo + ->translate($sport, 'title', 'lt_lt', 'sport lt') + ->translate($sport, 'content', 'lt_lt', 'content lt') + ->translate($sport, 'title', 'ru_ru', 'sport ru change') + ->translate($sport, 'content', 'ru_ru', 'content ru change') + ->translate($sport, 'title', 'en_us', 'sport en update') + ->translate($sport, 'content', 'en_us', 'content en update') + ; + $this->em->flush(); + + $translations = $repo->findTranslations($sport); + static::assertCount(3, $translations); + + $repo->removeTranslations($sport, null, 'lt_lt'); + + $translations = $repo->findTranslations($sport); + static::assertCount(2, $translations); + + static::assertArrayHasKey('de_de', $translations); + static::assertArrayNotHasKey('title', $translations['de_de']); + static::assertArrayHasKey('content', $translations['de_de']); + static::assertSame('sport de', $translations['de_de']['title']); + static::assertSame('content de', $translations['de_de']['content']); + + static::assertArrayHasKey('ru_ru', $translations); + static::assertArrayNotHasKey('title', $translations['ru_ru']); + static::assertArrayHasKey('content', $translations['ru_ru']); + static::assertSame('sport ru change', $translations['ru_ru']['title']); + static::assertSame('content ru change', $translations['ru_ru']['content']); + + static::assertArrayNotHasKey('lt_lt', $translations); + } + + public function testShouldRemoveFieldAndLocaleTranslations(): void + { + $this->populate(); + $repo = $this->em->getRepository(Translation::class); + $sport = $this->em->getRepository(Article::class)->find(1); + $repo + ->translate($sport, 'title', 'lt_lt', 'sport lt') + ->translate($sport, 'content', 'lt_lt', 'content lt') + ->translate($sport, 'title', 'ru_ru', 'sport ru change') + ->translate($sport, 'content', 'ru_ru', 'content ru change') + ->translate($sport, 'title', 'en_us', 'sport en update') + ->translate($sport, 'content', 'en_us', 'content en update') + ; + $this->em->flush(); + + $translations = $repo->findTranslations($sport); + static::assertCount(3, $translations); + + $repo->removeTranslations($sport, 'title', 'lt_lt'); + + $translations = $repo->findTranslations($sport); + static::assertCount(3, $translations); + + static::assertArrayHasKey('de_de', $translations); + static::assertArrayHasKey('title', $translations['de_de']); + static::assertArrayHasKey('content', $translations['de_de']); + static::assertSame('sport de', $translations['de_de']['title']); + static::assertSame('content de', $translations['de_de']['content']); + + static::assertArrayHasKey('ru_ru', $translations); + static::assertArrayHasKey('title', $translations['ru_ru']); + static::assertArrayHasKey('content', $translations['ru_ru']); + static::assertSame('sport ru change', $translations['ru_ru']['title']); + static::assertSame('content ru change', $translations['ru_ru']['content']); + + static::assertArrayHasKey('lt_lt', $translations); + static::assertArrayNotHasKey('title', $translations['lt_lt']); + static::assertArrayHasKey('content', $translations['lt_lt']); + static::assertSame('sport lt', $translations['lt_lt']['title']); + static::assertSame('content lt', $translations['lt_lt']['content']); + } + protected function getUsedEntityFixtures(): array { return [