From 2d8c4a71b4add0c3d5c5ff50b149dfd2470d7123 Mon Sep 17 00:00:00 2001 From: Richard Henkenjohann Date: Tue, 23 Aug 2022 17:45:21 +0200 Subject: [PATCH 1/4] Describe relation tables --- docs/dev/reference/widgets/picker.md | 73 ++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/docs/dev/reference/widgets/picker.md b/docs/dev/reference/widgets/picker.md index 83df0c4a1..5c1b91b30 100644 --- a/docs/dev/reference/widgets/picker.md +++ b/docs/dev/reference/widgets/picker.md @@ -99,3 +99,76 @@ a serialized array. Since you do not know the length in advance, a blob column i This picker is used for content element and article include content element as well as the article teaser content element in order to pick and preview the referenced element or article. + +## Managing Many-To-Many or One-To-Many relations with association tables + +With `'eval.multiple' => true`, Contao writes the picker value to the database as a serialized string. You might, however, want to maintain association tables, especially if you are using Doctrine Entities internally. + +To make the DCA picker compatible with association tables, you have to disable saving the picker and maintain the relations on your own via [load callback](/reference/dca/callbacks/#fields-field-load) and [save callback](/reference/dca/callbacks/#fields-field-load). + +First, disable saving the dca picker: + +```php +// ... +'myContentElements' => [ + 'inputType' => 'picker', + 'eval' => [ + 'doNotSaveEmpty' => true, + ], + // ... +], +// ... +``` + +Second, maintain the relations via callbacks (this example assumes Doctrine Entities internally): + + +```php + +use Contao\CoreBundle\DependencyInjection\Attribute\AsCallback; + +class ContentElementsPickerListener +{ + public function __construct(private \App\Repository\ArticleRepository $articleRepository, private \Doctrine\ORM\EntityManagerInterface $em) + { + } + + #[AsCallback(table: 'tl_my_article', target: 'fields.elements.load')] + public function loadContentElements($value, \Contao\DataContainer $dc = null): string + { + if (!$dc || !$dc->id) { + return ''; + } + + /** @var \App\Entity\Article|null $article */ + $article = $this->articleRepository->find($dc->id); + + if (null === $article) { + return ''; + } + + return serialize(array_map(static fn (\App\Entity\ContentElement $element) => $element->getId(), $article->getContentElements()->toArray())); + } + + #[AsCallback(table: 'tl_my_article', target: 'fields.categories.save')] + public function saveCategories($value, \Contao\DataContainer $dc = null) + { + if (!$dc || !$dc->id) { + return null; + } + + /** @var \App\Entity\Article|null $article */ + $article = $this->articleRepository->find($dc->id); + + if (null === $article) { + return null; + } + + $elements = StringUtil::deserialize($value, true); + + $article->setContentElements(array_map(static fn (int $elmentId) => $this->em->getReference(\App\Entity\ContentElement::class, $elementId), $elements)); + + return null; + } +} +``` From 919c442a206368139ed244bb3015d18e771949bf Mon Sep 17 00:00:00 2001 From: Richard Henkenjohann Date: Tue, 23 Aug 2022 18:35:59 +0200 Subject: [PATCH 2/4] Update picker.md --- docs/dev/reference/widgets/picker.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/reference/widgets/picker.md b/docs/dev/reference/widgets/picker.md index 5c1b91b30..c1c3f1d44 100644 --- a/docs/dev/reference/widgets/picker.md +++ b/docs/dev/reference/widgets/picker.md @@ -166,7 +166,7 @@ class ContentElementsPickerListener $elements = StringUtil::deserialize($value, true); - $article->setContentElements(array_map(static fn (int $elmentId) => $this->em->getReference(\App\Entity\ContentElement::class, $elementId), $elements)); + $article->setContentElements(array_map(fn (int $elmentId) => $this->em->getReference(\App\Entity\ContentElement::class, $elementId), $elements)); return null; } From 13761115ed74d654ad14145c462dde0f97c183cb Mon Sep 17 00:00:00 2001 From: fritzmg Date: Mon, 31 Oct 2022 08:51:48 +0000 Subject: [PATCH 3/4] code style --- docs/dev/reference/widgets/picker.md | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/docs/dev/reference/widgets/picker.md b/docs/dev/reference/widgets/picker.md index c1c3f1d44..707120de9 100644 --- a/docs/dev/reference/widgets/picker.md +++ b/docs/dev/reference/widgets/picker.md @@ -100,6 +100,7 @@ a serialized array. Since you do not know the length in advance, a blob column i This picker is used for content element and article include content element as well as the article teaser content element in order to pick and preview the referenced element or article. + ## Managing Many-To-Many or One-To-Many relations with association tables With `'eval.multiple' => true`, Contao writes the picker value to the database as a serialized string. You might, however, want to maintain association tables, especially if you are using Doctrine Entities internally. @@ -124,40 +125,45 @@ Second, maintain the relations via callbacks (this example assumes Doctrine Enti ```php - +// src/EventListener/DataContainer/ContentElementsPickerListener.php +use App\Entity\Article; +use App\Entity\ContentElement; +use App\Repository\ArticleRepository; use Contao\CoreBundle\DependencyInjection\Attribute\AsCallback; +use Contao\DataContainer; +use Doctrine\ORM\EntityManagerInterface; class ContentElementsPickerListener { - public function __construct(private \App\Repository\ArticleRepository $articleRepository, private \Doctrine\ORM\EntityManagerInterface $em) + public function __construct(private ArticleRepository $articleRepository, private EntityManagerInterface $em) { } #[AsCallback(table: 'tl_my_article', target: 'fields.elements.load')] - public function loadContentElements($value, \Contao\DataContainer $dc = null): string + public function loadContentElements($value, DataContainer $dc = null): string { if (!$dc || !$dc->id) { return ''; } - /** @var \App\Entity\Article|null $article */ + /** @var Article|null $article */ $article = $this->articleRepository->find($dc->id); if (null === $article) { return ''; } - return serialize(array_map(static fn (\App\Entity\ContentElement $element) => $element->getId(), $article->getContentElements()->toArray())); + return serialize(array_map(static fn (ContentElement $element) => $element->getId(), $article->getContentElements()->toArray())); } #[AsCallback(table: 'tl_my_article', target: 'fields.categories.save')] - public function saveCategories($value, \Contao\DataContainer $dc = null) + public function saveCategories($value, DataContainer $dc = null) { if (!$dc || !$dc->id) { return null; } - /** @var \App\Entity\Article|null $article */ + /** @var Article|null $article */ $article = $this->articleRepository->find($dc->id); if (null === $article) { @@ -166,7 +172,7 @@ class ContentElementsPickerListener $elements = StringUtil::deserialize($value, true); - $article->setContentElements(array_map(fn (int $elmentId) => $this->em->getReference(\App\Entity\ContentElement::class, $elementId), $elements)); + $article->setContentElements(array_map(fn (int $elmentId) => $this->em->getReference(ContentElement::class, $elementId), $elements)); return null; } From bd08e1f83f1a81f560f98e7298c05e8d627cffd3 Mon Sep 17 00:00:00 2001 From: fritzmg Date: Mon, 31 Oct 2022 11:01:57 +0000 Subject: [PATCH 4/4] add missing use statement --- docs/dev/reference/widgets/picker.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/dev/reference/widgets/picker.md b/docs/dev/reference/widgets/picker.md index 707120de9..f012fd440 100644 --- a/docs/dev/reference/widgets/picker.md +++ b/docs/dev/reference/widgets/picker.md @@ -131,6 +131,7 @@ use App\Entity\ContentElement; use App\Repository\ArticleRepository; use Contao\CoreBundle\DependencyInjection\Attribute\AsCallback; use Contao\DataContainer; +use Contao\StringUtil; use Doctrine\ORM\EntityManagerInterface; class ContentElementsPickerListener