You are browsing the Symfony 4 documentation, which changes significantly from Symfony 3.x. If your app doesn't use Symfony 4 yet, browse the Symfony 3.4 documentation.

How to Implement a Simple Registration Form

4.1 version
Maintained Unmaintained

How to Implement a Simple Registration Form

Creating a registration form works the same as creating any form. You configure the form to update some User model object (a Doctrine entity in this example) and then save it.

First, make sure you have all the dependencies you need installed:

1
$ composer require symfony/orm-pack symfony/form symfony/security-bundle symfony/validator

If you don't already have a User entity and a working login system, first start by following Security.

Your User entity will probably at least have the following fields:

username
This will be used for logging in, unless you instead want your user to log in via email (in that case, this field is unnecessary).
email
A nice piece of information to collect. You can also allow users to log in via email.
password
The encoded password.
plainPassword
This field is not persisted: (notice no @ORM\Column above it). It temporarily stores the plain password from the registration form. This field can be validated and is then used to populate the password field.

With some validation added, your class may look something like this:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// src/Entity/User.php
namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\UserInterface;

/**
 * @ORM\Entity
 * @UniqueEntity(fields="email", message="Email already taken")
 * @UniqueEntity(fields="username", message="Username already taken")
 */
class User implements UserInterface
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255, unique=true)
     * @Assert\NotBlank
     * @Assert\Email
     */
    private $email;

    /**
     * @ORM\Column(type="string", length=255, unique=true)
     * @Assert\NotBlank
     */
    private $username;

    /**
     * @Assert\NotBlank
     * @Assert\Length(max=4096)
     */
    private $plainPassword;

    /**
     * The below length depends on the "algorithm" you use for encoding
     * the password, but this works well with bcrypt.
     *
     * @ORM\Column(type="string", length=64)
     */
    private $password;

    /**
     * @ORM\Column(type="array")
     */
    private $roles;

    public function __construct()
    {
        $this->roles = array('ROLE_USER');
    }

    // other properties and methods

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

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

    public function getUsername()
    {
        return $this->username;
    }

    public function setUsername($username)
    {
        $this->username = $username;
    }

    public function getPlainPassword()
    {
        return $this->plainPassword;
    }

    public function setPlainPassword($password)
    {
        $this->plainPassword = $password;
    }

    public function getPassword()
    {
        return $this->password;
    }

    public function setPassword($password)
    {
        $this->password = $password;
    }

    public function getSalt()
    {
        // The bcrypt and argon2i algorithms don't require a separate salt.
        // You *may* need a real salt if you choose a different encoder.
        return null;
    }

    public function getRoles()
    {
        return $this->roles;
    }

    public function eraseCredentials()
    {
    }
}

The UserInterface requires a few other methods and your security.yaml file needs to be configured properly to work with the User entity. For a more complete example, see the Security Guide.

Notice that the plainPassword field has a max length of 4096 characters. For security purposes (CVE-2013-5750), Symfony limits the plain password length to 4096 characters when encoding it. Adding this constraint makes sure that your form will give a validation error if anyone tries a super-long password.

You'll need to add this constraint anywhere in your application where your user submits a plaintext password (e.g. change password form). The only place where you don't need to worry about this is your login form, since Symfony's Security component handles this for you.

Create a Form for the Entity

Next, create the form for the User entity:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// src/Form/UserType.php
namespace App\Form;

use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;

class UserType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('email', EmailType::class)
            ->add('username', TextType::class)
            ->add('plainPassword', RepeatedType::class, array(
                'type' => PasswordType::class,
                'first_options'  => array('label' => 'Password'),
                'second_options' => array('label' => 'Repeat Password'),
            ))
        ;
    }

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

There are just three fields: email, username and plainPassword (repeated to confirm the entered password).

Tip

To explore more things about the Form component, read the Forms guide.

Handling the Form Submission

Next, you need a controller to handle the form rendering and submission. If the form is submitted, the controller performs the validation and saves the data into the database:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// src/Controller/RegistrationController.php
namespace App\Controller;

use App\Form\UserType;
use App\Entity\User;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;

class RegistrationController extends AbstractController
{
    /**
     * @Route("/register", name="user_registration")
     */
    public function register(Request $request, UserPasswordEncoderInterface $passwordEncoder)
    {
        // 1) build the form
        $user = new User();
        $form = $this->createForm(UserType::class, $user);

        // 2) handle the submit (will only happen on POST)
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {

            // 3) Encode the password (you could also do this via Doctrine listener)
            $password = $passwordEncoder->encodePassword($user, $user->getPlainPassword());
            $user->setPassword($password);

            // 4) save the User!
            $entityManager = $this->getDoctrine()->getManager();
            $entityManager->persist($user);
            $entityManager->flush();

            // ... do any other work - like sending them an email, etc
            // maybe set a "flash" success message for the user

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

        return $this->render(
            'registration/register.html.twig',
            array('form' => $form->createView())
        );
    }
}

To define the algorithm used to encode the password in step 3 configure the encoder in the security configuration:

  • YAML
    1
    2
    3
    4
    # config/packages/security.yaml
    security:
        encoders:
            App\Entity\User: bcrypt
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    <!-- config/packages/security.xml -->
    <?xml version="1.0" charset="UTF-8" ?>
    <srv:container xmlns="http://symfony.com/schema/dic/security"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:srv="http://symfony.com/schema/dic/services"
        xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
    
        <config>
            <encoder class="App\Entity\User">bcrypt</encoder>
        </config>
    </srv:container>
    
  • PHP
    1
    2
    3
    4
    5
    6
    7
    8
    // config/packages/security.php
    use App\Entity\User;
    
    $container->loadFromExtension('security', array(
        'encoders' => array(
            User::class => 'bcrypt',
        ),
    ));
    

In this case the recommended bcrypt algorithm is used. If needed, check out the user password encoding article.

Next, create the template:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{# templates/registration/register.html.twig #}

{{ form_start(form) }}
    {{ form_row(form.username) }}
    {{ form_row(form.email) }}
    {{ form_row(form.plainPassword.first) }}
    {{ form_row(form.plainPassword.second) }}

    <button type="submit">Register!</button>
{{ form_end(form) }}

See How to Customize Form Rendering for more details.

Update your Database Schema

If you've updated the User entity during this tutorial, you have to update your database schema using this command:

1
2
$ php bin/console doctrine:migrations:diff
$ php bin/console doctrine:migrations:migrate

That's it! Head to /register to try things out!

Having a Registration form with only Email (no Username)

If you want your users to login via email and you don't need a username, then you can remove it from your User entity entirely. Instead, make getUsername() return the email property:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// src/Entity/User.php
// ...

class User implements UserInterface
{
    // ...

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

    // ...
}

Next, update the providers section of your security.yaml file so that Symfony knows how to load your users via the email property on login. See Using a Custom Query to Load the User.

Adding a "accept terms" Checkbox

Sometimes, you want a "Do you accept the terms and conditions" checkbox on your registration form. The only trick is that you want to add this field to your form without adding an unnecessary new termsAccepted property to your User entity that you'll never need.

To do this, add a termsAccepted field to your form, but set its mapped option to false:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// src/Form/UserType.php
// ...
use Symfony\Component\Validator\Constraints\IsTrue;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;

class UserType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('email', EmailType::class);
            // ...
            ->add('termsAccepted', CheckboxType::class, array(
                'mapped' => false,
                'constraints' => new IsTrue(),
            ))
        );
    }
}

The constraints option is also used, which allows us to add validation, even though there is no termsAccepted property on User.

Manually Authenticating after Success

If you're using Guard authentication, you can automatically authenticate after registration is successful.

This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.