Bonjour,
Je cherche a supprimer une image presente dans mon form annonce
ayant pour cela dans adtype mis ma collection images a allow_delete.
Mon javascript supprime bien le tag visé par le bouton supprimer.
Le dump me renvoie bien le tableau d'image avec le nb d'elements conservé mais a l'affichage de mon getAnnonce toutes les images sont presentes y compris en bdd et je n'ai aucun message d'erreur.
A l'inverse l'ajout d'image fonctionne tres bien
Je suis sur Symfony 5
Merci d'avance

Ad/Entity

<?php

namespace App\Entity;

use App\Entity\Image;
use Cocur\Slugify\Slugify;
use App\Repository\AdRepository;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;

/**
 * @ORM\Entity(repositoryClass=AdRepository::class)
 * @ORM\HasLifecycleCallbacks()
 * @UniqueEntity(
 *  fields = {"title"},
 *  message ="Une autre annonce à déjà ce titre merci de la modifier"
 * )
 */
class Ad
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * title
     *
     * @ORM\Column(type="string", length=255)
     *
     *@Assert\Length(
     * min=10,
     * max=50,
     * minMessage="Le titre doit au minimum contenir {{ limit }} caractères",
     * maxMessage="Le titre ne peut contenir plus de {{ limit }} caractères"
     *)
     * @var string
     */
    private $title;

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

    /**
     * @ORM\Column(type="float")
     *
     * @Assert\NotNull
     * @Assert\Regex(
     * pattern = "/(^[0-9]+)\W?([0-9]{0,2}$)/i",
     * match=true,
     * message ="Le prix ne peut contenir que des nombres")
     *
     */
    private $price;

    /**
     * @ORM\Column(type="text")
     */
    private $introduction;

    /**
     * @ORM\Column(type="text")
     */
    private $content;

    /**
     * @ORM\Column(type="string", length=255)
     *
     * @Assert\Url( message="l'url {{ value }}saisi n'est pas correcte")
     */
    private $coverImage;

    /**
     * @ORM\Column(type="integer")
     */
    private $rooms;

    /**
     * @ORM\OneToMany(targetEntity=Image::class, mappedBy="ad", cascade={"persist"})
     *
     * @Assert\Valid
     */
    private $images;

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

    /**
     * This function initialize a new slug in case is  empty
     *
     * @return void
     *
     * @ORM\PrePersist
     * @ORM\PreUpdate
     */
    public function initializeSlug()
    {
        if (empty($this->slug)) {
            $slugTitle = new Slugify();
            $this->setSlug($slugTitle->slugify($this->title));
        }
    }

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

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

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

        return $this;
    }

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

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

        return $this;
    }

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

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

        return $this;
    }

    public function getIntroduction(): ?string
    {
        return $this->introduction;
    }

    public function setIntroduction(string $introduction): self
    {
        $this->introduction = $introduction;

        return $this;
    }

    public function getContent(): ?string
    {
        return $this->content;
    }

    public function setContent(string $content): self
    {
        $this->content = $content;

        return $this;
    }

    public function getCoverImage(): ?string
    {
        return $this->coverImage;
    }

    public function setCoverImage(string $coverImage): self
    {
        $this->coverImage = $coverImage;

        return $this;
    }

    public function getRooms(): ?int
    {
        return $this->rooms;
    }

    public function setRooms(int $rooms): self
    {
        $this->rooms = $rooms;

        return $this;
    }

    /**
     * @return Collection|Image[]
     */
    public function getImages(): Collection
    {
        return $this->images;
    }

    public function addImage(Image $image): self
    {
        if (!$this->images->contains($image)) {
            $this->images[] = $image;
            $image->setAd($this);
        }

        return $this;
    }

    public function removeImage(Image $image): self
    {
        if ($this->images->contains($image)) {
            $this->images->removeElement($image);
            //set the owning side to null (unless already changed)
            if ($image->getAd() === $this) {
                $image->setAd(null);
            }
        }

        return $this;
    }

    /**
     * Set the value of images
     *
     * @return  self
     */
    public function setImages(?ArrayCollection $images)
    {
        $this->images = $images;
    }
}

Image/Entity

<?php

namespace App\Entity;

use App\Repository\ImageRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

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

    /**
     * @ORM\Column(type="string", length=255)
     *
     * @Assert\Url(message="Ceci n'est pas une url valide veuillez la modifier merci!")
     */
    private $url;

    /**
     * @ORM\Column(type="string", length=255)
     *
     * @Assert\Length(min=10, minMessage="Le titre doit faire au moins {{ limit }} caractères")
     */
    private $caption;

    /**
     * @ORM\ManyToOne(targetEntity=Ad::class, inversedBy="images")
     * @ORM\JoinColumn(nullable=false)
     */
    private $ad;

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

    public function getUrl(): ?string
    {
        return $this->url;
    }

    public function setUrl(string $url): self
    {
        $this->url = $url;

        return $this;
    }

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

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

        return $this;
    }

    public function getAd(): ?Ad
    {
        return $this->ad;
    }

    public function setAd(?Ad $ad): self
    {
        $this->ad = $ad;

        return $this;
    }
}

Ma View Twig

{% extends 'base.html.twig' %}

{% block title %}{{titre}}{% endblock %}
{% form_theme form _self %}
{% block body %}
<h1 class="text-center mb-5 pb-5 border-bottom border-grey mx-auto">{{titre}}</h1>
{{ form_start(form, {'attr':{'class':'form-group'}}) }}
{{ form_widget(form) }}
    <div class="d-flex justify-content-end">
        <input type="submit" class="btn btn-primary form-group" 
        {% if button_label is defined %} 
            value="{{ button_label }}" 
        {% else %} 
            value="Créer cette annonce"
        {% endif %} >
    </div>
{{ form_end(form) }}
{% endblock %}
{% block _ad_images_widget %}
    <p>Utlisez ces champs pour rajouter des images</p>
    {{form_widget(form)}}
<div class="form-group">
    <button type="button"
             name=""
             id="btn-add" 
             class="btn btn-primary ">
        Ajouter une image
    </button>
</div>
{% endblock %}

{# suppression des labels des champs dans twig #}
{# {cette suppression est commentée car réalisée dans les options du form} #}
{# {% block _ad_images_entry_label %}
    {{form_label(form,null,{'label_attr':{'class':'d-none'}})}}
{% endblock %} #}

{% block _ad_images_entry_widget %}
<div class="row">
    <div class="col d-inline">
    {{ form_errors(form.caption) }}
    {{ form_widget(form.caption) }}
    </div>
    <div class="col d-inline">
    {{ form_errors(form.url) }}
    {{ form_widget(form.url) }}
    </div>
    <div class="col d-inline">
        <button type='button' class="btn btn-danger btn-delete">
                Supprimer
        </button>
    </div>
</div>
{% endblock %}

{% block javascripts %}
<script>
$(document).ready(function(){

// Add an image when newImage addbutton is typed

    $(document).on('click','#btn-add',function(){

        let tmpl = $('#ad_images').data('prototype').replace(/__name__/g,count);
        $('#ad_images').append(tmpl);
    });

// Delete the image choosed by the  user when press delete button

    $(document).on('click','.btn-delete',function(e){
            let result = confirm('Confirmez-vous la suppression de cette image ?');
                if (result == true){
                    if ($(this).closest('fieldset.form-group').attr('id')){
                        $(this).closest('fieldset.form-group').remove();
                    } else {
                        $('fieldset.form-group:last').remove();
                    }
                }
    });

    function count(){
        let count = $('#ad_images .row').length;
        for(let i=0;i<=count;i++){
            if(!$('fieldset.form-group:nth-child('+i+')').attr('id')){
                $('fieldset.form-group:nth-child('+i+')').attr('id', "block_"+i);
            }
        }
    }
    count();
});
</script>
{% endblock %}

Ma Method dans le controller

     /**
    * This method edit the choosen ad form to be modifyed
    *
    * @Route("/ad/edit/{slug}", name="edit_ad")
    *
    * @return Ad
    */
    public function editAction(Request $request, Ad $ad, EntityManagerInterface $manager)
    {

        $form = $this->createForm(AdType::class, $ad);
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            foreach ($ad->getImages() as $image) {
                $image->setAd($ad);
                $manager->persist($image);
            }
            $manager->persist($ad);
            $manager->flush();
            $this->addFlash('success', "Les modifications de l'annonce ont bien été prises en compte");
            return $this->redirectToRoute('get_ad', ["slug"=>$ad->getSlug()]);
        }
        return $this->render(
            "ad/forms_ad.html.twig",
            [
            'titre'=>'Modification de l\'annonce: '.$ad->getTitle(),
            'button_label'=> "Modifier cette annonce",
            'ad'=>$ad,
            'form'=>$form->createView()
            ]
        );
    }

Mon FormType Annonce

<?php

namespace App\Form;

use App\Entity\Ad;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\UrlType;

class AdType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add(
                'title',
                TextType::class,
                $this->getConfiguration("Titre de l'annonce", 'Saisissez votre titre')
            )
            ->add(
                'introduction',
                TextType::class,
                $this->getConfiguration("Brève présentation", 'Petite phrase d\'introduction')
            )
            ->add(
                'coverImage',
                UrlType::class,
                $this->getConfiguration("Image de couverture", 'Url de l\'image de couverture')
            )
            ->add(
                'content',
                TextType::class,
                $this->getConfiguration("Contenu de l'annonce", 'Saisissez un contenu complet pour l\'annonce')
            )
            ->add(
                'price',
                MoneyType::class,
                $this->getConfiguration("Prix de la location / nuit", 'Saisissez le prix par nuit')
            )
            ->add(
                'rooms',
                IntegerType::class,
                $this->getConfiguration("Nombre de chambres à proposer", 'De combien de chambres disposez-vous ?')
            )
            ->add(
                'images',
                CollectionType::class,
                [
                'label'=>"Images complémentaires",
                'label_attr'=>["class"=>"font-weight-bold border-top my-4 border-grey"],
                'entry_type'=> ImageType::class,
                'entry_options'=>['label'=>false],
                'allow_add'=> true,
                'allow_delete' => true
                ]
            )
        ;
    }

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

    /**
     * This function returns the label and the placeholder for each field
     *
     * @param string $label
     * @param string $placeholder
     * @return array
     */
    private function getConfiguration(string $label, string $placeholder, $required = true):array
    {
        return [
            'required'=>$required,
            'label'=>$label,
            'attr'=>[
            'placeholder'=>$placeholder
            ]
        ];
    }
}

Mon Form Image

<?php

namespace App\Form;

use App\Entity\Image;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\UrlType;
use Symfony\Component\Form\Extension\Core\Type\TextType;

class ImageType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add(
                'url',
                UrlType::class,
                [
                    'attr'=>["placeholder"=>"Url de l'image"]
                ]
            )
            ->add(
                'caption',
                TextType::class,
                [
                    'attr'=>["placeholder"=>"Nom de l'image"]
                ]
            )
        ;
    }

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

1 réponse


Andre43
Auteur
Réponse acceptée

J'ai réecris ma méthode edit du controlleur
J'initialise un tableau qui reprends les images présentes en bdd
Je compare ce tableau a ma request
Je supprime de ma bdd les images n'appartenant pas a ma request
Je valide les images de ma request
C'est plus lourd mais efficace.

  /**
    * This method edit the choosen ad form to be modifyed
    *
    * @Route("/ad/edit/{slug}", name="edit_ad")
    *
    * @return Ad
    */
    public function editAction(Request $request, Ad $ad, EntityManagerInterface $manager)
    {
        //Tableau qui reprend les images présentes en bdd
        $originalImages = new ArrayCollection();
        foreach ($ad->getImages() as $image) {
            $originalImages->add($image);
        }
        $form = $this->createForm(AdType::class, $ad);
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {

            //Comparaison et suppression des images de ma request non présentes en bdd
            foreach ($originalImages as $image) {
                if (false === $ad->getImages()->contains($image)) {
                    $ad->removeImage($image);
                    $manager->remove($image);
                    $manager->flush();
                }
            }
            //Validation des images de ma request
            foreach ($ad->getImages() as $image) {
                $image->setAd($ad);
                $manager->persist($image);
            }
            $manager->persist($ad);
            $manager->flush();
            $this->addFlash('success', "Les modifications de l'annonce ont bien été prises en compte");
            return $this->redirectToRoute('get_ad', ["slug"=>$ad->getSlug()]);
        }
        return $this->render(
            "ad/forms_ad.html.twig",
            [
            'titre'=>'Modification de l\'annonce: '.$ad->getTitle(),
            'button_label'=> "Modifier cette annonce",
            'form'=>$form->createView()
            ]
        );
    }