Override Default Templates and Forms in Symfony 3

Published Apr 13, 2017
Override Default Templates and Forms in Symfony 3

If you have ever worked with Symfony, you must be aware of the FOSUserBundle that is often used for user management and setting up registration systems. This bundle provides rather simple, yet comprehensive functionality for complete user management with basic features like login,registration,profile and password reset.

However, the problem is that the bundle only offers limited fields while registering a user. Similarly, the views and templates are very basic in terms of UI. The best thing about FOSUserBundle is you can override the forms and templates easily.

In this article, I will override the default registration form of FOSUserBundle and add custom fields to it. Later on, I will also override the basic templates of the login page and try to give it a nicer look. I will start by first adding the custom fields to the form and save these fields in the database.

Add Property and Validators

While looking at the default User entity of FOSUserBundle, you will only find the ID property in the entity protected $id, along with the data type and auto increment attributes.

     <?php
    // src/AppBundle/Entity/User.php

    namespace AppBundle\Entity;

    use FOS\UserBundle\Model\User as BaseUser;
    use Doctrine\ORM\Mapping as ORM;

    /**
     * @ORM\Entity
     * @ORM\Table(name="fos_user")
     */
    class User extends BaseUser
    {
        /**
         * @ORM\Id
         * @ORM\Column(type="integer")
         * @ORM\GeneratedValue(strategy="AUTO")
         */
        protected $id;

        public function __construct()
        {
            parent::__construct();

        }
    }

To expand the form, I will add two more fields First Name and Last Name to the form. For this, go to src/AppBundle/Entity and open up User.php. Add two more properties, just below the protected $id;

    /**
         * @ORM\Column(type="string")
         */
        protected $first_name;

         /**
         * @ORM\Column(type="string")
         */
        protected $last_name;

Save the file. In the next step, I will generate the methods for these new properties.

Generate Entity Methods and Fields

Move to the project’s root destination and run the following command to generate methods for new properties.

php bin/console doctrine:generate:entities AppBundle/Entity/User

This command will create the required methods for the first name and the last name. At this point, the User entity looks like this:

<?php
      // src/AppBundle/Entity/User.php
      ​
      namespace AppBundle\Entity;
      ​
      use FOS\UserBundle\Model\User as BaseUser;
      use Doctrine\ORM\Mapping as ORM;
      ​
      /**
       * @ORM\Entity
       * @ORM\Table(name="fos_user")
       */
      class User extends BaseUser
      {
          /**
           * @ORM\Id
           * @ORM\Column(type="integer")
           * @ORM\GeneratedValue(strategy="AUTO")
           */
          protected $id;
      ​
           /**
           * @ORM\Column(type="string")
           */
          protected $first_name;
      ​
           /**
           * @ORM\Column(type="string")
           */
          protected $last_name;
      ​
          public function __construct()
          {
              parent::__construct();
              // your own logic
          }
      ​
          /**
           * Set firstName
           *
           * @param string $firstName
           *
           * @return User
           */
          public function setFirstName($firstName)
          {
              $this->first_name = $firstName;
      ​
              return $this;
          }
      ​
          /**
           * Get firstName
           *
           * @return string
           */
          public function getFirstName()
          {
              return $this->first_name;
          }
      ​
          /**
           * Set lastName
           *
           * @param string $lastName
           *
           * @return User
           */
          public function setLastName($lastName)
          {
              $this->last_name = $lastName;
      ​
              return $this;
          }
      ​
          /**
           * Get lastName
           *
           * @return string
           */
          public function getLastName()
          {
              return $this->last_name;
          }
      }

The User entity now has new methods for the required fields. it’s time to update the schema so that the name fields could appear in the database also.

php bin/console doctrine:schema:update --force

The above command updates the database and adds first_name and last_name columns. After the update, the database fields will look like:

Note: I'm using live server on Cloudways They provide Custom UI for database that's why the view is different.

Create Registration Form Type

Next, it’s time to create a custom form type in Symfony. This form type integrates both the custom and default input fields.

I need to create a form type in the bundle. This new class inherits from the base FOSUserBundle fos_user_registration type using the form type hierarchy and then adds in the custom fields.

Create a Form folder in the src/AppBundle and create a file RegistrationType.php in it. Add the following code to this file.

    <?php
    // src/AppBundle/Form/RegistrationType.php

    namespace AppBundle\Form;

    use Symfony\Component\Form\AbstractType;
    use Symfony\Component\Form\FormBuilderInterface;

    class RegistrationType extends AbstractType
    {
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
            $builder->add('first_name');
            $builder->add('last_name');
        }

        public function getParent()
        {
            return 'FOS\UserBundle\Form\Type\RegistrationFormType';

        }

        public function getBlockPrefix()
        {
            return 'app_user_registration';
        }

        // For Symfony 2.x
        public function getName()
        {
            return $this->getBlockPrefix();
        }
    }

Declare Form Type Service

Although the custom form type has been created, it will not work until it is defined as a service in the service.yml file. The tag contains the name (whose value is form.type) and the alias (which contains the value returned by getName() method).

    services:
      app.form.registration:
        class: AppBundle\Form\RegistrationType
        tags:
            - { name: form.type, alias: app_user_registration }

Update Config with Custom Form Type

Finally, the above created custom form type should be updated in config.yml, so that the FOSUserBundle use this form type instead of the default one.

    fos_user:
        db_driver: orm # other valid values are 'mongodb', 'couchdb' and 'propel'
        firewall_name: main
        user_class: AppBundle\Entity\User
        registration:
            form:
                type: AppBundle\Form\RegistrationType

Let’s test the above command by registering a user. I have created a demo for this.
Go to the application URL and append /register a user with it:

The user should be successfully registered when you click the Register button.

Now, check the database for the custom values in the First and Last Name column

Using the above procedure, you can definitely add more fields to the registration form and get the data from user as per your requirements.

Next, I will demonstrate how to override the default templates of FOSUserbundle.

Override Default Templates in FOSUserBundle

FOSUserbundle provides several basic templates for login and registration forms (nobody likes these forms!).So how the default templates can be converted to a nicer view? You have two options right now: you could either opt for Bootstrap or any other UI library. For the purpose of this article, I am going to integrate Bootstrap to override the default templates.

In FOSUserBundle, I can override templates in two ways.

  1. Create a new Bundle which is defined as a child of FOSUserBundle.
  2. Create a new template with same name in the Resources folder.

You can use any of the above methods to override the templates. For the purpose of this tutorial, I am going to use the second approach. Let’s start by first creating the new template with the same name.

Copy New Templates In Resources

The first step is to copy all the folders and layout.html.twig file from vendor\friendsofsymfony\user-bundle\Resources\views. Create a new folder FOSUserBundle in app/Resources under FOSUserBundle folder. Additionally, create a new folder views and paste in all the files which you copied earlier. Remember all the copied files will reside in the app/Resources/views folder.

Add Content Block and Bootstrap to base.html.twig

The next step involves editing the base.html.twig file. I will add a content block in the body and bootstrap CDN files URL. All the body content from other layout files should inherit this content block for the application of bootstrap.

In this case, the base.html.twig file looks like this. Copy the following code and paste it in the base file.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>{% block title %}shahroze nawaz!{% endblock %}</title>
        {% block stylesheets %}{% endblock %}
        <link rel="icon" type="image/x-icon" href="{{ asset('favicon.ico') }}" />
        <link rel="icon" type="image/x-icon" href="{{ asset('favicon.ico') }}" />
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" />
    </head>
    <body>

    <div class="container">
    {% block content %}{% endblock %}
    </div>

        <script src = "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
        <script src = "https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
        {% block javascripts %}{% endblock %}
    </body>
</html>

Edit layout.html.twig to Inherit the Content Block

The base.html.twig file is all set now. It is time to inherit the content block in the layout.html.twig. Open it from app\Resources\FOSUserBundle\views\layout.html.twig, remove the html tags from the file and add all the code in content block:

    {% block content %}

    <div>
        {% if is_granted("IS_AUTHENTICATED_REMEMBERED") %}
            {{ 'layout.logged_in_as'|trans({'%username%': app.user.username}, 'FOSUserBundle') }} |
            <a href="{{ path('fos_user_security_logout') }}">
                {{ 'layout.logout'|trans({}, 'FOSUserBundle') }}
            </a>
        {% else %}
            <a href="{{ path('fos_user_security_login') }}">{{ 'layout.login'|trans({}, 'FOSUserBundle') }}</a>
        {% endif %}
    </div>

    {% if app.request.hasPreviousSession %}
        {% for type, messages in app.session.flashbag.all() %}
            {% for message in messages %}
                <div class="flash-{{ type }}">
                    {{ message }}
                </div>
            {% endfor %}
        {% endfor %}
    {% endif %}

    <div>
        {% block fos_user_content %}
        {% endblock fos_user_content %}
    </div>

    {% endblock %}

Do not forget to extend this file with base.html.twig. Add this line to the top of the above code.

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

This will make sure that the layout file inherits all the blocks and scripts from the base layout.

Finally, Override the Login Template

Now I setup all templates Finally i am adding bootstrap form and input classes to login view of FOSUserBundle. Open the login.html.twig from app\Resources\FOSUserBundle\views\Security and add the bootstrap classes to form and input tags. You can use any form type of bootstrap here. The final code of login.html.twig will be this:

    {% extends "FOSUserBundle::layout.html.twig" %}
    {% trans_default_domain 'FOSUserBundle' %}

    {% block fos_user_content %}
    {% if error %}
        <div>{{ error.messageKey|trans(error.messageData, 'security') }}</div>
    {% endif %}

    <form action="{{ path("fos_user_security_check") }}" method="post">
        <input type="hidden" name="_csrf_token" value="{{ csrf_token }}" />

    <div class="formgroup">
        <label for="username">{{ 'security.login.username'|trans }}</label>
        <input class="form-control" type="text" id="username" name="_username" value="{{ last_username }}" required="required" />
    </div>

    <div class="formgroup">
        <label for="password">{{ 'security.login.password'|trans }}</label>
        <input class="form-control" type="password" id="password" name="_password" required="required" />
    <div>

    <div class="checkbox">
        <input type="checkbox" id="remember_me" name="_remember_me" value="on" />
        <label for="remember_me">{{ 'security.login.remember_me'|trans }}</label>
    </div>

        <input class="btn btn-success" type="submit" id="_submit" name="_submit" value="{{ 'security.login.submit'|trans }}" />
    </form>
    {% endblock fos_user_content %}

Save the file and run the application in the browser. If the app does not work, make sure to clear the cache first by running the following command:

php bin/console cache:clear

This command will empty out the cache for all respective folders. Run the application in browser again to see the nicer looks of the login form.

Final Words

FOSUserBundle provides comprehensive user management. Perhaps, the best thing about the bundle is that you could easily add custom functionalities without breaking anything! Similarly, you can also override the default templates of FOSUserBundle and create a more friendly user-interface for the login forms.

If you have any query or questions about the usage of the bundle, feel free to comment below.

Discover and read more posts from Shahroze Nawaz
get started
Enjoy this post?

Leave a like and comment for Shahroze

1