I have simple many-to-many relation between Article
and Tag
entities. I want to create a new article using FormType and associate tags with it. But the case is: I want to associate tags that may not exist yet.
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('title', TextType::class)
->add('tags', EntityType::class, [
'class' => Tag::class,
'multiple' => true
])
;
}
This FormType generates a multi select form for existing tags only. But I want to have a <textarea>
field, where users can put existing and not existing tags. Then after form submission, existing tags would be associated with the new article, and not existing tags first would be added and then associated with a new article.
I'm pretty new in Symfony world, so excuse me if my problem is trivial.
CodePudding user response:
First, sorry for my English. I will give you an example based on Embarazada
and EtiquetaAspectoEmbarazada
entities (Many to many relationship) and the use of
CodePudding user response:
You should use several tricks to reach it.
- Put to options
choices
all existing tags but as a relationArticleHasTag
. - Write some logic for deleting orphan entities on submit action.
I can show you how it works.
look at choices
option and submit the fragment here.
#[Route(path: '/article/{articleId}', methods: ['GET', 'POST'])]
public function articleEdit(string $articleId, Request $request): Response
{
$article = $this->entityManager->find(Article::class, $articleId);
if (!$article) {
throw new NotFoundHttpException();
}
$originalTags = new ArrayCollection($article->getArticleHasTagList()->toArray());
$formBuilder = $this->createFormBuilder($article, [
'data_class' => Article::class,
]);
$formBuilder->add('title', TextType::class);
$formBuilder->add('articleHasTagList', EntityType::class, [
'class' => ArticleHasTag::class,
'choice_label' => 'tag.name',
'choice_value' => 'tag.id',
'multiple' => true,
'choices' => (function (Article $article) {
$articleHasTagList = clone $article->getArticleHasTagList();
$tags = $this->entityManager->getRepository(Tag::class)->findAll();
foreach ($tags as $tag) {
/** @var ArticleHasTag[] $articleHasTagList */
foreach ($articleHasTagList as $articleHasTag) {
if ($tag === $articleHasTag->getTag()) {
continue 2;
}
}
$articleHasTag = new ArticleHasTag();
$articleHasTag->setArticle($article);
$articleHasTag->setTag($tag);
$articleHasTagList->add($articleHasTag);
}
return $articleHasTagList;
})($article),
]);
$formBuilder->add('submit', SubmitType::class);
$form = $formBuilder->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->entityManager->persist($article);
/** @var ArticleHasTag[] $originalTags */
foreach ($originalTags as $articleHasTag) {
if (!$article->getArticleHasTagList()->contains($articleHasTag)) {
$this->entityManager->remove($articleHasTag);
}
}
foreach ($article->getArticleHasTagList() as $articleHasTag) {
$this->entityManager->persist($articleHasTag);
}
$this->entityManager->flush();
return $this->redirectToRoute('app_app_articleedit', [
'articleId' => $articleId,
]);
}
return $this->render('base.html.twig', [
'form' => $form->createView(),
]);
}
of course, you also should make entities.
// src/Entity/Article.php
declare(strict_types=1);
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\OneToMany;
#[Entity]
class Article
{
#[Id]
#[Column(type: 'bigint')]
#[GeneratedValue]
private string $id;
#[Column]
private string $title;
/**
* @var Collection<int, ArticleHasTag>
*/
#[OneToMany(mappedBy: 'article', targetEntity: ArticleHasTag::class, fetch: 'EAGER')]
private Collection $articleHasTagList;
public function __construct()
{
$this->articleHasTagList = new ArrayCollection();
}
public function getId(): string
{
return $this->id;
}
public function setId(string $id): void
{
$this->id = $id;
}
public function getTitle(): string
{
return $this->title;
}
public function setTitle(string $title): void
{
$this->title = $title;
}
public function addArticleHasTagList(ArticleHasTag $articleHasTag): void
{
$articleHasTag->getTag()->addTagHasArticleList($articleHasTag);
$articleHasTag->setArticle($this);
$this->articleHasTagList->add($articleHasTag);
}
public function removeArticleHasTagList(ArticleHasTag $articleHasTag): void
{
$articleHasTag->getTag()->removeTagHasArticleList($articleHasTag);
$this->articleHasTagList->removeElement($articleHasTag);
}
public function getArticleHasTagList(): Collection
{
return $this->articleHasTagList;
}
}
// src/Entity/ArticleHasTag.php
declare(strict_types=1);
namespace App\Entity;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\Index;
use Doctrine\ORM\Mapping\JoinColumn;
use Doctrine\ORM\Mapping\ManyToOne;
use Doctrine\ORM\Mapping\OneToMany;
use Doctrine\ORM\Mapping\Table;
use Doctrine\ORM\Mapping\UniqueConstraint;
#[Entity]
#[UniqueConstraint(name: 'uniqArticleIdTagId', columns: ['article_id', 'tag_id'])]
#[Index(columns: ['article_id'], name: 'idxArticleId')]
#[Index(columns: ['tag_id'], name: 'idxTagId')]
class ArticleHasTag
{
#[Id]
#[Column(type: 'bigint')]
#[GeneratedValue]
private string $id;
#[ManyToOne(targetEntity: Article::class, fetch: 'EAGER', inversedBy: 'articleHasTagList')]
#[JoinColumn(nullable: false)]
private Article $article;
#[ManyToOne(targetEntity: Tag::class, fetch: 'EAGER', inversedBy: 'tagHasArticleList')]
#[JoinColumn(nullable: false)]
private Tag $tag;
public function getId(): string
{
return $this->id;
}
public function setId(string $id): void
{
$this->id = $id;
}
public function getArticle(): Article
{
return $this->article;
}
public function setArticle(Article $article): void
{
$this->article = $article;
}
public function getTag(): Tag
{
return $this->tag;
}
public function setTag(Tag $tag): void
{
$this->tag = $tag;
}
}
// src/Entity/Tag.php
declare(strict_types=1);
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\OneToMany;
#[Entity]
class Tag
{
#[Id]
#[Column(type: 'bigint')]
#[GeneratedValue]
private string $id;
#[Column]
private string $name;
/**
* @var Collection<int, ArticleHasTag>
*/
#[OneToMany(mappedBy: 'tag', targetEntity: ArticleHasTag::class, fetch: 'EAGER')]
private Collection $tagHasArticleList;
public function getId(): string
{
return $this->id;
}
public function setId(string $id): void
{
$this->id = $id;
}
public function getName(): string
{
return $this->name;
}
public function setName(string $name): void
{
$this->name = $name;
}
public function __construct()
{
$this->tagHasArticleList = new ArrayCollection();
}
public function addTagHasArticleList(ArticleHasTag $articleHasTag): void
{
if (!$this->tagHasArticleList->contains($articleHasTag)) {
$this->tagHasArticleList->add($articleHasTag);
}
}
public function removeTagHasArticleList(ArticleHasTag $articleHasTag): void
{
if ($this->tagHasArticleList->contains($articleHasTag)) {
$this->tagHasArticleList->removeElement($articleHasTag);
}
}
public function getTagHasArticleList(): Collection
{
return $this->tagHasArticleList;
}
}