Hello I have a question regarding the CollectionType field of symfony

  Kiến thức lập trình

Good morning!
I have an ads entity which has a title, a slug and other information, to this ads entity I added an entity named images and the image to just a single caption field which I added to the entity ads as CollectionType. But when I validate my form, only the last image is saved.
Please help me.

This is my entity ads

<?php

namespace AppEntity;

use AppRepositoryAdsListingsRepository;
use DoctrineCommonCollectionsArrayCollection;
use DoctrineCommonCollectionsCollection;
use DoctrineORMMapping as ORM;
use SymfonyComponentStringSluggerAsciiSlugger;

#[ORMEntity(repositoryClass: AdsListingsRepository::class)]
#[ORMHasLifecycleCallbacks()]
class AdsListings
{

    const PRICENOTE = [
        "" => "",
        "Ferme" => "Ferme",
        "Négociable" => "Négociable",
        "À partir de" => "À partir de",
        "Par heure" => "Par heure",
        "Par jour" => "Par jour",
        "Par semaine" => "Par semaine",
        "Par mois" => "Par mois",
        "Par an" => "Par an",
    ];
    const DEVISE = [
        "XOF/Franc CFA" => "XOF/Franc CFA",
        "GHS/ Ghana Cedi" => "GHS/ Ghana Cedi",
        "Euro / €" => "Euro / €",
        "Dollar USD / $" => "Dollar USD / $",
    ];

    #[ORMId]
    #[ORMGeneratedValue]
    #[ORMColumn]
    private ?int $id = null;

    #[ORMColumn(length: 255)]
    private ?string $title = null;

    #[ORMColumn(length: 255)]
    private ?string $slug = null;

    #[ORMColumn(nullable: true)]
    private ?float $price = null;

    #[ORMColumn(length: 100, nullable: true)]
    private ?string $devise = null;

    #[ORMColumn(length: 100, nullable: true)]
    private ?string $priceNote = null;

    #[ORMOneToOne(inversedBy: 'adsListings', cascade: ['persist', 'remove'])]
    #[ORMJoinColumn(nullable: false)]
    private ?References $reference = null;

    #[ORMOneToMany(targetEntity: Images::class, mappedBy: 'Ads', cascade: ['persist', 'remove'],orphanRemoval: true)]
    private Collection $images;

    public function __construct()
    {
        $this->images = new ArrayCollection();
    }

    #[ORMPrePersist()]
    #[ORMPreUpdate()]
    public function initializeSlug()
    {
        if (empty($this->slug)){
            $slugger = new AsciiSlugger();
            $this->slug = $slugger->slug($this->title)->lower();
        }
        return $this;
    }

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



    public function getTitle(): ?string
    {
        return $this->title;
    }

    public function setTitle(string $title): static
    {
        $this->title = $title;

        return $this;
    }

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

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

        return $this;
    }

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

    public function setPrice(?float $price): static
    {
        $this->price = $price;

        return $this;
    }

    public function getDevise(): ?string
    {
        return $this->devise;
    }

    public function setDevise(?string $devise): static
    {
        $this->devise = $devise;

        return $this;
    }

    public function getPriceNote(): ?string
    {
        return $this->priceNote;
    }

    public function setPriceNote(?string $priceNote): static
    {
        $this->priceNote = $priceNote;

        return $this;
    }

    public function getReference(): ?References
    {
        return $this->reference;
    }

    public function setReference(References $reference): static
    {
        $this->reference = $reference;

        return $this;
    }

    /**
     * @return Collection<int, Images>
     */
    public function getImages(): Collection
    {
        return $this->images;
    }

    public function addImage(Images $image): static
    {
        if (!$this->images->contains($image)) {
            $this->images->add($image);
            $image->setAds($this);
        }

        return $this;
    }

    public function removeImage(Images $image): static
    {
        if ($this->images->removeElement($image)) {
            // set the owning side to null (unless already changed)
            if ($image->getAds() === $this) {
                $image->setAds(null);
            }
        }

        return $this;
    }
}


here is my AdsType

<?php

namespace AppForm;

use AppEntityAdsListings;
use SymfonyComponentFormAbstractType;
use SymfonyComponentFormExtensionCoreTypeChoiceType;
use SymfonyComponentFormExtensionCoreTypeCollectionType;
use SymfonyComponentFormExtensionCoreTypeNumberType;
use SymfonyComponentFormExtensionCoreTypeTextType;
use SymfonyComponentFormFormBuilderInterface;
use SymfonyComponentOptionsResolverOptionsResolver;

class AdsListingsFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('title', TextType::class,[
                'label' => "Titre de mon annonce :"
            ])
            ->add('price', NumberType::class,[
                'label' => "Prix / Montant :",
                'required'   => false,
            ])
            ->add('devise', ChoiceType::class,[
                'label' => "Devise :",
                'choices' => AdsListings::DEVISE,
                'required'   => false,
            ])
            ->add('PriceNote', ChoiceType::class,[
                'choices' => AdsListings::PRICENOTE,
                'label' => "Note sur le prix :"
            ])
            ->add('images', CollectionType::class,[
                'entry_type' => ImagesFormType::class,
                'allow_add' => true,
                'allow_delete' => true,
            ])
        ;
    }

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


here is my ImagesEntity

<?php

namespace AppEntity;

use AppRepositoryImagesRepository;
use DoctrineORMMapping as ORM;

#[ORMEntity(repositoryClass: ImagesRepository::class)]
class Images
{
    #[ORMId]
    #[ORMGeneratedValue]
    #[ORMColumn]
    private ?int $id = null;

    #[ORMColumn(length: 255, nullable: false)]
    private ?string $caption = null;

    #[ORMManyToOne(inversedBy: 'images')]
    private ?AdsListings $Ads = null;

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

    public function getCaption(): ?string
    {
        return $this->caption;
    }

    public function setCaption(?string $caption): self
    {
        $this->caption = $caption;

        return $this;
    }

    public function getAds(): ?AdsListings
    {
        return $this->Ads;
    }

    public function setAds(?AdsListings $Ads): self
    {
        $this->Ads = $Ads;

        return $this;
    }
}


here is my ImagesType

<?php

namespace AppForm;

use AppEntityAdsListings;
use AppEntityImages;
use SymfonyBridgeDoctrineFormTypeEntityType;
use SymfonyComponentFormAbstractType;
use SymfonyComponentFormExtensionCoreTypeTextType;
use SymfonyComponentFormFormBuilderInterface;
use SymfonyComponentOptionsResolverOptionsResolver;

class ImagesFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('caption', TextType::class, [
                'label' => "Caption"
            ])

        ;
    }

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


Here is my Twig view


{% extends 'base.html.twig' %}
{% block title %} Louer un appartement {% endblock %}
{% block stylesheets %}
    <link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/css/select2.min.css" rel="stylesheet">
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/summernote-lite.min.css" rel="stylesheet">
    <link rel="stylesheet" href="{{ asset('assets/leaflet/leaflet.css') }}">
    <style>
        #detailsMap{
            height: 350px;
        }
    </style>

    {% include '_partials/_users/_nav.html.twig' %}
    {% form_theme appartementRentalForm _self %}
    <nav class="navbar navbar-expand-lg bg-body-light navbar-user-menu">
        <div class="container">

            <div class="collapse navbar-collapse" id="navbarNavDropdown">
                {% include '_partials/_users/_nav-users-menu.html.twig' %}
                <ul class="navbar-nav ms-auto">

                    <li class="nav-item nav-page">
                        <a class="nav-link nav-item-in" href="{{ path("app_user_profil")}}">
                            Mes annonces <i class="fa fa-add"></i>
                        </a>

                    </li>
                </ul>
            </div>

        </div>
    </nav>

    {% include "_partials/_flash.html.twig" %}

    <div class="container-fluid container-body">
        <div class="container py-4 ">
            <div class="row">
                <div class="col-sm-12">
                    <strong class="div-h4 h2">Créez votre annonce</strong>
                </div>
                <div class="col-sm-10 container-fluid mt-4 offset-sm-1 ">
                    <div class="">
                        {{ form_start(appartementRentalForm) }}
                            <fieldset class="pb-5">
                                <legend>INFORMATION DE BASE</legend>
                                <div class="container">
                                    <label for="">{{ form_label(appartementRentalForm.title) }}<sup class="text-danger">*</sup></label>
                                    {{ form_widget(appartementRentalForm.title) }}
                                    <p>
                                        <i>
                                            Les mots-clés employés dans votre titre sont importants. Ils indiquent aux acheteurs et surtout, à notre moteur de recherche, le niveau de pertinence de votre annonce. Faites de bons choix !
                                        </i>
                                    </p>
                                    <div class="row">
                                        <div class="col-4">
                                            <label for="">{{ form_label(appartementRentalForm.price) }}<sup class="text-danger">*</sup></label>
                                            {{ form_widget(appartementRentalForm.price) }}
                                        </div>
                                        <div class="col-3">
                                            <label for="">{{ form_label(appartementRentalForm.devise) }}</label>
                                            {{ form_widget(appartementRentalForm.devise) }}
                                        </div>
                                        <div class="col-2">
                                            <label for="">{{ form_label(appartementRentalForm.PriceNote) }}</label>
                                            {{ form_widget(appartementRentalForm.PriceNote) }}
                                        </div>
                                    </div>

                                </div>
                            </fieldset>

                            <fieldset class="pb-5">
                                <legend>ÉTAT & DISPONIBILITÉ</legend>
                                <div class="container">
                                    <div class="row">
                                        <div class="col-12 form-check-inline">
                                            <div class="row">
                                                {# <div class="col-2">État : <sup class="text-danger">*</sup></div>
                                                <div class="col-9">{{ form_widget(appartementRentalForm.state, {'label_attr': {'class': 'radio-inline'}}) }}</div> #}
                                            </div>

                                        </div>
                                        <div class="col-12 row mt-3">
                                            {# <div class="col-2">{{ form_label(appartementRentalForm.year) }} <sup class="text-danger">*</sup></div>
                                            <div class="col-2">{{ form_widget(appartementRentalForm.year) }}</div> #}
                                        </div>

                                        <div class="col-12 row mt-3">
                                            {# <div class="col-2">Date de disponibilité :</div>
                                            <div class="col-3">{{ form_widget(appartementRentalForm.disponibility) }}</div> #}
                                        </div>

                                    </div>
                                </div>
                            </fieldset>

                            <fieldset class="pb-5">
                                <legend>COMMODITÉ & OPTION</legend>
                                    <div class="container">

                                        <div class="row">
                                            {# Nombre de pièces :
                                            <div class="col-12 row mt-3">
                                                <div class="col-2">{{ form_label(appartementRentalForm.disponibility) }} </div>
                                                <div class="col-3">{{ form_widget(appartementRentalForm.disponibility) }}</div>
                                            </div>
                                            #}
                                            <div class="col-12 form-check-inline">
                                                <div class="row">
                                                    <div class="col-2">Ameublement :</div>
                                                    {# <div class="col-9">{{ form_widget(appartementRentalForm.furniture, {'label_attr': {'class': 'radio-inline'}}) }}</div> #}
                                                </div>

                                            </div>
                                            <div class="col-12 mt-3 form-check-inline">
                                                <div class="row">
                                                    <div class="col-2">Animaux acceptés? : </div>
                                                    {# <div class="col-9">{{ form_widget(appartementRentalForm.pet, {'label_attr': {'class': 'radio-inline'}}) }}</div> #}
                                                </div>

                                            </div>
                                            <div class="col-12 mt-3">
                                                <div class="row">
                                                    {# <div class="col-2">
                                                        <label for="">{{ form_label(appartementRentalForm.orientation) }}</label>
                                                    </div>
                                                    <div class="col-4">{{ form_widget(appartementRentalForm.orientation) }}</div> #}
                                                </div>
                                            </div>
                                            <div class="col-12 mt-3">
                                                <div class="row">
                                                    {# <div class="col-2">
                                                        <label for="">{{ form_label(appartementRentalForm.homeOptions) }}</label>
                                                    </div>
                                                    <div class="col-10">{{ form_widget(appartementRentalForm.homeOptions) }}</div> #}
                                                </div>
                                            </div>

                                            <div class="col-12 mt-3">
                                                <div class="row">
                                                    {# <div class="col-2">
                                                        <label for="">{{ form_label(appartementRentalForm.cookOptions) }}</label>
                                                    </div>
                                                    <div class="col-10">{{ form_widget(appartementRentalForm.cookOptions) }}</div> #}
                                                </div>
                                            </div>

                                        </div>

                                        {#

             *
             *      autre options relation

                                        #}
                                    </div>
                            </fieldset>

                            <fieldset class="pb-5">
                                <legend>PHOTOS</legend>
                                <div class="container">
                                    <div class="row">
                                        {{ form_widget(appartementRentalForm.images) }}
                                    </div>
                                </div>
                            </fieldset>

                            <fieldset class="pb-5">
                                <legend>LIEU DE MON ANNONCE</legend>
                                <div class="container">
                                    {# <div class="row">
                                        <div class="col-sm-6">
                                            <div class="row mt-4">
                                                <div class="col-4">
                                                    <label for="">{{ form_label(appartementRentalForm.country) }}</label>
                                                    {{ form_widget(appartementRentalForm.country) }}
                                                </div>
                                                <div class="col-8">
                                                    <label for="">{{ form_label(appartementRentalForm.city) }}</label>
                                                    {{ form_widget(appartementRentalForm.city) }}
                                                </div>
                                            </div>

                                            <div class="row">
                                                <div class="col-4">
                                                    <label for="">{{ form_label(appartementRentalForm.zip) }}</label>
                                                    {{ form_widget(appartementRentalForm.zip) }}
                                                </div>
                                                <div class="col-8">
                                                    <label for="">{{ form_label(appartementRentalForm.address) }}</label>
                                                    {{ form_widget(appartementRentalForm.address) }}
                                                </div>
                                            </div>

                                            <label for="">{{ form_label(appartementRentalForm.longitude) }}</label>
                                            {{ form_widget(appartementRentalForm.longitude) }}

                                            <label for="">{{ form_label(appartementRentalForm.latitude) }}</label>
                                            {{ form_widget(appartementRentalForm.latitude) }}

                                        </div>
                                        <div class="col-sm-6">
                                            <div id="detailsMap">
                                                 
                                            </div>
                                        </div>

                                    </div> #}
                                    <p class="mt-2">
                                        <i>
                                            Une annonce immobilière bien localisée peut attirer de nombreux clients potentiels.
                                        </i>
                                    </p>
                                </div>
                            </fieldset>

                            <fieldset class="pb-5">
                                <legend>DESCRIPTION</legend>
                                <div class="container">
                                    <div class="row">
                                        {# <div class="col-12"><label for="">{{ form_label(appartementRentalForm.description) }}</label></div>
                                        <div class="col-12">{{ form_widget(appartementRentalForm.description) }}</div> #}
                                    </div>
                                    <button class="btn btn-primary mt-4" type="submit"> Publier mon annonce</button>
                                </div>


                            </fieldset>

                        {{ form_end(appartementRentalForm) }}
                    </div>
                </div>
            </div>
        </div>
    </div>


    {% include '_partials/_footer.html.twig' %}
{% endblock %}

{% block _ads_listings_form_images_entry_row %}
    <div class="col-2 mb-2">
        {{ form_widget(form) }}
    </div>
{% endblock %}



{% block _ads_listings_form_images_entry_widget %}
    <div class="form-group" id="block_{{ id }}">
        <div class="row">
            <div class="col-12">
                <div class="row">
                    {#<div class="col-12">
                        <input class="form-control" type="file" id="fileInput_{{ id }}" name="{{ full_name }}[imageFile]" accept="image/*" onchange="previewImage(this, '{{ id }}')">
                        <img id="preview_{{ id }}" class="img-thumbnail" style="display: none;">
                        <p id="fileName_{{ id }}" class="mt-2"></p>
                    </div> #}
                    <div class="col-12">
                        {{ form_widget(form.caption) }}
                    </div>
                </div>
            </div>
            <div class="col-12 mt-1">
                <button class="btn btn-danger col-12" data-action="delete" data-target="#block_{{ id }}" type="button">Supprimer</button>
            </div>
        </div>

    </div>
{% endblock %}

{% block _ads_listings_form_images_widget %}

    <input type="hidden" id="counter" value="0">
    <div class="col-12 mb-2">
        <button class="btn btn-info" id="add-image" type="button"> Ajouter une image</button>
    </div>

    <div class="col-12 row">
        {{ form_widget(form)}}
    </div>
{% endblock %}

{% block javascripts %}
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/summernote-lite.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/js/select2.min.js"></script>
    <script src="{{ asset('assets/leaflet/leaflet.js') }}" type="application/javascript"></script>
    <script src="{{ asset('assets/leaflet/gogaga-leaflet.js') }}" type="application/javascript"></script>
    <script>
        $('#apartement_rental_form_homeOptions').select2({
            placeholder: 'Selection une option'
        });
        $('#apartement_rental_form_cookOptions').select2({
            placeholder: 'Selection une option de cuisine'
        });

        $('#apartement_rental_form_description').summernote({
            placeholder: 'Description',
            tabsize: 2,
            height: 120,
            toolbar: [
                ['font', ['bold', 'underline', 'clear']],
                ['color', ['color']],
                ['para', ['ul', 'ol', 'paragraph']],
            ]
        });

        $("#ads_listings_form_images").addClass("col-12 row")

        $('#add-image').click(function()  {
            const  index = +$("#counter").val();
            const tmpl = $('#ads_listings_form_images').data('prototype').replace(/___name__/g, index);
            $('#ads_listings_form_images').append(tmpl);
            $('#counter').val(index +1);
            handleDeleteButtons();
            desableBtn();
        });

        function desableBtn(){
            const count = +$('#ads_listings_form_images div.col-2.mb-2').length;
            if (count >= 6){
                $("#add-image").attr("disabled" , true);
            }else if(count < 6){
                $("#add-image").attr("disabled" , false);
            }
        }

        function handleDeleteButtons() {
            $('button[data-action="delete"]').click(function () {
                const target = this.dataset.target;
                $(target).parent().remove();
                updateCounter();
                desableBtn();
            });
        }


        function updateCounter() {
            const count = +$('#ads_listings_form_images div.col-2.mb-2').length;

            $('#counter').val(count);

        }
        handleDeleteButtons();
        updateCounter();
        desableBtn();

    </script>

    <script>
        function previewImage(input, id) {
            const preview = document.getElementById('preview_' + id);
            const fileName = document.getElementById('fileName_' + id);

            if (input.files && input.files[0]) {
                const reader = new FileReader();

                reader.onload = function (e) {
                    preview.src = e.target.result;
                    preview.style.display = 'block';
                };

                reader.readAsDataURL(input.files[0]);
                //fileName.textContent = input.files[0].name;
            } else {
                preview.style.display = null;
                preview.src = null;
                fileName.textContent = '';
            }
        }
    </script>


{% endblock %}

Here is my Controller

#[Route('/louer-un-appartement', name: 'new')]
    public function index(Request $request, EntityManagerInterface $entityManager): Response
    {
        $ads = new AdsListings();

        $appartementRentalForm = $this->createForm(AdsListingsFormType::class, $ads);
        $appartementRentalForm->handleRequest($request);

        if ($appartementRentalForm->isSubmitted() && $appartementRentalForm->isValid()){

            foreach ($ads->getImages() as $image){
                $image->setAds($ads);
                $entityManager->persist($image);
            }

            $reference =  new References();
            $ads->setReference($reference);

            $entityManager->persist($ads);
            $entityManager->flush();
            $this->addFlash('success', "Félicitation, votre annonce a été publier avec succès !</br> Il y a des annonces qui mettent du temps à être affichées sur le site, car elles sont vérifiées par les administrateurs de GoGaGaa. ");
        }



        return $this->render('propertyRental/appartementRental.html.twig', [
            'appartementRentalForm' => $appartementRentalForm->createView()
        ]);
    }

Help me find the error please

New contributor

Cedric Odilon Lil’kouezy is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

LEAVE A COMMENT