Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Registration Form

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Let's add a registration form to our site. There's a funny thing about registration forms: they have basically nothing to do with security! Think about it: the point of a registration form is just to insert new users into the database. So creating a registration form is really not any different than creating a form to insert any data into your database.

And to make things even simpler, we're going to cheat... by generating code. Find your terminal and run:

symfony console make:registration-form

Ooh! This gives us an error! It says:

Missing packages: run composer require form validator

In this Symfony 5 series, we haven't talked about the Form component. And that's in part because it hasn't changed much since our Symfony 4 tutorial. We're not going to go into too much detail about it right now, but we do need it to run this command. So let's install both packages:

composer require form validator

Awesome. When that finishes, run:

symfony console make:registration-form

again. Cool! So the first question asks:

Do we want to add a @UniqueEntity validation annotation to our User class to make sure duplicate accounts aren't created.

You almost definitely want to say "Yes" so that the user gets a validation error if they enter an email that's already taken.

Next:

Do you want to send an email to verify the user's email address after registration?

We're going to add this later, but I want to do it manually. So say "No".

Do you want to automatically authenticate the user after registration?

That sounds awesome, but say "No", because we're also going to do that manually. I know, I'm making us work! The last question is:

What route should the user be redirected to after registration?

Let's just use our homepage route. So that's number 16 for me. And... done!

Checking out the Generated Code

This command just gave us a RegistrationController, a form type, and a template that renders that form. Let's... go check that stuff out!

Start with the controller: src/Controller/RegistrationController.php:

... lines 1 - 12
class RegistrationController extends AbstractController
{
/**
* @Route("/register", name="app_register")
*/
public function register(Request $request, UserPasswordHasherInterface $userPasswordHasher): Response
{
$user = new User();
$form = $this->createForm(RegistrationFormType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// encode the plain password
$user->setPassword(
$userPasswordHasher->hashPassword(
$user,
$form->get('plainPassword')->getData()
)
);
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($user);
$entityManager->flush();
// do anything else you need here, like send an email
return $this->redirectToRoute('app_homepage');
}
return $this->render('registration/register.html.twig', [
'registrationForm' => $form->createView(),
]);
}
}

Again, we're not going to talk much about the Form component. But, on a high level, this controller creates a User object and then, on submit, it hashes the plain password that was submitted and then saves the User. This is exactly the same thing that we're doing in our fixtures to create users: there's nothing special about this at all.

Fixing the Form Styling

So... let's see what this looks like! Head over to /register to see... the world's ugliest form! We... can do better. The template for this page is registration/register.html.twig. Open that up:

{% extends 'base.html.twig' %}
{% block title %}Register{% endblock %}
{% block body %}
<h1>Register</h1>
{{ form_start(registrationForm) }}
{{ form_row(registrationForm.email) }}
{{ form_row(registrationForm.plainPassword, {
label: 'Password'
}) }}
{{ form_row(registrationForm.agreeTerms) }}
<button type="submit" class="btn">Register</button>
{{ form_end(registrationForm) }}
{% endblock %}

and... I'm just going to add a couple of divs to give this more structure. Awesome... then indent all of this form stuff to be inside of those... and then we just need 3 closing divs on the bottom:

... lines 1 - 4
{% block body %}
<div class="container">
<div class="row">
<div class="bg-light mt-4 p-4">
<h1>Register</h1>
{{ form_start(registrationForm) }}
{{ form_row(registrationForm.email) }}
{{ form_row(registrationForm.plainPassword, {
label: 'Password'
}) }}
{{ form_row(registrationForm.agreeTerms) }}
<button type="submit" class="btn">Register</button>
{{ form_end(registrationForm) }}
</div>
</div>
</div>
{% endblock %}

Cool. That doesn't really fix the form... but at least our ugly form sort of appear in the center of the page. Oh, but let me fix my typo on the mt-4. And... yea, that looks better.

To fix the form itself, we can tell Symfony to output the form with markup that's Bootstrap 5-friendly. This is... kind of a topic for the form tutorial, but it's easy. Go to config/packages/twig.yaml. Here, add an option called form_themes with one new item: boostrap_5_layout.html.twig:

twig:
default_path: '%kernel.project_dir%/templates'
form_themes:
- bootstrap_5_layout.html.twig
... lines 6 - 9

Try it now and... woh! That made a huge difference! Oh, but let me add one more class to that registration button... so that it's not invisible: btn-primary:

... lines 1 - 4
{% block body %}
<div class="container">
<div class="row">
<div class="bg-light mt-4 p-4">
... lines 9 - 10
{{ form_start(registrationForm) }}
... lines 12 - 17
<button type="submit" class="btn btn-primary">Register</button>
{{ form_end(registrationForm) }}
</div>
</div>
</div>
{% endblock %}

Cool.

And while we're making things look and work nicely, we can finally make the "Sign up" button.. actually go somewhere. In base.html.twig, search for "Sign up" - here it is - set the href to path() and target the new route, which... if we look... is called app_register:

... lines 1 - 12
class RegistrationController extends AbstractController
{
/**
* @Route("/register", name="app_register")
*/
public function register(Request $request, UserPasswordHasherInterface $userPasswordHasher): Response
{
... lines 20 - 43
}
}

So path('app_register'):

... line 1
<html>
... lines 3 - 14
<body
... lines 16 - 21
<nav
class="navbar navbar-expand-lg navbar-light bg-light px-1"
{{ is_granted('ROLE_PREVIOUS_ADMIN') ? 'style="background-color: red !important"' }}
>
<div class="container-fluid">
... lines 27 - 35
<div class="collapse navbar-collapse" id="navbar-collapsable">
... lines 37 - 47
{% if is_granted('IS_AUTHENTICATED_REMEMBERED') %}
... lines 49 - 73
{% else %}
... line 75
<a href="{{ path('app_register') }}" class="btn btn-dark">Sign up</a>
{% endif %}
</div>
</div>
</nav>
... lines 81 - 85
</body>
</html>

Beautiful!

This would now work if we tried it. But, before we do, I want to add one other feature to this. After successfully submitting the registration form, I want to automatically authenticate the user. Is that possible? Of course! Let's do it next.

Leave a comment!

2
Login or Register to join the conversation
Ad F. Avatar

thanks !

Reply

Yo! No problem! We always happy if our users get what they want from the course!

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial also works great for Symfony 6!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.4.1 || ^8.0.0",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "babdev/pagerfanta-bundle": "^3.3", // v3.3.0
        "composer/package-versions-deprecated": "^1.11", // 1.11.99.4
        "doctrine/annotations": "^1.0", // 1.13.2
        "doctrine/doctrine-bundle": "^2.1", // 2.6.3
        "doctrine/doctrine-migrations-bundle": "^3.0", // 3.1.1
        "doctrine/orm": "^2.7", // 2.10.1
        "knplabs/knp-markdown-bundle": "^1.8", // 1.9.0
        "knplabs/knp-time-bundle": "^1.11", // v1.16.1
        "pagerfanta/doctrine-orm-adapter": "^3.3", // v3.3.0
        "pagerfanta/twig": "^3.3", // v3.3.0
        "phpdocumentor/reflection-docblock": "^5.2", // 5.2.2
        "scheb/2fa-bundle": "^5.12", // v5.12.1
        "scheb/2fa-qr-code": "^5.12", // v5.12.1
        "scheb/2fa-totp": "^5.12", // v5.12.1
        "sensio/framework-extra-bundle": "^6.0", // v6.2.0
        "stof/doctrine-extensions-bundle": "^1.4", // v1.6.0
        "symfony/asset": "5.3.*", // v5.3.4
        "symfony/console": "5.3.*", // v5.3.7
        "symfony/dotenv": "5.3.*", // v5.3.8
        "symfony/flex": "^1.3.1", // v1.17.5
        "symfony/form": "5.3.*", // v5.3.8
        "symfony/framework-bundle": "5.3.*", // v5.3.8
        "symfony/monolog-bundle": "^3.0", // v3.7.0
        "symfony/property-access": "5.3.*", // v5.3.8
        "symfony/property-info": "5.3.*", // v5.3.8
        "symfony/rate-limiter": "5.3.*", // v5.3.4
        "symfony/runtime": "5.3.*", // v5.3.4
        "symfony/security-bundle": "5.3.*", // v5.3.8
        "symfony/serializer": "5.3.*", // v5.3.8
        "symfony/stopwatch": "5.3.*", // v5.3.4
        "symfony/twig-bundle": "5.3.*", // v5.3.4
        "symfony/ux-chartjs": "^1.3", // v1.3.0
        "symfony/validator": "5.3.*", // v5.3.8
        "symfony/webpack-encore-bundle": "^1.7", // v1.12.0
        "symfony/yaml": "5.3.*", // v5.3.6
        "symfonycasts/verify-email-bundle": "^1.5", // v1.5.0
        "twig/extra-bundle": "^2.12|^3.0", // v3.3.3
        "twig/string-extra": "^3.3", // v3.3.3
        "twig/twig": "^2.12|^3.0" // v3.3.3
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.3", // 3.4.0
        "symfony/debug-bundle": "5.3.*", // v5.3.4
        "symfony/maker-bundle": "^1.15", // v1.34.0
        "symfony/var-dumper": "5.3.*", // v5.3.8
        "symfony/web-profiler-bundle": "5.3.*", // v5.3.8
        "zenstruck/foundry": "^1.1" // v1.13.3
    }
}