Bonjour.

Je développe à titre perso un blog de recettes de cuisine spécialisées pour les personnes allergique.
Ma table recette à une relation en oneToMany avec deux autres tables (ingrédients et étapes)

LA classe recette

<?php

namespace App\Entity;

use App\Repository\CookingReceipeRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;

/**
 * @ORM\Entity(repositoryClass=CookingReceipeRepository::class)
 * @UniqueEntity(fields={"name"}, message="Cette recette existe déjà")
 */
class CookingReceipe
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     * @Assert\Length(
     *      min = 2,
     *      max = 200,
     *      minMessage = "Votre titre doit faire au moins {{ limit }} caratères",
     *      maxMessage = "Votre titre ne doit pas dépasser {{ limit }} caactères"
     * )

     */
    private $name;

    /**
     * @ORM\Column(type="string", length=255)
     * @Gedmo\Slug(fields={"name"})
     */
    private $slug;

    /**
     * @ORM\ManyToOne(targetEntity=Category::class, inversedBy="cookingReceipes")
     */
    private $category;

    /**
     * @ORM\ManyToOne(targetEntity=User::class, inversedBy="cookingReceipes")
     */
    private $author;

    /**
     * @ORM\Column(type="string", length=250)
     */
    private $cookingTime;

    /**
     * @ORM\Column(type="string", length=250)
     */
    private $numberOfGuests;

    /**
     * @ORM\OneToMany(targetEntity=Favorite::class, mappedBy="cookingReceipe")
     */
    private $favorites;

    /**
     * @ORM\OneToMany(targetEntity=Comment::class, mappedBy="cookingReceipe")
     */
    private $comments;

    /**
     * @ORM\ManyToOne(targetEntity=Difficulty::class, inversedBy="cookingReceipe")
     */
    private $difficulty;

    /**
     * @ORM\ManyToOne(targetEntity=Price::class, inversedBy="cookingReceipe")
     */
    private $price;

    /**
     * @ORM\OneToMany(targetEntity=SetpsCooking::class, mappedBy="cookingReceipe")
     */
    private $setpsCookings;

    /**
     * @ORM\OneToMany(targetEntity=ReceipeIngredients::class, mappedBy="cookingReceipe" , cascade={"persist"})
     */
    private $receipeIngredients;

    /**
     * @ORM\Column(type="datetime")
     * @Gedmo\Timestampable(on="create")
     */
    private $createdAt;

    /**
     * @ORM\Column(type="datetime", nullable=true)
     * @Gedmo\Timestampable(on="update")
     */
    private $updatedAt;

    public function __construct()
    {
        $this->favorites = new ArrayCollection();
        $this->comments = new ArrayCollection();
        $this->setpsCookings = new ArrayCollection();
        $this->receipeIngredients = new ArrayCollection();
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }

    public function getSlug(): ?string
    {
        return $this->slug;
    }

    public function setSlug(string $slug): self
    {
        $this->slug = $slug;

        return $this;
    }

    public function getCategory(): ?Category
    {
        return $this->category;
    }

    public function setCategory(?Category $category): self
    {
        $this->category = $category;

        return $this;
    }

    public function getAuthor(): ?User
    {
        return $this->author;
    }

    public function setAuthor(?User $author): self
    {
        $this->author = $author;

        return $this;
    }

    public function getCookingTime(): ?string
    {
        return $this->cookingTime;
    }

    public function setCookingTime(string $cookingTime): self
    {
        $this->cookingTime = $cookingTime;

        return $this;
    }

    public function getNumberOfGuests(): ?string
    {
        return $this->numberOfGuests;
    }

    public function setNumberOfGuests(string $numberOfGuests): self
    {
        $this->numberOfGuests = $numberOfGuests;

        return $this;
    }

    /**
     * @return Collection|Favorite[]
     */
    public function getFavorites(): Collection
    {
        return $this->favorites;
    }

    public function addFavorite(Favorite $favorite): self
    {
        if (!$this->favorites->contains($favorite)) {
            $this->favorites[] = $favorite;
            $favorite->setCookingReceipe($this);
        }

        return $this;
    }

    public function removeFavorite(Favorite $favorite): self
    {
        if ($this->favorites->removeElement($favorite)) {
            // set the owning side to null (unless already changed)
            if ($favorite->getCookingReceipe() === $this) {
                $favorite->setCookingReceipe(null);
            }
        }

        return $this;
    }

    /**
     * @return Collection|Comment[]
     */
    public function getComments(): Collection
    {
        return $this->comments;
    }

    public function addComment(Comment $comment): self
    {
        if (!$this->comments->contains($comment)) {
            $this->comments[] = $comment;
            $comment->setCookingReceipe($this);
        }

        return $this;
    }

    public function removeComment(Comment $comment): self
    {
        if ($this->comments->removeElement($comment)) {
            // set the owning side to null (unless already changed)
            if ($comment->getCookingReceipe() === $this) {
                $comment->setCookingReceipe(null);
            }
        }

        return $this;
    }

    public function getDifficulty(): ?Difficulty
    {
        return $this->difficulty;
    }

    public function setDifficulty(?Difficulty $difficulty): self
    {
        $this->difficulty = $difficulty;

        return $this;
    }

    public function getPrice(): ?Price
    {
        return $this->price;
    }

    public function setPrice(?Price $price): self
    {
        $this->price = $price;

        return $this;
    }

    /**
     * @return Collection|SetpsCooking[]
     */
    public function getSetpsCookings(): Collection
    {
        return $this->setpsCookings;
    }

    public function addSetpsCooking(SetpsCooking $setpsCooking): self
    {
        if (!$this->setpsCookings->contains($setpsCooking)) {
            $this->setpsCookings[] = $setpsCooking;
            $setpsCooking->setCookingReceipe($this);
        }

        return $this;
    }

    public function removeSetpsCooking(SetpsCooking $setpsCooking): self
    {
        if ($this->setpsCookings->removeElement($setpsCooking)) {
            // set the owning side to null (unless already changed)
            if ($setpsCooking->getCookingReceipe() === $this) {
                $setpsCooking->setCookingReceipe(null);
            }
        }

        return $this;
    }

    /**
     * @return Collection|ReceipeIngredients[]
     */
    public function getReceipeIngredients(): Collection
    {
        return $this->receipeIngredients;
    }

    public function addReceipeIngredient(ReceipeIngredients $receipeIngredient): self
    {
        if (!$this->receipeIngredients->contains($receipeIngredient)) {
            $this->receipeIngredients[] = $receipeIngredient;
            $receipeIngredient->setCookingReceipe($this);
        }

        return $this;
    }

    public function removeReceipeIngredient(ReceipeIngredients $receipeIngredient): self
    {
        if ($this->receipeIngredients->removeElement($receipeIngredient)) {
            // set the owning side to null (unless already changed)
            if ($receipeIngredient->getCookingReceipe() === $this) {
                $receipeIngredient->setCookingReceipe(null);
            }
        }

        return $this;
    }

    public function getCreatedAt(): ?\DateTimeInterface
    {
        return $this->createdAt;
    }

    public function setCreatedAt(\DateTimeInterface $createdAt): self
    {
        $this->createdAt = $createdAt;

        return $this;
    }

    public function getUpdatedAt(): ?\DateTimeInterface
    {
        return $this->updatedAt;
    }

    public function setUpdatedAt(?\DateTimeInterface $updatedAt): self
    {
        $this->updatedAt = $updatedAt;

        return $this;
    }
}

La classe ingrédient :

<?php

namespace App\Entity;

use App\Repository\ReceipeIngredientsRepository;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass=ReceipeIngredientsRepository::class)
 */
class ReceipeIngredients
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $receipeIngredientsName;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $quantity;

    /**
     * @ORM\Column(type="string", length=255, nullable=true)
     */
    private $measured;

    /**
     * @ORM\ManyToOne(targetEntity=CookingReceipe::class, inversedBy="receipeIngredients")
     */
    private $cookingReceipe;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getReceipeIngredientsName(): ?string
    {
        return $this->receipeIngredientsName;
    }

    public function setReceipeIngredientsName(string $receipeIngredientsName): self
    {
        $this->receipeIngredientsName = $receipeIngredientsName;

        return $this;
    }

    public function getQuantity(): ?string
    {
        return $this->quantity;
    }

    public function setQuantity(string $quantity): self
    {
        $this->quantity = $quantity;

        return $this;
    }

    public function getMeasured(): ?string
    {
        return $this->measured;
    }

    public function setMeasured(?string $measured): self
    {
        $this->measured = $measured;

        return $this;
    }

    public function getCookingReceipe(): ?CookingReceipe
    {
        return $this->cookingReceipe;
    }

    public function setCookingReceipe(?CookingReceipe $cookingReceipe): self
    {
        $this->cookingReceipe = $cookingReceipe;

        return $this;
    }
}

Le form de la classe recette

<?php

namespace App\Form;

use App\Entity\Price;
use App\Entity\Category;
use App\Entity\Difficulty;
use App\Entity\CookingReceipe;
use App\Entity\ReceipeIngredients;
use App\Form\RecipeIngredientsType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;

class CookingReceipeType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('name', TextType::class)
            ->add('cookingTime', TextType::class)
            ->add('numberOfGuests', TextType::class)
            ->add('category', EntityType::class, [
                'class' => Category::class,
                'multiple' => false,
                'expanded' => false
            ])
            ->add('difficulty', EntityType::class, [
                'class' => Difficulty::class,
                'multiple' => false,
                'expanded' => false
            ])
            ->add('price', EntityType::class, [
                'class' => Price::class,
                'multiple' => false,
                'expanded' => false
            ])
            ->add('receipeIngredients', CollectionType::class, [
                'entry_type' => RecipeIngredientsType::class,
                'allow_add' => true, // true si tu veux que l'utilisateur puisse en ajouter
                'allow_delete' => true, // true si tu veux que l'utilisateur puisse en supprimer
                'label' => 'Les ingrédients',
                'entry_options' => ['label' => false],
                'prototype' => true, //prototype : On veut qu’un prototype soit défini afin de pouvoir gérer la collection en javascript côté client.
                'by_reference' => false, //En passant cet attribut à false, on force Symfony à appeler le setter de l’entité.
            ])
            //->add('setpsCookings', TextType::class)
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => CookingReceipe::class,
        ]);
    }
}

Et celui de la table ingrédients

<?php

namespace App\Form;

use App\Entity\ReceipeIngredients;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class RecipeIngredientsType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('receipeIngredientsName')
            ->add('quantity')
            ->add('measured');
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => ReceipeIngredients::class,
        ]);
    }
}

Ce que je veux

J'ai bien cherché sur la doc symfony, mais pour l'instant je n'arrive pas à afficher une ligne contenant les items de la classe ingrédients. Sur symfony il me dit bien qu'il les attends en field

Ce que j'obtiens

Je n'ai pas d'erreurs mais bien uniquement le form de l'entité recette.
Je suis capable d'ajouter en JS de nouvelles lignes mais je n'arrive pas à en afficher une seule.

C'est un peu comme quand on s'incrit sur un site pour postuler à un emploi, qu'on a rempli son nom et son prénom, mais qu'il faut mettre ses diplomes (et donc on clique le le boutton + pour ajouter une nouvelle ligne)

D'avance merci.

2 réponses


lhapaipai
Réponse acceptée

Il me semble que c'est ce que tu dis. Avec CollectionType, sans js tu ne peux qu'éditer les ingrédients qui sont déja inclus dans ta recette. si tu veux en ajouter ou supprimer, tu es autorisé dès lors que tu as précisé allow_add/ allow_delete mais tu es obligé d'utiliser le JS.

symfony t'as simplifié la tache, tu as tout le html nécessaire dans l'attribut data-prototype. tu peux trouver comment t'y prendre ici : symfony casts

steeve971
Auteur
Réponse acceptée

Bonjour
En effet d'habitude quand j'utilise collectionType il a déjà des champs prérempli et je n'ai qu'à choisir. La ça n'affiche rien, puisqu''effectivement il n'y a rien.
Merci pour le lien je vais regarder.
Je revienrai si j'ai besoin d'aide.