I use Symfony 4 and the bundle easy admin to create a form with multiple collection, here is my code :
Produit.yaml
easy_admin: entities: Produit: class: App\Entity\Produit templates: new: 'security/custom_produit.html.twig' edit: 'security/custom_produit.html.twig' form: fields: - nom - {property: 'fiche_technique_file', label: 'Fiche technique', type: 'vich_file'} - categorie - { property: 'modele1', label: 'Modèle 1', type: 'collection', type_options: {entry_type: 'App\Form\Modele1Type', allow_add: true, allow_delete: true, by_reference: false} } list: fields: ['nom', {property: 'fiche_technique', label: 'Fiche technique'}, {property: 'categorie', label: 'Catégorie'}, {property: 'modele1', label: 'Modèle 1'}
Modele1Type.php
class Modele1Type extends AbstractType{ public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('intro', CKEditorType::class) ->add('caract_tech', CKEditorType::class, ['label' => 'Caractéristique technique' ]) ->add('applications', TextareaType::class) ->add('info_comp_1', CKEditorType::class, ['label' => 'Info complémentaire 1' ]) ->add('info_comp_2', CKEditorType::class, ['label' => 'Info complémentaire 2' ]) ->add('info_comp_3', CKEditorType::class, ['label' => 'Info complémentaire 3' ]) ->add('galerie_photo', CollectionType::class, ['entry_type' => MultipleImgModele1Type::class,'prototype' => true,'allow_add' => true,'allow_delete' => true,'by_reference' => false,'required' => false,'label' => false, ]) ->add('avantages', CollectionType::class, ['entry_type' => AvantageType::class,'prototype' => true,'allow_add' => true,'allow_delete' => true,'by_reference' => false,'required' => false,'label' => false, ]) ; } public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(['data_class' => Modele1::class, ]); }}
Produit.php
/** * @ORM\Entity(repositoryClass="App\Repository\ProduitRepository") * @Vich\Uploadable() */class Produit{ /** * @ORM\Id() * @ORM\GeneratedValue() * @ORM\Column(type="integer") */ private $id; /** * @ORM\Column(type="string", length=255) */ private $nom; /** * @ORM\Column(type="string", length=255, nullable=true) */ private $fiche_technique; /** * @Assert\File( * maxSize = "5M", * mimeTypes = {"application/pdf", "application/x-pdf"}, * mimeTypesMessage = "Veuillez envoyer un fichier PDF valide" * ) * @Vich\UploadableField(mapping="produit_fiche_tech", fileNameProperty="fiche_technique") */ private $fiche_technique_file; /** * @ORM\Column(type="datetime", nullable=true) */ private $updated_at; /** * @ORM\ManyToOne(targetEntity="Categorie", inversedBy="produits") */ private $categorie; /** * @ORM\ManyToOne(targetEntity="Modele1", inversedBy="produits", cascade={"persist"}) * @ORM\JoinColumn(name="modele1_id", referencedColumnName="id", nullable=true) */ private $modele1; public function __construct() { $this->updated_at = new \DateTime(); } public function __toString() { $res = $this->nom; if($this->modele1 != null) { $res .= " - Modèle 1"; } else if($this->modele2 != null) { $res .= " - Modèle 2"; } else if($this->modele3 != null) { $res .= " - Modèle 3"; } return $res; } public function getId(): ?int { return $this->id; } public function getNom(): ?string { return $this->nom; } public function setNom(string $nom): self { $this->nom = $nom; return $this; } public function getFicheTechnique(): ?string { return $this->fiche_technique; } public function setFicheTechnique(?string $fiche_technique): self { $this->fiche_technique = $fiche_technique; return $this; } public function getUpdatedAt(): ?\DateTimeInterface { return $this->updated_at; } public function setUpdatedAt(?\DateTimeInterface $updated_at): self { $this->updated_at = $updated_at; return $this; } /** * @return mixed */ public function getFicheTechniqueFile() { return $this->fiche_technique_file; } /** * @param mixed $fiche_technique_file * @throws \Exception */ public function setFicheTechniqueFile(?File $fiche_technique_file): void { $this->fiche_technique_file = $fiche_technique_file; if($fiche_technique_file) { $this->updated_at = new \DateTime(); } } /** * @return mixed */ public function getCategorie(): ?Categorie { return $this->categorie; } /** * @param mixed $categorie */ public function setCategorie(?Categorie $categorie): void { $this->categorie = $categorie; } /** * @return mixed */ public function getModele1(): ?Modele1 { return $this->modele1; } /** * @param mixed $modele1 */ public function setModele1($modele1): self { if(empty(array_values($modele1)[0])) { $this->modele1 = null; } else { $this->modele1 = array_values($modele1)[0]; } return $this; }}
Modele1.php
/** * @ORM\Entity(repositoryClass="App\Repository\Modele1Repository") */class Modele1{ /** * @ORM\Id() * @ORM\GeneratedValue() * @ORM\Column(type="integer") */ private $id; /** * @ORM\Column(type="text") */ private $intro; /** * @ORM\Column(type="text") */ private $caract_tech; /** * @ORM\Column(type="text") */ private $applications; /** * @ORM\Column(type="text", nullable=true) */ private $info_comp_1; /** * @ORM\Column(type="text", nullable=true) */ private $info_comp_2; /** * @ORM\Column(type="text", nullable=true) */ private $info_comp_3; /** * @ORM\OneToMany(targetEntity="Produit", mappedBy="modele1", cascade={"persist"}) */ private $produits; /** * @ORM\OneToMany(targetEntity="MultipleImgModele1", mappedBy="modele1", cascade={"persist", "remove"}, orphanRemoval=true) */ private $galerie_photo; /** * @ORM\OneToMany(targetEntity="Avantage", mappedBy="modele1") */ private $avantages; public function __construct() { $this->produits = new ArrayCollection(); $this->galerie_photo = new ArrayCollection(); $this->avantages = new ArrayCollection(); } public function getId(): ?int { return $this->id; } public function getIntro(): ?string { return $this->intro; } public function setIntro(string $intro): self { $this->intro = $intro; return $this; } public function getCaractTech(): ?string { return $this->caract_tech; } public function setCaractTech(string $caract_tech): self { $this->caract_tech = $caract_tech; return $this; } public function getApplications(): ?string { return $this->applications; } public function setApplications(string $applications): self { $this->applications = $applications; return $this; } public function getInfoComp1(): ?string { return $this->info_comp_1; } public function setInfoComp1(?string $info_comp_1): self { $this->info_comp_1 = $info_comp_1; return $this; } public function getInfoComp2(): ?string { return $this->info_comp_2; } public function setInfoComp2(?string $info_comp_2): self { $this->info_comp_2 = $info_comp_2; return $this; } public function getInfoComp3(): ?string { return $this->info_comp_3; } public function setInfoComp3(?string $info_comp_3): self { $this->info_comp_3 = $info_comp_3; return $this; } /** * @return Collection|Produit */ public function getProduits(): Collection { return $this->produits; } public function addProduit(Produit $produits): self { if (!$this->produits->contains($produits)) { $this->produits[] = $produits; $produits->setModele1($this); } return $this; } public function removeProduit(Produit $produits): self { if ($this->produits->contains($produits)) { $this->produits->removeElement($produits); // set the owning side to null (unless already changed) if ($produits->getModele1() === $this) { $produits->setModele1(null); } } return $this; } /** * @return Collection|MultipleImgModele1[] */ public function getGaleriePhoto(): Collection { return $this->galerie_photo; } public function addGaleriePhoto(MultipleImgModele1 $galeriePhoto): self { if (!$this->galerie_photo->contains($galeriePhoto)) { $this->galerie_photo[] = $galeriePhoto; $galeriePhoto->setModele1($this); } return $this; } public function removeGaleriePhoto(MultipleImgModele1 $galeriePhoto): self { if ($this->galerie_photo->contains($galeriePhoto)) { $this->galerie_photo->removeElement($galeriePhoto); // set the owning side to null (unless already changed) if ($galeriePhoto->getModele1() === $this) { $galeriePhoto->setModele1(null); } } return $this; } /** * @return mixed */ public function getAvantages(): Collection { return $this->avantages; } public function addAvantage(Avantage $avantages): self { if (!$this->avantages->contains($avantages)) { $this->avantages[] = $avantages; $avantages->setModele1($this); } return $this; } public function removeAvantage(Avantage $avantages): self { if ($this->avantages->contains($avantages)) { $this->avantages->removeElement($avantages); // set the owning side to null (unless already changed) if ($avantages->getModele1() === $this) { $avantages->setModele1(null); } } return $this; }}
custom_produit.html.twig
{% extends '@EasyAdmin/default/edit.html.twig' %}{% block head_custom_stylesheets %} {{ encore_entry_link_tags('app') }}{% endblock %}{% block main %} {% block entity_form %} {{ form_start(form) }}<div class="container cont-modele-produit-admin mb-4"><div class="accordion" id="accordionModeles"><div class="row mb-4"> {% for model in form.modele1.vars.prototype %}<div class="col-md-12 mt-3"> {% if model.vars.name == "galerie_photo" %}<fieldset class="p-4"><legend>Galerie Photos</legend> {{ form_row(model) }}</fieldset> {% endif %} {% if model.vars.name == "avantages" %}<fieldset class="p-4"><legend>Avantages</legend> {{ form_row(model) }}</fieldset> {% endif %} {{ form_label(model) }} {{ form_widget(model) }}</div> {% endfor %} {% do form.modele1.setRendered %}</div></div></div> {{ form_end(form) }} {% endblock %}{% endblock %}{% block body_custom_javascript %} {{ encore_entry_script_tags('app') }}{% endblock %}
In the setModele1 function in the Produit entity, I had to do a little update to get the Modele1 entity data because it give me an array like that :
Produit.php on line 180:array:1 [▼"__name__" => Modele1^ {#2564 ▼ -id: null -intro: "<p>test</p>" -caract_tech: "<p>test</p>" -applications: "test" -info_comp_1: null -info_comp_2: null -info_comp_3: null -produits: ArrayCollection^ {#2577 ▶} -galerie_photo: ArrayCollection^ {#2806 ▶} -avantages: ArrayCollection^ {#2804 ▶} }]
it returned me an error because it should get the Modele1 Entity instead of an array, and now when I try to edit a Produit that have a Modele1 it give me this error :Expected argument of type "array or (\Traversable and \ArrayAccess)", "Proxies__CG__\App\Entity\Modele1" given
So why do I need to give an array instead of the Modele1 entity to get all the data and put it in the form to edit then ?
Sorry for my bad english I hope you will all undestand what I wanted to do ^^
Thank you in advance for your help !