diff --git a/src/Form/EventListener/TranslationsFormsListener.php b/src/Form/EventListener/TranslationsFormsListener.php index e2b9a77..40c3cf4 100644 --- a/src/Form/EventListener/TranslationsFormsListener.php +++ b/src/Form/EventListener/TranslationsFormsListener.php @@ -13,6 +13,7 @@ namespace A2lix\TranslationFormBundle\Form\EventListener; +use Doctrine\Common\Collections\Collection; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents; @@ -41,6 +42,25 @@ public function preSetData(FormEvent $event): void } } + private function translationIsEmpty($translation): bool + { + // default + if (empty($translation)) { + return true; + } + + // knp + if ( + \is_object($translation) + && method_exists($translation, 'isEmpty') + && $translation->isEmpty() + ) { + return true; + } + + return false; + } + public function submit(FormEvent $event): void { $form = $event->getForm(); @@ -50,10 +70,16 @@ public function submit(FormEvent $event): void foreach ($data as $locale => $translation) { // Remove useless Translation object - if ((method_exists($translation, 'isEmpty') && $translation->isEmpty() && !\in_array($locale, $formOptions['required_locales'], true)) // Knp - || empty($translation) // Default - ) { - $data->removeElement($translation); + if ($this->translationIsEmpty($translation) && !\in_array($locale, $formOptions['required_locales'], true)) { + if (\is_array($data) && \array_key_exists($locale, $data)) { + unset($data[$locale]); + $event->setData($data); + } + + if ($data instanceof Collection) { + $data->removeElement($translation); + } + continue; } diff --git a/tests/Fixtures/DTO/Form/ProductTranslationType.php b/tests/Fixtures/DTO/Form/ProductTranslationType.php new file mode 100644 index 0000000..d79e1e9 --- /dev/null +++ b/tests/Fixtures/DTO/Form/ProductTranslationType.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace A2lix\TranslationFormBundle\Tests\Fixtures\DTO\Form; + +use A2lix\TranslationFormBundle\Tests\Fixtures\DTO\ProductTranslation; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class ProductTranslationType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $builder + ->add('title') + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => ProductTranslation::class, + ]); + } +} diff --git a/tests/Fixtures/DTO/Product.php b/tests/Fixtures/DTO/Product.php new file mode 100644 index 0000000..656f934 --- /dev/null +++ b/tests/Fixtures/DTO/Product.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace A2lix\TranslationFormBundle\Tests\Fixtures\DTO; + +class Product +{ + /** + * @var int + */ + public $id; + + /** + * @var array + */ + public $translations = []; + + /** + * @var string + */ + public $url; +} diff --git a/tests/Fixtures/DTO/ProductTranslation.php b/tests/Fixtures/DTO/ProductTranslation.php new file mode 100644 index 0000000..4323c4e --- /dev/null +++ b/tests/Fixtures/DTO/ProductTranslation.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace A2lix\TranslationFormBundle\Tests\Fixtures\DTO; + +class ProductTranslation +{ + /** + * @var int + */ + protected $id; + + /** + * @var string + */ + protected $locale; + + /** + * @var string|null + */ + protected $title; + + public function getId(): ?int + { + return $this->id; + } + + public function getLocale(): string + { + return $this->locale; + } + + public function setLocale(string $locale): self + { + $this->locale = $locale; + + return $this; + } + + public function getTitle(): ?string + { + return $this->title; + } + + public function setTitle(?string $title): self + { + $this->title = $title; + + return $this; + } +} diff --git a/tests/Fixtures/Entity/MediaLocalize.php b/tests/Fixtures/Entity/MediaLocalize.php index 5c45690..e70959d 100644 --- a/tests/Fixtures/Entity/MediaLocalize.php +++ b/tests/Fixtures/Entity/MediaLocalize.php @@ -14,12 +14,15 @@ namespace A2lix\TranslationFormBundle\Tests\Fixtures\Entity; use Doctrine\ORM\Mapping as ORM; +use Knp\DoctrineBehaviors\Model\Translatable\TranslationTrait; /** * @ORM\Entity */ class MediaLocalize { + use TranslationTrait; + /** * @ORM\Id * @ORM\Column(type="integer") diff --git a/tests/Fixtures/Entity/Product.php b/tests/Fixtures/Entity/Product.php index 63457c4..0119517 100644 --- a/tests/Fixtures/Entity/Product.php +++ b/tests/Fixtures/Entity/Product.php @@ -50,7 +50,7 @@ class Product protected $medias; /** - * @ORM\OneToMany(targetEntity="ProductTranslation", mappedBy="object", indexBy="locale", cascade={"all"}, orphanRemoval=true) + * @ORM\OneToMany(targetEntity="ProductTranslation", mappedBy="translatable", indexBy="locale", cascade={"all"}, orphanRemoval=true) */ protected $translations; diff --git a/tests/Form/Type/TranslationsFormsTypeSimpleDTOTest.php b/tests/Form/Type/TranslationsFormsTypeSimpleDTOTest.php new file mode 100644 index 0000000..5881a80 --- /dev/null +++ b/tests/Form/Type/TranslationsFormsTypeSimpleDTOTest.php @@ -0,0 +1,222 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace A2lix\TranslationFormBundle\Tests\Form\Type; + +use A2lix\TranslationFormBundle\Form\Type\TranslationsFormsType; +use A2lix\TranslationFormBundle\Tests\Fixtures\DTO\Form\ProductTranslationType; +use A2lix\TranslationFormBundle\Tests\Fixtures\DTO\Product; +use A2lix\TranslationFormBundle\Tests\Fixtures\DTO\ProductTranslation; +use A2lix\TranslationFormBundle\Tests\Form\TypeTestCase; +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\PreloadedExtension; + +/** + * @internal + */ +final class TranslationsFormsTypeSimpleDTOTest extends TypeTestCase +{ + protected $locales = ['en', 'fr', 'de']; + protected $defaultLocale = 'en'; + protected $requiredLocales = ['en', 'fr']; + + /** + * @dataProvider provideEmptyDETranslationCases + */ + public function testEmptyTranslation(array $formData, Product $productExpected): void + { + $form = $this->factory->createBuilder(FormType::class, new Product()) + ->add('translations', TranslationsFormsType::class, [ + 'form_type' => ProductTranslationType::class, + ]) + ->getForm() + ; + + $form->submit($formData); + static::assertTrue($form->isSynchronized()); + static::assertEquals($productExpected, $form->getData()); + } + + public function provideEmptyDETranslationCases(): iterable + { + $product = new Product(); + $product->translations['en'] = (new ProductTranslation()) + ->setTitle('title en') + ->setLocale('en') + ; + $product->translations['fr'] = (new ProductTranslation()) + ->setTitle('title fr') + ->setLocale('fr') + ; + + yield 'Translation DE is "null"' => [ + [ + 'translations' => [ + 'en' => [ + 'title' => 'title en', + ], + 'fr' => [ + 'title' => 'title fr', + ], + 'de' => null, + ], + ], + $product, + ]; + + yield 'Translation DE is an empty string' => [ + [ + 'translations' => [ + 'en' => [ + 'title' => 'title en', + ], + 'fr' => [ + 'title' => 'title fr', + ], + 'de' => '', + ], + ], + $product, + ]; + + yield 'Translation DE is "false"' => [ + [ + 'translations' => [ + 'en' => [ + 'title' => 'title en', + ], + 'fr' => [ + 'title' => 'title fr', + ], + 'de' => false, + ], + ], + $product, + ]; + + yield 'Translation DE does\'t exist' => [ + [ + 'translations' => [ + 'en' => [ + 'title' => 'title en', + ], + 'fr' => [ + 'title' => 'title fr', + ], + ], + ], + $product, + ]; + } + + /** + * @dataProvider provideEmptyENTranslationCases + */ + public function testEmptyAndRequiredTranslation(array $formData, Product $productExpected): void + { + $form = $this->factory->createBuilder(FormType::class, new Product()) + ->add('translations', TranslationsFormsType::class, [ + 'form_type' => ProductTranslationType::class, + ]) + ->getForm() + ; + + $form->submit($formData); + static::assertTrue($form->isSynchronized()); + static::assertEquals($productExpected, $form->getData()); + } + + public function provideEmptyENTranslationCases(): iterable + { + $product = new Product(); + $product->translations['en'] = (new ProductTranslation()) + ->setLocale('en') + ; + $product->translations['fr'] = (new ProductTranslation()) + ->setTitle('title fr') + ->setLocale('fr') + ; + $product->translations['de'] = (new ProductTranslation()) + ->setTitle('title de') + ->setLocale('de') + ; + + yield 'Translation EN is "null"' => [ + [ + 'translations' => [ + 'en' => null, + 'fr' => [ + 'title' => 'title fr', + ], + 'de' => [ + 'title' => 'title de', + ], + ], + ], + $product, + ]; + + yield 'Translation EN is an empty string' => [ + [ + 'translations' => [ + 'en' => null, + 'fr' => [ + 'title' => 'title fr', + ], + 'de' => [ + 'title' => 'title de', + ], + ], + ], + $product, + ]; + + yield 'Translation EN is "false"' => [ + [ + 'translations' => [ + 'en' => false, + 'fr' => [ + 'title' => 'title fr', + ], + 'de' => [ + 'title' => 'title de', + ], + ], + ], + $product, + ]; + + yield 'Translation EN does\'t exist' => [ + [ + 'translations' => [ + 'fr' => [ + 'title' => 'title fr', + ], + 'de' => [ + 'title' => 'title de', + ], + ], + ], + $product, + ]; + } + + protected function getExtensions(): array + { + $translationsFormsType = $this->getConfiguredTranslationsFormsType($this->locales, $this->defaultLocale, $this->requiredLocales); + + return [new PreloadedExtension([ + $translationsFormsType, + ], [])]; + } +} diff --git a/tests/Form/Type/TranslationsFormsTypeSimpleTest.php b/tests/Form/Type/TranslationsFormsTypeSimpleTest.php index 986d52e..7c39a0e 100644 --- a/tests/Form/Type/TranslationsFormsTypeSimpleTest.php +++ b/tests/Form/Type/TranslationsFormsTypeSimpleTest.php @@ -162,6 +162,98 @@ public function testEditionForm(Product $product): void } } + public function testEmptyTranslation(): void + { + $form = $this->factory->createBuilder(FormType::class, new Product()) + ->add('url') + ->add('medias', TranslationsFormsType::class, [ + 'form_type' => MediaLocalizeType::class, + ]) + ->add('save', SubmitType::class) + ->getForm() + ; + + $mediaEn = new MediaLocalize(); + $mediaEn->setLocale('en') + ->setUrl('http://en') + ->setDescription('desc en') + ; + $mediaFr = new MediaLocalize(); + $mediaFr->setLocale('fr') + ->setUrl('http://fr') + ->setDescription('desc fr') + ; + + $product = new Product(); + $product->setUrl('a2lix.fr') + ->addMedia($mediaEn) + ->addMedia($mediaFr) + ; + + $formData = [ + 'url' => 'a2lix.fr', + 'medias' => [ + 'en' => [ + 'url' => 'http://en', + 'description' => 'desc en', + ], + 'fr' => [ + 'url' => 'http://fr', + 'description' => 'desc fr', + ], + 'de' => [ + 'url' => '', + 'description' => '', + ], + ], + ]; + + $form->submit($formData); + static::assertTrue($form->isSynchronized()); + static::assertEquals($product, $form->getData()); + } + + public function testEmptyAndRequiredTranslation(): void + { + $form = $this->factory->createBuilder(FormType::class, new Product()) + ->add('url') + ->add('medias', TranslationsFormsType::class, [ + 'form_type' => MediaLocalizeType::class, + ]) + ->add('save', SubmitType::class) + ->getForm() + ; + + $mediaEn = new MediaLocalize(); + $mediaEn->setLocale('en'); + + $mediaFr = new MediaLocalize(); + $mediaFr->setLocale('fr') + ->setUrl('http://fr') + ->setDescription('desc fr') + ; + + $product = new Product(); + $product->setUrl('a2lix.fr') + ->addMedia($mediaEn) + ->addMedia($mediaFr) + ; + + $formData = [ + 'url' => 'a2lix.fr', + 'medias' => [ + 'fr' => [ + 'url' => 'http://fr', + 'description' => 'desc fr', + ], + ], + ]; + + $form->submit($formData); + static::assertTrue($form->isSynchronized()); + static::assertEquals($product, $form->getData()); + } + protected function getExtensions(): array { $translationsFormsType = $this->getConfiguredTranslationsFormsType($this->locales, $this->defaultLocale, $this->requiredLocales);