A Different Way To Handle POST


Towards the end of the previous video we had extracted out the form submission logic from our Contact Form indexAction and placed it inside a new action, the handleFormSubmissionAction. The idea here is to separate out the concern of showing the form to the end user from the concern of handling their submitted form data.

Now, in truth this setup is less prevalent in Symfony-land. Typically you will see both the display and processing of a form handled in the same action. From experience, I believe this is because separating these two concerns in Symfony is more difficult than going with the "all-in-one" approach. This may just be my experience.

Anyway, let's quickly recap the handleFormSubmissionAction logic in full:

    /**
     * @param Request $request
     * @Route("/form-submission", name="handle_form_submission")
     * @Method("POST")
     */
    public function handleFormSubmissionAction(Request $request)
    {
        $form = $this->createForm(ContactFormType::class);

        $form->handleRequest($request);

        if ( ! $form->isSubmitted() || ! $form->isValid())
        {
            return $this->redirectToRoute('homepage');
        }

        $data = $form->getData();

        dump($data);

        $message = \Swift_Message::newInstance()
            ->setSubject('Support Form Submission')
            ->setFrom($data['from'])
            ->setTo('cexzaukk@sharklasers.com')
            ->setBody(
                $form->getData()['message'],
                'text/plain'
            )
        ;

        $this->get('mailer')->send($message);

        $this->addFlash('success', 'Your message was sent!');

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

We've covered createForm, and handleRequest already previously, so I am going to skip over them. If you want to know more, please watch the previous videos in this series.

Differing from most implementations you will find is the 'inversion' of the if statement check.

Typically you will see:

if ($form->isSubmitted() && $form->isValid())
{
    // do something
}

Yet I find this leads to lots of code indented that needn't be. Personally I dislike any indentation if I can at all avoid it. And no, I don't just mean for aesthetics.

In my mind, whenever I see an if or an else or similar, it's one extra 'layer' that I need to keep in my head. As your code inevitably grows in complexity, having to keep these 'layers' in mind when reading code becomes mentally exhausting. I like simplicity. Simplicity, for me, is reducing as many of these 'layers' as possible.

This is all personal taste, but it has helped me over the years and is a practice I recommend to others.

We've covered the creation of the \Swift_message instance in previous videos, and sending the created $message.

The remaining interesting thing here is the deliberate inclusion of the dump statement.

If you now try and use the form, you will notice that there is no output on the web debug toolbar for the dump statement. Why is this?

Well, whether our form is submitted and is valid or not, in both instances we:

return $this->redirectToRoute('homepage');

When redirecting we are going to 'lose' access to the $request in which that dump output is stored. This is confusing.

We can 'fix' this by telling Symfony that actually, we don't want to redirect when we're working in the development (app_dev) environment:

# /app/config/config_dev.yml

web_profiler:
    toolbar: true
    intercept_redirects: true # changed from false

And so now when we submit the form we won't be fully redirected to where we expected, but instead halted at an intermediate page that allows us to inspect the request / response cycle as we would intuitively expect it to work.

We've also included a call to addFlash, which won't quite work unless we set this up further.

Flash Messages

I have a full video on Flash Messages in Symfony 3 which also includes the template we will use in this video. Be sure to go there and grab the code snippet.

Important - Please be sure to have copied all the contents of this template over to your project, paying particular attention to the Bootstrap JS and CSS files. You will find the latest version of these files here

We will need to include this snippet somewhere in our code. For that I'm going to add in a new line to our support/index.html.twig file, which is not the correct place, but will do for the moment:

<!-- /app/Resources/views/support/index.html.twig -->

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

{% block body %}
<div class="container">
    {% include('flash-messages.html.twig') %}
    {{ form(our_form) }}
</div>
{% endblock %}

{% block stylesheets %}
<style>
    .container {
        margin-top: 40px;
    }
</style>
{% endblock %}

Don't worry, we will refactor this properly in an upcoming video in this series.

Form Submission

We have most of the pieces in place now to both display and handle our form submission. However, we are missing one key piece.

If you try and submit the form now it will POST back to the / (the homepage route).

This is because by default, the form component will submit to whatever route the form renders on, unless we tell it otherwise.

To fix this, we must specify our custom form submission path by way of an entry in the form $options.

<?php

// /src/AppBundle/Controller/DefaultController.php

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use AppBundle\Form\Type\ContactFormType;

class SupportController extends Controller
{
    /**
     * @Route("/", name="homepage")
     */
    public function indexAction(Request $request)
    {
        $form = $this->createForm(ContactFormType::class, null, [
            'action' => $this->generateUrl('handle_form_submission'),
        ]);

        return $this->render('support/index.html.twig', [
            'our_form' => $form->createView(),
        ]);
    }

    /**
     * @param Request $request
     * @Route("/form-submission", name="handle_form_submission")
     * @Method("POST")
     */
    public function handleFormSubmissionAction(Request $request)
    {
        // * snip

Remember how we discussed that instead of using the URI we could instead refer back to specific routes by name? Here we can call generateUrl and pass in the name property of another controller action and Symfony will correctly point to whatever path is in use for that named route. Nice.

Finally now when we submit this form we should get the desired outcome. Hurrah.

This pretty much covers all the hard parts of this process. In the next and final video in this section we will do a little styling and tweaking of form labels, just the basic stuff to make our front end seem a little less "designed by a programmer".

Then we can get on with securing our contact form, which is where the fun starts :)

Code For This Course

Get the code for this course.

Episodes