1

I have an error that I can't resolve. I have a Symfony application, running well in dev env on my local and on the staging server, so with app_dev.php. When I test the same application on my staging server in prod mod (so with app.phpor just http://servername/ delete forms does't work. I have errors in my log : Request.ERROR: Uncaught PHP Exception Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException: "No route found for "POST /fr/invitesgroupe/3024": Method Not Allowed (Allow: DELETE)"

I generated this part with CRUD generator: Controller :

namespace AppBundle\Controller;

use AppBundle\Entity\InvitesGroupe;
use AppBundle\Entity\Reservations;
use AppBundle\Form\InvitesGroupeType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

/**
 * InvitesGroupe controller.
 *
 * @Route("/invitesgroupe")
 */
class InvitesGroupeController extends Controller
{
    /**
     * Lists all InvitesGroupe entities.
     *
     * @Route("/", name="invitesgroupe_index")
     * @Method("GET")
     */
    public function indexAction(Request $request)
    {

        $em = $this->getDoctrine()->getManager();

        $day = $request->get('day');
        $resto = $request->get('resto');

        $user = !$this->get('security.authorization_checker')
            ->isGranted('ROLE_SUPERVISEUR') ? "AND ig.user = '".$this->getUser()->getId()."'" : "";

        if ($day) {
            $day = \DateTime::createFromFormat('d/m/Y', $day);
            $dql   = "SELECT ig FROM AppBundle:InvitesGroupe ig 
                    LEFT JOIN AppBundle:Reservations r WITH r.idInvite = ig.idInvite
                    WHERE ig.inviteVisible = 1
                    AND ig.inviteTemporaire IS NULL
                    AND r.date = '".$day->format('Y-m-d')."'
                    ".$user."
                    ORDER BY ig.idInvite DESC";
        } elseif ($resto && $day) {
            $day = \DateTime::createFromFormat('d/m/Y', $day);
            $dql   = "SELECT ig FROM AppBundle:InvitesGroupe ig 
                    LEFT JOIN AppBundle:Reservations r WITH r.idInvite = ig.idInvite
                    WHERE ig.inviteVisible = 1 AND ig.inviteTemporaire IS NULL
                    AND r.idRestaurant = ".$resto."
                    AND r.date = '".$day->format('Y-m-d')."'
                    ".$user."
                    ORDER BY ig.idInvite DESC";

        } else {
            $dql   = "SELECT ig FROM AppBundle:InvitesGroupe ig
                    LEFT JOIN AppBundle:Reservations r WITH r.idInvite = ig.idInvite
                    WHERE ig.inviteVisible = 1 AND ig.inviteTemporaire IS NULL
                    AND r.date >= CURRENT_TIMESTAMP()
                    ".$user."
                    ORDER BY ig.idInvite DESC";
        }

        $query = $em->createQuery($dql);


        $paginator  = $this->get('knp_paginator');
        $pagination = $paginator->paginate(
            $query, /* query NOT result */
            $request->query->getInt('page', 1)/*page number*/,
            10/*limit per page*/
        );

        $deleteForms = array();

        if (null !== $request->get('print')) {
            return $pagination;
        }

        foreach ($pagination as $page) {
            $deleteForms[$page->getIdInvite()] = $this->createDeleteForm($page)->createView();
        }

        if ($day) {
            $restoTest = array();
            $restos = array();
            foreach ($pagination as $invite) {
                if (!in_array($invite->getIdService()->getIdRestaurant()->getIdRestaurant(), $restoTest)) {
                    $obj = new \stdClass();
                    $obj->id = $invite->getIdService()->getIdRestaurant()->getIdRestaurant();
                    $obj->nom = $invite->getIdService()->getIdRestaurant()->getNomRestaurant();
                    $restos[] = $obj;
                    $restoTest[] = $invite->getIdService()->getIdRestaurant()->getIdRestaurant();

                }
            }
        }



        return $this->render('invitesgroupe/index.html.twig', array(
            'invitesGroupes' =>  $pagination,
            'delete_forms' => $deleteForms,
            'day'  => $day ? $day : null,
            'restos' => isset($restos) ? json_encode($restos) : null
        ));
    }

    /**
     * Creates a new InvitesGroupe entity.
     *
     * @Route("/new", name="invitesgroupe_new")
     * @Method({"GET", "POST"})
     */
    public function newAction(Request $request)
    {
        $invitesGroupe = new InvitesGroupe();
        $reservation = new Reservations();
        $invitesGroupe->addReservation($reservation);
        $invitesGroupe->setUser($this->getUser());
        $form = $this->createForm('AppBundle\Form\InvitesGroupeType', $invitesGroupe);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $em = $this->getDoctrine()->getManager();
//            $coutRepas = $em->getRepository('AppBundle:CoutsRepas')->coutParDefaut();
            // récupération des dates dans datepicker
            $datePicker = $form['datepicker']->getData();
            $datePicker = explode(',', $datePicker);

            $dates = array();

            foreach ($datePicker as $dt) {
                $dates[] = \DateTime::createFromFormat('d/m/Y', $dt);
            }

            $first = true;
            /*
             * First reservation is in the form
             */

            if (!$form->getViewData()->getReservations()[0]->getIdService()) {
                $service = $em->getRepository('AppBundle:Services')
                    ->findOneBy(
                        array(
                            'idRestaurant' => $form->getViewData()->getReservations()[0]->getIdRestaurant(),
                            'serviceResa' => 1
                        )
                    );
                $form->getViewData()->getReservations()[0]->setIdService($service);
            } else {
                $service = $form->getViewData()->getReservations()[0]->getIdService();
            }

            $invitesGroupe->setIdService($service);
            if (null === $form->getViewData()->getUser()) {
                $invitesGroupe->setUser($this->getUser());
            }

            foreach ($dates as $date) {
                if ($first) {
                    $reservation = $form->getViewData()->getReservations()[0];

                    $invitesGroupe->setNombre($reservation->getNbPlace());

                    $reservation->setDate($date)
                        ->setIdRestaurant($form->getViewData()->getReservations()[0]->getIdRestaurant())
                        ->setIdService($service)
                        ->setNbPlace($invitesGroupe->getNombre())
//                        ->setCoutTotal($reservation->getNbPlace() * $coutRepas->getCout())
                    ;

                    $invitesGroupe->setNombre($reservation->getNbPlace());
                    $first = false;
                } else {
                    $reservation = new Reservations();
                    $reservation->setDate($date)
                        ->setIdRestaurant($form->getViewData()->getReservations()[0]->getIdRestaurant())
                        ->setIdService($service)
                        ->setNbPlace($invitesGroupe->getNombre())
//                        ->setCoutTotal($reservation->getNbPlace() * $coutRepas->getCout())
                        ->setRepasCompris($reservation->getRepasCompris())
                    ;
                }

                $invitesGroupe->addReservation($reservation);
                $reservation->setInvitesGroupes($invitesGroupe);

                $em->persist($reservation);
                // $em->flush();


            }

            $em->persist($invitesGroupe);
            $em->flush();


            // sending confirmation mail

            $message = \Swift_Message::newInstance()
                ->setSubject('['.$form->getViewData()
                        ->getReservations()[0]->getIdRestaurant()
                        ->getNomRestaurant().'] Réservation prise en compte')
                ->setFrom('')
                ->setTo($invitesGroupe->getUser()->getEmail())
                ->setBody(
                    $this->renderView(
                    // app/Resources/views/emails/invitegroupe.html.twig
                        'emails/invitegroupe.html.twig',
                        array('invite' => $invitesGroupe,
                            'modif' => false,
                            'temp' => false)
                    ),
                    'text/html'
                )
            ;
            $this->get('mailer')->send($message);

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

        return $this->render('invitesgroupe/new.html.twig', array(
            'invitesGroupe' => $invitesGroupe,
            'form' => $form->createView(),
        ));
    }


    /**
     * Print pdf of the day selected.
     *
     * @Route("/print", name="invitesgroupe_print")
     * @Method("GET")
     */
    public function printAction(Request $request)
    {

        $invites = $this->indexAction($request);

        $services = array();
        foreach ($invites as $invite) {
            if (!in_array($invite->getIdService(), $services) && $invite->getIdService()
                    ->getIdRestaurant()->getIdRestaurant() == $request->get('resto')) {
                $services[] = $invite->getIdService();
            }
        }

        $day = \DateTime::createFromFormat('d/m/Y', $request->get('day'));

        // on stocke la vue à convertir en PDF,
        // en n'oubliant pas les paramètres twig si la vue comporte des données dynamiques
        $html = $this->render('pdf/invitation.html.twig', array('invites' => $invites,
            'day' => $day,
            'resto' => $request->get('resto'),
            'services' => $services
        ));
        //on appelle le service html2pdf
        $html2pdf = $this->get('html2pdf_factory')->create();
        //real : utilise la taille réelle
        $html2pdf->pdf->SetDisplayMode('real');
        // $html2pdf->setModeDebug();
        //writeHTML va tout simplement prendre la vue stocker dans la variable $html pour la convertir en format PDF
        $html2pdf->writeHTML($html);
        //Output envoit le document PDF au navigateur internet
        return new Response($html2pdf->Output('invitation-du'.$day->format('d-m-Y').'.pdf'),
            200, array('Content-Type' => 'application/pdf')
        );
    }

    /**
     * Print pdf of the day selected.
     *
     * @Route("/see", name="invitesgroupe_see")
     * @Method("GET")
     */
    public function seeAction(Request $request) {

        $invites = $this->indexAction($request);

        $services = array();
        foreach ($invites as $invite) {
            if (!in_array($invite->getIdService(), $services) && $invite->getIdService()
                    ->getIdRestaurant()->getIdRestaurant() == $request->get('resto')) {
                $services[] = $invite->getIdService();
            }
        }

        $day = \DateTime::createFromFormat('d/m/Y', $request->get('day'));

        return $this->render('pdf/invitation.html.twig', array('invites' => $invites,
            'day' => $day,
            'resto' => $request->get('resto'),
            'services' => $services
        ));
    }

    /**
     * Displays a form to edit an existing InvitesGroupe entity.
     *
     * @Route("/{id}/edit", name="invitesgroupe_edit")
     * @Method({"GET", "POST"})
     */
    public function editAction(Request $request, InvitesGroupe $invitesGroupe)
    {
        $deleteForm = $this->createDeleteForm($invitesGroupe);
        $editForm = $this->createForm('AppBundle\Form\InvitesGroupeType', $invitesGroupe);
        $editForm->handleRequest($request);
        if ($editForm->isSubmitted() && $editForm->isValid()) {
            $em = $this->getDoctrine()->getManager();
            /**
             * @Doc : retrieve dates from field datepicker
             */
            $datePicker = $editForm['datepicker']->getData();
            $datePicker = explode(',', $datePicker);
            $dates = array();
            foreach ($datePicker as $dt) {
                $dates[] = \DateTime::createFromFormat('d/m/Y', $dt);
            }

            $data = $editForm->getViewData();
            $postedReservation = $data->getReservations()[0];

            // changement de restaurant et de service
            $restaurant = $postedReservation->getIdRestaurant() !== null ?
                $postedReservation->getIdRestaurant() :
                $invitesGroupe->getIdService()->getIdRestaurant();

            if ($postedReservation->getIdService() !== $invitesGroupe->getIdService()) {
                $invitesGroupe->setIdService($postedReservation->getIdService());
            }

//            $coutRepas = $em->getRepository('AppBundle:CoutsRepas')->coutParDefaut();

            /**
             * @doc : check if one date has been removed
             */

            $invitesGroupe->setNombre($data->getReservations()[0]->getNbPlace());

            $checkDates = $reservation= $em->getRepository('AppBundle:Reservations')
                ->findByIdInvite($invitesGroupe->getIdInvite());

            foreach ($checkDates as $checkDate) {
                if (!in_array($checkDate, $dates)) {
                    // date removed
                    $invitesGroupe->removeReservation($checkDate);
                    $em->remove($checkDate);
                    $em->persist($invitesGroupe);
                    $em->flush();
                }
            }

            if (null === $editForm->getViewData()->getUser()) {
                $invitesGroupe->setUser($this->getUser());
            }

            foreach ($dates as $date) {

                /**
                 * @doc : dates is new ?
                 */
                $reservation= $em->getRepository('AppBundle:Reservations')
                    ->findBy(array('idInvite' => $invitesGroupe->getIdInvite(), 'date' => $date));

                if (!$reservation) {
                    /* yes */
                    $reservation = new Reservations();
                    $reservation->setDate($date)
                        ->setIdRestaurant($restaurant)
                        ->setIdService($invitesGroupe->getIdService())
                        ->setNbPlace($invitesGroupe->getNombre())
//                        ->setCoutTotal($invitesGroupe->getNombre() * $coutRepas->getCout())
                        ->setRepasCompris($invitesGroupe->getRepasOffert())
                    ;

                    $invitesGroupe->addReservation($reservation);
                    $reservation->setInvitesGroupes($invitesGroupe);

                    $em->persist($reservation);

                }

            }

            $em->persist($invitesGroupe);
            $em->flush();

            // sending confirmation mail

            $message = \Swift_Message::newInstance()
                ->setSubject('['.$restaurant->getNomRestaurant().'] Réservation modifiée')
                ->setFrom('')
                ->setTo($invitesGroupe->getUser()->getEmail())
                ->setBody(
                    $this->renderView(
                    // app/Resources/views/emails/invitegroupe.html.twig
                        'emails/invitegroupe.html.twig',
                        array('invite' => $invitesGroupe,
                            'restaurant' => $restaurant,
                            'modif' => true,
                            'temp' => false)
                    ),
                    'text/html'
                )
            ;
            $this->get('mailer')->send($message);

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

        return $this->render('invitesgroupe/edit.html.twig', array(
            'invitesGroupe' => $invitesGroupe,
            'edit_form' => $editForm->createView(),
            'delete_form' => $deleteForm->createView(),
        ));
    }

    /**
     * Deletes a InvitesGroupe entity.
     *
     * @Route("/{id}", name="invitesgroupe_delete")
     * @Method("DELETE")
     */
    public function deleteAction(Request $request, InvitesGroupe $invitesGroupe)
    {
        $form = $this->createDeleteForm($invitesGroupe);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid() &&
            $this->get('security.authorization_checker')->isGranted('ROLE_SUPERVISEUR')
        ) {
            $em = $this->getDoctrine()->getManager();
            $em->remove($invitesGroupe);
            $em->flush();

            // sending confirmation mail

            if ($invitesGroupe->getUser() !== $this->getUser()) {
                $restaurant = $em->getRepository('AppBundle:Restaurants')
                    ->findOneByIdRestaurant($invitesGroupe->getIdService()->getIdRestaurant());

                $message = \Swift_Message::newInstance()
                    ->setSubject('['.$restaurant->getNomRestaurant().'] Réservation annulée')
                    ->setFrom('')
                    ->setPriority(1)
                    ->setTo($invitesGroupe->getUser()->getEmail())
                    ->setBody(
                        $this->renderView(
                        // app/Resources/views/emails/invitegroupe_canceled.html.twig
                            'emails/invitegroupe.html.twig',
                            array('invite' => $invitesGroupe,
                                'restaurant' => $restaurant,
                                'delete' => true,
                                'temp' => false,
                                'modif' => true
                            )
                        ),
                        'text/html'
                    )
                ;
                $this->get('mailer')->send($message);
            }

        }

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

    /**
     * Creates a form to delete a InvitesGroupe entity.
     *
     * @param InvitesGroupe $invitesGroupe The InvitesGroupe entity
     *
     * @return mixed
     */
    private function createDeleteForm(InvitesGroupe $invitesGroupe)
    {
        return $this->createFormBuilder()
            ->setAction($this->generateUrl('invitesgroupe_delete', array('id' => $invitesGroupe->getIdInvite())))
            ->setMethod('DELETE')
            ->getForm()
            ;
    }
}

I'm on plesk Onyx 17.8.11, php 5.6.36. Thanks for your help

Climberdav
  • 61
  • 7
  • Have you checked in the browser debug tools that `DELETE` is sent? If yes it seems that your server maps the `DELETE` to `POST` somehow. What webserver do you use? – vstm Jun 13 '18 at 14:03
  • Yes I think so. When I check my browser, I have a POST method. (dev and prod) but where my dev env accept that (ie app_dev.php) my prod env doesn't work (so app.php). I have plesk server (nginx serving apache) – Climberdav Jun 13 '18 at 14:52
  • 1
    Ah now I see you use the forms and not Ajax. Sorry. With the forms there is no DELETE, but Symfony should add a hidden field named `_method` to every form. Do you see this field in the generated HTML? Is it sent in the request (check this in the browser debug tools). Also verify that [http_method_override](https://symfony.com/doc/current/reference/configuration/framework.html#configuration-framework-http-method-override) is enabled. – vstm Jun 13 '18 at 15:15
  • GREAT ! I knew it was linked to proxy, but I didn't know this option. It works like a charm. Thanks a lot !! vstm – Climberdav Jun 14 '18 at 07:07

2 Answers2

0

Your request method is POST but following your route declaration "/fr/invitesgroupe/3024" is handled by the deleteAction() and there the only allowed method is "DELETE". So "POST /fr/invitesgroupe/3024" should be "DELETE /fr/invitesgroupe/3024".

Chris
  • 799
  • 6
  • 15
  • Yes Chris I see it, but why does it work in dev mode ? I tried to add POST method in the deleteAction() and I have no error, but the requested entity is not deleted. – Climberdav Jun 13 '18 at 13:50
  • Don't know, why it is working in dev mode. Try throwing an Exception after the $form->isValid() block and returm the errors with $form->getErrors(). – Chris Jun 13 '18 at 13:58
  • OK, I tried this. I put a dump on the $form>isValid() and it 's false, but I have to add "POST" method. And so nothing append. – Climberdav Jun 13 '18 at 14:44
0

I realise this is a rather old question, but I just had the same problem, and I may be able to explain.

First, a bit of background: Symfony forms don't actually use a real HTTP DELETE method when deleting an object. Instead, they send a POST request with a hidden attribute, _method, set to DELETE. This is because HTML forms only support GET and POST methods. This is all done behind the scenes for you if you use the Symfony form components.

To make everything work nicely in the backend, the Symfony framework has an option called http_method_override, which is enabled by default. This translates the _method sent by a POST request into something that looks to the Symfony router like a genuine DELETE request, so you can mark your route as handling a DELETE and everything works when the form actually sends it a POST with _method: DELETE.

My guess would be that like me you were using the Symfony Reverse Proxy, with it disabled in the dev environment and enabled in production.

In the recommended default configuration, the Symfony Reverse Proxy will ignore the _method parameter. This means that when running in production, the request appears to the routing as a POST request rather than a DELETE, leading to the error.

As you can see, there's a recommendation now to call Request::enableHttpMethodParameterOverride() just after creating the caching Kernel in your index.php. This lets the reverse proxy interpret the POST with _method: DELETE as a real DELETE for the router, and is all I needed to do to get things working for my situation.

Matt Gibson
  • 37,886
  • 9
  • 99
  • 128