Adding a contact form

Let’s create a new page for a contact form.

Add another controller action to the PageController class:

          return [
              'bikes' => $bikes,
          ];
      }

+     /**
+      * @Route("/contact")
+      * @Template
+      */
+     public function contactAction()
+     {
+         return [];
+     }
  }

and create a new frontend template for it:

./bin/console perform-dev:create:page AppBundle:Page:contact --frontend twbs3

Finally, update the frontend menu in nav.html.twig to link to the new route.

  <ul class="nav navbar-nav">
    {% block menu %}
      <li><a href="/">Home</a></li>
+     <li><a href="{{path('app_page_contact')}}">Contact</a></li>
    {% endblock %}
  </ul>

Note

We are using the path twig function to link to the new action, but you could simply write /contact as well. Remember you’ll need to update the url manually whe using this method if the route ever changes.

nav.html.twig was generated by the bootstrap 3 frontend. Not all frontends will create this file; you may need to update the frontend menu (if one even exists) in a different file when using a different frontend.

Integrating PerformContactBundle

The PerformContactBundle provides a contact form type, with tools to handle submissions and sending of notifications when a message is sent. We’ll use this bundle to construct a contact form quickly.

Update the contactAction method:

+ use Symfony\Component\HttpFoundation\Request;
+ use Perform\ContactBundle\Form\Type\MessageType;
  /**
   * @Route("/contact")
   * @Template
   */
- public function contactAction()
+ public function contactAction(Request $request)
+ {
+     $form = $this->createForm(MessageType::class);
+     $handler = $this->get('perform_contact.form.handler.contact');

+     try {
+         $result = $handler->handleRequest($request, $form);
+         if ($result) {
+             $this->addFlash('success', 'Thank you. Your message has been sent.');

+             return $this->redirectToRoute('app_page_contact');
+         }
+     } catch (\Exception $e) {
+         $this->get('logger')->error($e);
+         $this->addFlash('danger', 'An error occurred. Please try again.');
+     }

-     return [];
+     return [
+         'form' => $form->createView(),
+     ];
  }

This will look familiar if you’ve used forms in a Symfony controller action before. We create a new form with Perform\ContactBundle\Form\Type\MessageType, redirecting with a flash message on success, and showing an error message on failure. However, instead of handling the form submission ourselves, we get the perform_contact.form.handler.contact service to handle it for us. This service checks the form submission, saves a new message to the database, checks for spam, and sends notifications when configured. See the PerformContactBundle documentation for more information on how this works.

Now update contact.html.twig:

- <div class="col-md-12">
-   <h1>Contact</h1>
+ <div class="col-md-6">
+   <h1>Contact Us</h1>
+   {{form_start(form)}}
+   {{form_row(form.name)}}
+   {{form_row(form.email)}}
+   {{form_row(form.message)}}
+   <button type="submit" class="btn btn-primary">Send</button>
+   {{form_end(form)}}
  </div>

Head to the new page at http://127.0.0.1:8000/contact and fill out the form. The page should refresh, and you’ll be shown a success message.

../_images/contact_form.png

Now head to the admininstration area and click on the ‘Contact Form’ link. You’ll see a grid of form submissions, with buttons to archive messages you’ve dealt with and mark messages as spam.

../_images/contact_admin.png

Configuring notifications

You can be notified of successful form submissions in a variety of ways.

By default, email notifications will be sent to the email address you configure in the settings page. Open the ‘Contact Form’ panel in the ‘Settings’ page of the admin and add an email address to send notifications too.

../_images/contact_settings.png

Depending on your system, you might need to update the swiftmailer bundle configuration to send emails correctly. By default, it uses the values of the mailer_* parameters in app/config/parameters.yml. See the Swiftmailer bundle documentation for more information.

The delivery_address setting can be useful for local development. All emails will be sent to this address, with the original address being included in the X-Swift-To email header.

Once you’ve configured email sending, try submitting the form again. You’ll be sent an email notification with details of the submission.

Extending the message entity

Unfortunately Perform’s message entity doesn’t quite fit our needs. We’ve been asked to include another optional field in the form; favourite bike, asking for the visitor’s favourite bike on the site.

Does this mean we have to scrap the PerformContactBundle, the admin interface, and all the tooling? Of course not! Perform has tools that make it easy to extend an entity from a vendor bundle to fit your requirements.

First, create a new entity class that extends Perform\ContactBundle\Entity\Message:

<?php

namespace AppBundle\Entity;

use Perform\ContactBundle\Entity\Message;

class ContactMessage extends Message
{
    protected $favouriteBike;

    /**
     * @param Bike|null $favouriteBike
     *
     * @return ContactMessage
     */
    public function setFavouriteBike(Bike $favouriteBike = null)
    {
        $this->favouriteBike = $favouriteBike;

        return $this;
    }

    /**
     * @return Bike|null
     */
    public function getFavouriteBike()
    {
        return $this->favouriteBike;
    }
}

Then create a doctrine mapping file, but only include the new favouriteBike property:

AppBundle\Entity\ContactMessage:
    type: entity
    manyToOne:
        favouriteBike:
            targetEntity: Bike
            joinColumn:
                nullable: true

Note

You could also use the perform-dev:create:entity command to create these files, but remember to remove the id and other unrelated fields, and to extend the Perform\ContactBundle\Entity\Message class.

Now for the clever bit - we will tell Perform that this entity extends the message entity in the contact bundle. All the tools in the contact bundle will continue to work, even though they have no knowledge about our new entity.

Add an entry to perform_base:extended_entities in app/config/config.yml:

  perform_base:
+     extended_entities:
+         "PerformContactBundle:Message": "AppBundle:ContactMessage"
      panels:
          left: []

Note

Under the hood, Perform will rewrite some Doctrine metadata to treat the original message entity like an abstract mapped superclass.

Read the extending entities documentation for a look into how it works.

Now update the database schema, where you’ll notice creation of a table for the new message entity. With the --complete flag, the existing perform_contact_message table will be removed.

./bin/console doctrine:schema:update --force --dump-sql --complete

Extending the contact form

Now we need to add the new option to the contact form.

Create a new form type in src/AppBundle/Form/Type/ContactMessageType.php that extends the existing form type:

<?php

namespace AppBundle\Form\Type;

use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Perform\ContactBundle\Form\Type\MessageType;
use AppBundle\Entity\ContactMessage;

class ContactMessageType extends MessageType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        parent::buildForm($builder, $options);

        $builder->add('favouriteBike', EntityType::class, [
            'class' => 'AppBundle:Bike',
            'choice_label' => 'title',
        ]);
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => ContactMessage::class,
        ]);
    }
}

And update the controller action to use this new form type:

  use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
- use Perform\ContactBundle\Form\Type\MessageType;
+ use AppBundle\Form\Type\ContactMessageType;
  use Symfony\Component\HttpFoundation\Request;
  public function contactAction(Request $request)
  {
-     $form = $this->createForm(MessageType::class);
+     $form = $this->createForm(ContactMessageType::class);
      $handler = $this->get('perform_contact.form.handler.contact');

Finally, update the twig template for the page:

  {{form_start(form)}}
  {{form_row(form.name)}}
  {{form_row(form.email)}}
+ {{form_row(form.favouriteBike)}}
  {{form_row(form.message)}}
  <button type="submit" class="btn btn-primary">Send</button>
  {{form_end(form)}}

That’s it! Refresh the contact page and you’ll see the new form field. All contact form submissions will now be an instance of AppBundle\Entity\ContactMessage, saving the favouriteBike field as well.

Extending the message crud

There is just one piece missing. We can save form submissions with the new field, but it doesn’t appear in the admin yet.

Create a new crud class:

./bin/console perform-dev:create:crud AppBundle:ContactMessage

and make some modifications.

First, extend the existing crud from the contact bundle:

- use Perform\BaseBundle\Crud\AbstractCrud;
+ use Perform\ContactBundle\Crud\MessageCrud;
  use Perform\BaseBundle\Config\FieldConfig;
  use Perform\BaseBundle\Config\FilterConfig;
  use Perform\BaseBundle\Config\ActionConfig;

- class ContactMessageCrud extends AbstractCrud
+ class ContactMessageCrud extends MessageCrud

Then add the new field to configureFields, making sure to also call the parent method:

  public function configureFields(FieldConfig $config)
  {
+     parent::configureFields($config);
+
+     $config->add('favouriteBike', [
+         'type' => 'entity',
+         'options' => [
+             'class' => 'AppBundle:Bike',
+             'display_field' => 'title',
+         ],
+     ]);
  }

Finally, either call the parent method in configureFilters, or remove the method entirely.

  public function configureFilters(FilterConfig $config)
  {
+     parent::configureFilters($config);
  }

After refreshing the page, the contact message admin page should display the new field.

Extending the message view template

While the new field shows up in the list of messages, unfortunately it’s not visible when viewing the messages.

This is because the contact bundle has a different template for viewing message entities. Let’s override that template with our own version that displays the visitor’s favourite bike too.

Override the getTemplate method of ContactMessageCrud to the following:

  use Perform\BaseBundle\Config\ActionConfig;
  use Perform\ContactBundle\Crud\MessageCrud;
+ use Twig\Environment;
<?php

public function getTemplate(Environment $twig, $entityName, $context)
{
    if ($context === CrudRequest::CONTEXT_VIEW) {
        return '@App/crud/contact_message/view.html.twig';
    }

    return parent::getTemplate($twig, $entityName, $context);
}

Here we override the template, but only for the view context. All other contexts fall back to the default behaviour of the parent crud class.

Note

See the crud documentation for more information on overriding templates.

Create the file src/AppBundle/Resources/views/crud/contact_message/view.html.twig for this new view and insert the following:

{% extends '@PerformContact/crud/message/view.html.twig' %}

{% block extras %}
  {% if entity.favouriteBike is not null %}
    <p>
      Favourite bike:
      <a href="{{perform_crud_route(entity.favouriteBike, 'view')}}">
        {{entity.favouriteBike.title}}
      </a>
    </p>
  {% endif %}
{% endblock %}

Now a link to the visitor’s favourite bike will be shown when viewing the contact form message.