Submitting Our Form and Sending An Email


In this video we are continuing on with our Contact / Support form implementation, this time concerning ourselves with actually handling the submitted form.

By the end of this video we will have covered:

  • how to accept a Symfony form submission
  • how to extract data from a Symfony form submission
  • how to create and dispatch an email using Symfony and SwiftMailer
  • ways to start debugging if / when things go wrong

To begin working with a form submission, the very first thing we need to do is ensure that we have injected the Request object into our Controller action.

This is really simple to do, and something you will see frequently throughout any Symfony application that you work with:

<?php

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

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;

class SupportController extends Controller
{
    /**
     * @Route("/", name="homepage")
     */
    public function indexAction(Request $request)
    {

There are two new additions here.

The most obvious one is the inclusion of Request $request in the indexAction method arguments.

The less obvious one is the additional use statement:

use Symfony\Component\HttpFoundation\Request;

Using a decent IDE like PHPStorm will flag this up if you forget to include the use statement. It will also prompt you to select the right value with auto-completion by looking into the files available in your project. Very handy.

Now, where this $request argument comes from is a little more complex. If you would like to know more about this, please do leave a comment below as I'm happy to cover it in a separate video.

Handling A Symfony Form Request

By injecting the $request object we can start working with incoming requests in interesting ways.

I would strongly recommend you to add a dump statement into your code and look at what this $request variable contains. It's a very useful, and frequently used object.

To dump, try this:

<?php

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

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;

class SupportController extends Controller
{
    /**
     * @Route("/", name="homepage")
     */
    public function indexAction(Request $request)
    {
        dump($request);

        // rest of your code here

And then refresh your page.

In the development environment (app_dev.php) you will see a toolbar at the bottom of your web browser (check the video if unsure). When using the dump function you should see a new item added to the web debug toolbar. It looks like a cross hair, and it contains the output of whatever things you have dumped.

It's fascinating how much data this object contains on even the most basic of page requests. On more involved requests - GET requests with query parameters, or POST requests with a body full of data - you can work really easily with your data. Very cool - and so much nicer than working directly with super globals ($_GET, $_SERVER, etc).

To begin with, we need a way of handling the incoming request. Is it a GET, or has the form been filled in and submitted invoking a POST request?

This isn't anything unique to Symfony - this is how a typical web form workflow would go.

What is unique to Symfony is that we must call $form->handleRequest with the $request object as its argument in order to determine if the form has been POST'ed or not.

<?php

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

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;

class SupportController extends Controller
{
    /**
     * @Route("/", name="homepage")
     */
    public function indexAction(Request $request)
    {
        $form = $this->createFormBuilder()
            ->add('from', EmailType::class)
            ->add('message', TextareaType:class)
            ->add('send', SubmitType::class)
            ->getForm()
        ;

        $form->handleRequest($request); // the new line

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

However, this by itself won't be enough to really do very much for us.

To continue we need to check if the form has been submitted or not. It is also incredibly common practice to check if the form is valid at this point.

Now, we have no validation constraints at this point. Therefore, our form will always pass the validation check. But it won't always have been submitted, so this next step won't always trigger - it will only trigger if the form has been submitted:

<?php

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

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;

class SupportController extends Controller
{
    /**
     * @Route("/", name="homepage")
     */
    public function indexAction(Request $request)
    {
        $form = $this->createFormBuilder()
            ->add('from', EmailType::class)
            ->add('message', TextareaType:class)
            ->add('send', SubmitType::class)
            ->getForm()
        ;

        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            // condition is met only if the form has been
            // submitted, and passed validation checks
        }

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

The next interesting step is to get access to whatever data the user submitted from the form.

For this we could do things the hard way and get access to the form submission via the $request object - it's in their under the request section.

However, the form component takes care of this for us.

To begin with, we can get access to the data in numerous ways. In my opinion, the easiest way is to call $form->getData(). As we aren't working with an object as our underlying form data, the default data structure will be in the format of an associative array.

If we add in the following:

<?php

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

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;

class SupportController extends Controller
{
    /**
     * @Route("/", name="homepage")
     */
    public function indexAction(Request $request)
    {
        $form = $this->createFormBuilder()
            ->add('from', EmailType::class)
            ->add('message', TextareaType:class)
            ->add('send', SubmitType::class)
            ->getForm()
        ;

        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $ourFormData = $form->getData();

            dump($ourFormData);
        }

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

Then go back to our browser, and refresh. To begin with, we shouldn't see anything dump'ed out to the web debug toolbar as we haven't yet submitted the form.

If we fill in the form with the values:

  • 'from' - 'chris@codereviewvideos.com'
  • 'message' - 'hi Chris!'

And submit the form, then we should see the page reload, and this time the web debug toolbar will contain the cross hair icon, which when clicked or hovered over should reveal an array containing:

[
    'from' => 'chris@codereviewvideos.com',
    'message' => 'hi Chris!'
];

Because $form->getData() returns an array, you may often see the shorthand similar to:

$from = $form->getData()['from'];

This is because $form->getData() resolves to an array, and then the brackets notation is the standard way of accessing values by key on a PHP array. Don't over think this. Its effectively a one liner for:

$formData = $form->getData();
$from = formData['from'];

You will likely see other variations of accessing form data. This is the convention I prefer.

Given that we now know how to get access to the form data, why don't we use it to go ahead and create a real email that we can send out to ourselves and prove that this all works?

Sending Our First Support Email

To complete this step ideally you will need some SMTP server credentials. You can use your existing GMail credentials, or some other SMTP credentials you have available.

Unfortunately I can't share my credentials because these would use my live account, and that's not a good thing for me to do :)

If you don't have any credentials available then don't worry, we can still see what would have happened - you just won't actually send / receive any emails.

There's a brilliant piece of documentation available on what we are about to do which gives you a perfect example to copy / paste. I want to stress that you do not need to memorise how to do things like this. I certainly don't. Never be ashamed to refer to the documentation.

Ok, so given that we have access to the submitted form data, let's use this to create a new Swift Message object which we can then send out using the Symfony mailer service:

<?php

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

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;

class SupportController extends Controller
{
    /**
     * @Route("/", name="homepage")
     */
    public function indexAction(Request $request)
    {
        $form = $this->createFormBuilder()
            ->add('from', EmailType::class)
            ->add('message', TextareaType:class)
            ->add('send', SubmitType::class)
            ->getForm()
        ;

        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $ourFormData = $form->getData();

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

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

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

I'm making use of Guerrilla Mail, as this is a very easy to use website that's perfect for the purposes of a video demonstration. Feel free to use your own email address.

One really nice feature available in Symfony is to override the recipient's email address with your own when in development. Again, rather than me regurgitating what's already been written, I would advise reading the official documentation on working with emails in development.

Creating the message is - as mentioned above - largely an exercise in copy / paste from the documentation. We only need to tweak a few parameters to meet our own needs and we are pretty much good to go. There's a fair amount of options available when creating a Swift Message so again, be sure to read the docs if in any doubt as to what you can / cannot do here.

Less obvious is that there is already the mailer service available.

What exactly is the mailer?

It's a preconfigured Symfony service, and is actually a shorthand version of the real underlying service:

php bin/console debug:container mailer

// This service is an alias for the service swiftmailer.mailer.default

And the Swift Mailer expects certain parameters to be configured in order to work.

We can find these parameters in /app/config/parameters.yml:

# /app/config/parameters.yml

parameters:
    mailer_transport:  smtp
    mailer_host:       127.0.0.1
    mailer_user:       ~
    mailer_password:   ~

Again, you will need to provide your own values here.

Should every go to plan you should anticipate seeing something similar to the following in the Symfony profiler:

symfony-swiftmail-email-example-output

Now the nice thing is, even if you don't have email properly configured, you should still be able to see this. Of course, if you don't have email properly configured then you won't be able to actually send this email out - nor receive it in the designated inbox - but it's nice to see what you would have sent all the same.

Over the last three videos we have covered a fair amount.

We still have quite a way to go at this point, but by now you have experienced what it's like to write your first few lines of code in a Symfony Framework environment.

Like learning any new skill, it can take a while to start feeling comfortable. It can be frustrating that it initially takes longer to complete tasks you can already achieve using alternative methods in a fraction of the time. I understand this point but would advise that you stick with it until you've given the framework a fair chance.

In the next few videos we will be expanding upon this setup further, working our way towards a login system to protect our support form, restricting its use to members only.

As ever if you have any questions, comments, feedback or suggestions please do leave them in the comments box below. Thanks for watching, and see you in the next video!

Code For This Course

Get the code for this course.

Episodes