Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Describe how to maintain Many-To-Many or One-To-Many relations with the DCA picker #1069

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions docs/dev/reference/widgets/picker.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,82 @@ 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
// 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 ArticleRepository $articleRepository, private EntityManagerInterface $em)
{
}

#[AsCallback(table: 'tl_my_article', target: 'fields.elements.load')]
public function loadContentElements($value, DataContainer $dc = null): string
{
if (!$dc || !$dc->id) {
return '';
}

/** @var Article|null $article */
$article = $this->articleRepository->find($dc->id);

if (null === $article) {
return '';
}

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, DataContainer $dc = null)
{
if (!$dc || !$dc->id) {
return null;
}

/** @var Article|null $article */
$article = $this->articleRepository->find($dc->id);

if (null === $article) {
return null;
}

$elements = StringUtil::deserialize($value, true);

$article->setContentElements(array_map(fn (int $elmentId) => $this->em->getReference(ContentElement::class, $elementId), $elements));

return null;
}
}
```