Symfony2 Tutorial: User Submissions – Controller, Forms, E-Mails

This is the forth part of the series Symfony2 Tutorial for Beginners which will describe the process of creating an interactive contact page.

This part of the Symfony2 tutorial has the goal to deepen the understanding of controllers and Twig templates. Furthermore forms will be introduced so that at the end of this tutorial the reader will be able to create an interactive page with the symfony2 framework.

Creating the Contact Entity

First of all, create a directory Entity   in which all entities will reside from now on.

Next, we will need a Contact   entity class. It will have a name, email, subject and message as well as the appropriate getters and setters.

<?php
// src/YourIdentifier/YourBundle/Entity/Contact.php

namespace YourIdentifier\YourBundle\Entity;

use Symfony\Component\Validator\Constraints as Assert;

class Contact {

    /**
     * @Assert\NotBlank()
     */

    private $name;

    /**
     * @Assert\Email()
     */

    private $email;

    /**
     * @Assert\NotBlank()
     */

    private $subject;

    /**
     * @Assert\NotBlank()
     */

    private $message;

    public function getName()
    {
        return $this->name;
    }

    public function setName($name)
    {
        $this->name = $name;
    }

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }

    public function getSubject()
    {
        return $this->subject;
    }

    public function setSubject($subject)
    {
        $this->subject = $subject;
    }

    public function getMessage()
    {
        return $this->message;
    }

    public function setMessage($message)
    {
        $this->message = $message;
    }
}
?>

Note the annotations which define constraints on each of the fields. We will use those later to validate the form.

Creating the Form Class

In Symfony2, forms can be automatically created using the FormBuilder. This can be done in the Controller, but to keep the code as clean as possible, we will first create a new folder called Form   . Inside this folder, we will create a php class called ContactType   .

Inside this class, we will use the FormBuilder and add all desired fields to it.

<?php
// src/YourIdentifier/YourBundle/Form/ContactType.php

namespace YourIdentifier\YourBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;

class ContactType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder->add('name'); // adds the name field to the form
        $builder->add('email', 'email', array('label' => 'E-Mail')); // email field of type 'email' with a label option
        $builder->add('subject', null, array('max_length' => 100)); // null passed as type, type guessing enabled
        $builder->add('message', 'textarea');
    }

    public function getName()
    {
        return 'contact';
    }
}
?>

The first argument of the add   method is the name of the field, the second the type and the third an array of options. The type decides how a browser will render the field. Be aware that each type accepts different options. You may want to look at this list of possible form types. The link of each type takes you to a list of options acceptable for the type. Here is a list of options available for all types.

The constraints that are possible with these options are of course evaluated client-side, so be aware that for example setting the required option to true or a max_length on a password field does not mean that users must obey this.

Please also note that the string returned by the name method must be a unique identifier.

Twig template to render a form

Lets go ahead and create a twig template to render the form.

<!-- /src/YourIdentifier/YourBundle/Resources/views/contact.html.twig -->
{% extends 'YourIdentifierYourBundle::base.html.twig' %}

{% block stylesheets %}
    {% stylesheets filter='yui_css' 'css/main.css' 'css/basic_form.css' %}
        <link rel="stylesheet" href="{{ asset_url }}" type="text/css" media="screen" />
    {% endstylesheets %}
{% endblock %}

{% block title %}Contact {% endblock %}

{% block body %}
    <h1>Contact</h1>
    <form action="{{ path('page_contact') }}" method="post" class="basic">
        {{ form_widget(form) }}    
        <input type="submit" value="Submit" />
    </form>
{% endblock %}

You already know how to extend twig templates, include stylesheets and override a twig block from Part 3 of the symfony2 tutorial, so lets look at the form. The action is defined via the page_contact   label which routes to the contactAction   of the PageController   .

Rendering the form is as easy as calling form_widget   with the form   (which we will later pass from the controller). A bit more is possible though. Fields of the form may be rendered individually by calling form_row(form.NameOfField)   in which case unrendered fields may be rendered by calling form_rest(form)   and errors may be rendered by calling form_errors(form)   .

So an alternative – which provides the possibility to customize the form in the template – to the above code to render the form would be:

{{ form_errors(form) }}

{{ form_row(form.name) }}
{{ form_row(form.email) }}
{{ form_row(form.subject) }}
{{ form_row(form.message) }}

{{ form_rest(form) }}

Adding the Controller Action

First, configure Symfony2 to send emails with the SwiftmailerBundle (it should be enough to edit app/config/parameters.ini   ). Then add the methods below to the PageController  

// src/YourIdentifier/YourBundle/Controller/PageController.php

// [...]

    /**
     * displays as well as processes contact page.
     *
     * @Route("/contact", name="page_contact")
     * @Template("YourIdentifierYourBundle:Page:contact.html.twig")
     */

    public function contactAction()
    {
        // creating the contact entity and the form
        $contact = new Contact();
        $form = $this->createForm(new ContactType(), $contact);

        // if request is post, validate form and send mail
        $request = $this->getRequest();
        if ($request->getMethod() == 'POST') {
            $form->bindRequest($request);
            if ($form->isValid()) {
                $this->sendEmail($contact);
            }
        }

        // pass the generated form in the variable $form to the
        // template defined by the @Template annotation
        return array('form' => $form->createView());        
    }

    /**
     * @param Contact $contact
     * @return type 1 (true) if send successfully, 0 (false) otherwise
     */

    private function sendEmail($contact) {
        $message = \Swift_Message::newInstance()
            ->setSubject('Contact from example.com')
            ->setFrom('contact@example.com')
            ->setTo('yourmail@example.com')
            ->setBody($this->renderView('YourIdentifierYourBundle:Page:email.txt.twig', array('contact' => $contact)))
        ;
        return $this->get('mailer')->send($message);
    }

As we did in Part 3 of the symfony2 tutorial we define a route via the @Route   annotation, but this time instead of calling render   we define the template which should be used via the @Template   annotation and just return an array containing the variables which should be passed to the template.

It would be possible to call the render   method as we did in the impressumAction   and add the array as a second argument, but as we sometimes need to call it in two places, I think that it is cleaner using annotations.

We also create the form and – if it is a post request – validate it (using the constraints defined in the Contact   entity) and call the sendEmail   method. If it is a get request we return the form as a variable in an array. It will be passed to the twig template and rendered.

The sendEmail   method uses the SwiftmailerBundle to actually send the mail. The body is rendered using the following template:

Name: {{ contact.name }}
Email: {{ contact.email }}
Subject: {{ contact.subject }}
Message: {{ contact.message }}

Flash-Messages: Feedback across requests

The contact page should be fully functional at this point.

The contact form should be rendered if you visit http://localhost/your_project/web/app_dev.php/contact   and you should be able to send emails with it.

For now, the user is not informed if the mail was send successfully or not, which is problematic. The solution are Flash-Messages. To use them, just replace the $this->sendEmail($contact)   call with the following code:

if ($this->sendEmail($contact)) {
    $this->get('session')->setFlash('notice', 'Email send!');
} else {
    $this->get('session')->setFlash('notice', 'Could not send Email!');
}

And to render it add this to the contact template:

{% if app.session.hasFlash('notice') %}
    <div class="flash-notice">
        {{ app.session.flash('notice') }}
    </div>
{% endif %}

Replacing hard-coded strings / Using configurable settings

In my opinion, hard-coding things like an email address in a controller is just bad practice, so lets go ahead and replace the hard coded string:

    /**
     * @param Contact $contact
     * @return type 1 (true) if send successfully, 0 (false) otherwise
     */

    private function sendEmail($contact) {
        $message = \Swift_Message::newInstance()
            ->setSubject('Contact from example.com')
            ->setFrom($container->getParameter('youridentifier_yourbundle_contact_email_from'))
            ->setTo($container->getParameter('youridentifier_yourbundle_contact_email_to'))
            ->setBody($this->renderView('YourIdentifierYourBundle:Page:email.txt.twig', array('contact' => $contact)))
        ;
        return $this->get('mailer')->send($message);
    }

Create a file called parameters.ini (or use the one in /app):

; src/YourIdentifier/YourBundle/Resources/config/parameters.ini
[parameters]
    youridentifier_yourbundle_contact_email_from = contact@example.com
    youridentifier_yourbundle_contact_email_to = yourmail@example.com
If you created a project specific parameters.ini add it to the main config.yml:
; app/config/config.yml
; [...]
imports
:
    - { resource
: parameters.ini }
    - { resource
: security.yml }
    - { resource
: @YourIdentifierYourBundle/Resources/config/parameters.ini }
;[...]

And that is it for this part of the symfony2 tutorial. You should now be able to write interactive pages in Symfony2, including creating forms.

If there are any questions please feel free to ask. Feedback is also always welcome :)

The next part of this tutorial is: Storing Data – Entities, Doctrine, Sql and an overview over all parts can be seen at Symfony2 Tutorial for Beginners.

9 thoughts on “Symfony2 Tutorial: User Submissions – Controller, Forms, E-Mails

  1. Thanks for a great tutorial! A couple of notes:

    – I had to add my bundle to the bundles array in config.yml to get this to work.
    – had to put the yuicompressor in place: app/Resources/java/yuicompressor-2.4.7.jar
    – Had to change the twig.html files to something like
    {% stylesheets filter=’?yui_css’ output=’css/compiled-main.css’
    ‘@MyBundle/Resources/public/css/*’%}

  2. Hello, I have a problem with this do you know how can I fix it? Thanks

    FatalErrorException: Compile Error: Declaration of Scoveco\MainBundle\Form\ContactType::buildForm() must be compatible with Symfony\Component\Form\FormTypeInterface::buildForm(Symfony\Component\Form\FormBuilderInterface $builder, array $options) in L:\xampp\htdocs\SymfonyTest\src\Scoveco\MainBundle\Form\ContactType.php line 9

  3. Hi,
    I had to do this, for the classes are ambiguous in amce:
    1. declare the right class:
    public function contactAction() {
    $contact = new \gimba1\SymfonyBundle\Entity\Contact();
    $form = $this->createForm(new \gimba1\SymfonyBundle
    \Form\ContactType(), $contact);

    2. use FormBuilderInterface (not FormBuilder):
    namespace gimba1\SymfonyBundle\Form;
    use Symfony\Component\Form\AbstractType;
    use Symfony\Component\Form\FormBuilderInterface;

    3. and finally I get an ArgumentException, though all is there and fits:
    Unable to find template “gimba1SymfonyBundle:Page:contact.html.twig”.

    Best regards
    Axel Arnold Bangert – Herzogenrath 2014

  4. Hi,
    OK, the reference here in the above example is wrong:
    /src/YourIdentifier/YourBundle/Resources/views/contact.html.twig

    it has to be:
    /src/YourIdentifier/YourBundle/Resources/views/Page/contact.html.twig

    relating to:
    @Template(“YourIdentifierYourBundle:Page:contact.html.twig”)

    It works.
    Axel Arnold Bangert – Herzogenrath 2014

  5. Hi,
    the code for sendEmail() in PageController.php has to be like that:

    private function sendEmail($contact) {
    $message = \Swift_Message::newInstance()
    ->setSubject($contact->getSubject())
    ->setFrom(“xxxxxx@yyyyyyy.de”)
    ->setTo($contact->getEmail())
    ->setBody($contact->getMessage())
    ;
    return $this->get(‘mailer’)->send($message);
    }
    Best regards
    Axel Arnold Bangert – Herzogenrath 2014

  6. Hi,
    you can add this piece of code to get a feedback for delivery right on the contact top:
    $request = $this->getRequest();
    if ($request->getMethod() == ‘POST’) {
    $form->bind($request);
    if ($form->isValid()) {
    if ($this->sendEmail($contact)){
    echo (“Die Email wurde erfolgreich an:”.$contact->getEmail().”versandt.”);
    }
    else{
    echo “Es gab einen Fehler beim Email Versand.”;
    }
    }
    }
    Best regards
    Axel Arnold Bangert – Herzogenrath 2014

  7. Hi,
    and if you want to use the sender email from out of parameters.yml:
    parameters:
    contact_email_from: xxx.xxxxxxx@yyyyyyyy.de
    […]
    you have to use this code:
    private function sendEmail($contact) {
    $message = \Swift_Message::newInstance()
    ->setSubject($contact->getSubject())
    ->setFrom($this->container->getParameter(‘contact_email_from’))
    ->setTo($contact->getEmail())
    ->setBody($contact->getMessage())
    ;
    return $this->get(‘mailer’)->send($message);
    }
    Best regards
    Axel Arnold Bangert – Herzogenrath 2014

  8. The$container->getParameter – Think isn´t explained well here… (and doesn´t work…)
    Can you please explain it for version 2.4 ?

  9. @jk: It just loads values from the config file (config.yml). I’m not sure why it wouldn’t work for you. Have you defined those values? What’s your error message? If it really does not work, you could always just hardcode the strings, I guess.

Leave a Reply

Your email address will not be published.