Bonjour à tous,

Pour un projet perso j'essaye de supprimer un objet dans le CRUD via la fonction delete de mon controller.

En JS je stoppe l'action pour ouvrir une modale de confirmation. Au clic sur la confirmation j'obtiens une erreur et mon objet n'est pas supprimé. Si je remplace le clic de la confirmation par un confirm classique je n'ai aucun problme. Je suis débutant en ajax et je ne trouve pas d'aide concrète sur le web. Je suppose une erreur avec la transmission d'info pour le token...

Controller :

    /**
     * @Route("/delete/{id}", name="_delete", methods={"DELETE", "GET"})
     *
     * @param Request $request
     * @param Blog $blog
     * @return Response
     */
    public function delete(Request $request, Blog $blog): Response
    {
        $data = json_decode($request->getContent(), true);

        if ($this->isCsrfTokenValid('delete' . $blog->getId(), $data['_token'])) {
            try {
                $entityManager = $this->getDoctrine()->getManager();
                $entityManager->remove($blog);
                $entityManager->flush();

                return new JsonResponse(['success' => 1]);
            } catch (\Throwable $th) {
                $this->addFlash('error', 'Une erreur est intervenue, merci de réessayer.');
            }
        } else {
            return new JsonResponse(['error' => 'Token invalide'], 400);
        }

        return $this->redirectToRoute('admin_blog_list', [], Response::HTTP_SEE_OTHER);
    }

JS :

// Supression des données
$("a[data-delete]").on("click", function (e) {
  e.preventDefault();
  var link = this;
  var token = this.getAttribute("data-token");

  // Affichage de la modale
  $("body").append(
    '<div class="modal-confirm"><div><p>Etes-vous sur de vouloir supprimer cet article ?</p><div><button class="yes btn-validate">oui</button><button class="no btn-back">non</button></div></div></div>'
  );

  // Confirmation
  $(".btn-validate").on("click", function () {
    $.ajax({
      type: "GET",
      url: "/admin/blog/delete/126",  ==> URL en dur uniquement pour le test
      data: { _token: token },
      success: function (response) {
        console.log(response);
      },
      error: function (error) {
        console.log(error);
      },
    });
  });

J'obtiens cette erreur :

Erreur symfony : Warning: Trying to access array offset on value of type null
Erreur console : GET http://localhost:9070/admin/blog/delete/126?_token=6f4f5af917e3538a06b8b7c94040.G2CDYYorh52ucDAZRJmUud5WCRwFyvB_wbcWhJjwVpA.YVHEKMld7fv9JUh_A6DnjJ8SYE9vpK9MpM9Xtc-zIPtSEsoUsk3i5-8xAA 500 (Internal Server Error)

Si je modifie ma fonction JS pour le code ci dessous cela fonctionne (mais je me passe de la modale de confirmation) :

// Confirmation
  if (confirm()) {
    fetch(link.getAttribute("href"), {
      method: "DELETE",
      headers: {
        "X-requested-with": "XMLHttpRequest",
        "Content-type": "application/json",
      },
      body: JSON.stringify({ _token: this.dataset.token }),
    })
      .then((response) => response.json())
      .then((data) => {
        if (data.success) {
          console.log(data.success);
          $(".modal-confirm").remove();
        } else {
          console.log(data.error);
        }
      })
      .catch((e) => console.log(e));
  }

Si j'intègre à nouveau ce code dans une fonction onClick j'obtiens une autre erreur :

$(".btn-validate").on("click", function () {
    if (confirm()) {
      fetch(link.getAttribute("href"), {
        method: "DELETE",
        headers: {
          "X-requested-with": "XMLHttpRequest",
          "Content-type": "application/json",
        },
        body: JSON.stringify({ _token: this.dataset.token }),
      })
        .then((response) => response.json())
        .then((data) => {
          if (data.success) {
            console.log(data.success);
            $(".modal-confirm").remove();
          } else {
            console.log(data.error);
          }
        })
        .catch((e) => console.log(e));
    }
  });

Symfony: Warning: Undefined array key "_token"
Console : SyntaxError: Unexpected token < in JSON at position 0

Merci à ceux qui me répondront :)

6 réponses


Salut,

Utilises-tu bootstrap ? Et également peux-tu nous montrer le code de ton button de suppression ? Est-ce un simple bouton ou bien un formulaire avec seulement un bouton de suppresion ?

Merci

floriqn
Auteur

Non pas de bootstrap, et c'est un simple lien de suppression (qui fonctionne avec la méthode confirm) :

                            <a href="{{ path('admin_blog_delete', {'id': article.id}) }}" data-delete data-token="{{ csrf_token('delete' ~ article.id) }}"><button><i class="fas fa-trash-alt"></i></button></a>

Salut,

Je trouve ça très bizarre que tu gères cette suppression de ressource en AJAX, enfin de la manière dont tu procèdes.. Il y a une raison à cela ?

Je te conseille plutôt (s'il n'y a pas de véritable raison), de faire un formulaire avec un button de suppression. Le formulaire sera soumis via la methode DELETE only, donc dans ton controller tu peux supprimer la methode GET qui est inutile.
Tu rajoutes un data-attribute à ton formulaire data-confirmation="true".
Dans ton template, tu peux y insérer directement la modal qui sera par défaut cacher.
Et en JS ou jQuery tu attrapes l'évenement de submit qui possède le data attribute "data-confirmation". Et il te suffit avec un peu de JS de checker si ton button "oui" est click alors le formulaire est soumis sinon rien ne se passe et on ferme la modal.

Ainsi, ca t'évite d'envoyer une requête via AJAX qui n'est vraiment pas necessaire.

Bon courage

floriqn
Auteur

Salut Raizen,

L'ajax c'était plutot une découverte pour moi (histoire de pratiquer), je peux effectivement modifier. Mais avec ta solution, le token ne sera plus géré ?

Salut,

D'accord je comprends mais je ne pense pas que ce soit un très bon cas pratique pour le coup.
Si le token csrf est toujours d'actualité avec ma solution.

Exemple (fonctionnant avec une modal bootstrap par contre):

Le formulaire:

<form action="**TA_ROUTE**" method="post" data-confirmation="true">
      <input type="hidden" name="_method" value="DELETE">
      <input type="hidden" name="token" value="{{ csrf_token('delete') }}" />
      <button type="submit" class="dropdown-item text-danger">
          <i class="tio-delete-outlined dropdown-item-icon text-danger"></i> Delete
      </button>
</form>

Le controlleur:

  /**
   * @Route("/{id}/delete", methods="DELETE", name="delete")
   *
   * @param Request $request
   * @param User    $user
   *
   * @return Response
   */
  public function delete(Request $request, User $user): Response
  {
      if (!$this->isCsrfTokenValid('delete', $request->request->get('token')))
          return $this->redirectToRoute('admin_user_index');

      $em = $th[](null)is->getDoctrine()->getManager();

      $em->remove($user);
      $em->flush();

      $this->addFlash('success', "User <b>$user</b> has been successfully deleted.");

      return $this->redirectToRoute('admin_user_index');
 }

La fonction JS:

$(document).on('submit', 'form[data-confirmation]', function (event) {
      var $form = $(this),
          $confirm = $('#confirmationModal');

      if ($confirm.data('result') !== 'yes') {
          //cancel submit event
          event.preventDefault();

          $confirm
              .off('click', '#buttonClose')
              .on('click', '#buttonConfirm', function () {
                  $confirm.data('result', 'yes');
                  $form.find('input[type="submit"]').attr('disabled', 'disabled');
                  $form.submit();
              })
              .modal('show');
      }
});

Et voilà, à toi d'adapter la fonction JS avec ta modal. La fonction est très simple comme tu peux le voir.

Bon courage !

floriqn
Auteur

Merci Raizen, je ne suis sur de ce que tu posts car je n'utilise pas boostrap dans ce projet mais je vais l'adapter. Je te fais un retour demain ! Merci à toi.