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

That's it for security! We covered authentication and authorization. So, I'm not really sure why I'm still recording.

Oh yea, I remember: let's create a registration form! Actually, this has nothing to do with security: registration is all about creating and saving a User entity. But, there are a few interesting things - call it a bonus round.

Controller, Form, Check!

Start like normal: create a new controller class called UserController - for stuff like registration and maybe future things like reset password:

... lines 1 - 2
namespace AppBundle\Controller;
... lines 4 - 5
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
... lines 7 - 8
class UserController extends Controller
{
... lines 11 - 17
}

Inside, add registerAction() with the URL /register. Let's call the route user_register:

... lines 1 - 4
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
... line 6
use Symfony\Component\HttpFoundation\Request;
class UserController extends Controller
{
/**
* @Route("/register", name="user_register")
*/
public function registerAction(Request $request)
{
}
}

Make sure you have your use statement for @Route.

Next, this will be a nice, normal form situation. So click the Form directory, open the new menu, and create a new Symfony Form. Call it UserRegistrationForm:

... lines 1 - 2
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class UserRegistrationForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
}
public function configureOptions(OptionsResolver $resolver)
{
}
}

Brilliant! Delete the extra getName() method that's super not needed in Symfony 3.

Now, bind the form to User, with $resolver->setDefaults() and a data_class option to set User::class:

... lines 1 - 4
use AppBundle\Entity\User;
... lines 6 - 12
class UserRegistrationForm extends AbstractType
{
... lines 15 - 23
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class
]);
}
}

Next, the fields! And we need two: first an email field set to EmailType::class:

... lines 1 - 6
use Symfony\Component\Form\Extension\Core\Type\EmailType;
... lines 8 - 12
class UserRegistrationForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email', EmailType::class)
... lines 19 - 21
}
... lines 23 - 29
}

Then, we do need a password field, but think about it: the property we want to set on User is not actually the password property. We need to set plainPassword. Add this. It'll be a password type. But, if you want the user to type the password twice, use a RepeatedType. Then, in the third argument, pass the real type with type set to PasswordType::class:

... lines 1 - 7
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
... lines 10 - 12
class UserRegistrationForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email', EmailType::class)
->add('plainPassword', RepeatedType::class, [
'type' => PasswordType::class
]);
}
... lines 23 - 29
}

That'll render two password boxes. And if the values don't match, validation will automatically fail.

Rendering the Form

Form done! In the controller, start with $form = $this->createForm(). And of course, make sure you're extending the Symfony base Controller! Then, pass this UserRegistrationForm::class:

... lines 1 - 4
use AppBundle\Form\UserRegistrationForm;
... line 6
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
... lines 8 - 9
class UserController extends Controller
{
... lines 12 - 14
public function registerAction(Request $request)
{
$form = $this->createForm(UserRegistrationForm::class);
... lines 18 - 21
}
}

Go straight to the template: return $this->render('user/register.html.twig') and pass it $form->createView():

... lines 1 - 16
$form = $this->createForm(UserRegistrationForm::class);
return $this->render('user/register.html.twig', [
'form' => $form->createView()
]);
... lines 22 - 24

Ok, all standard!

As a short cut, I'll hover over the template, press Option+Enter and select "Create Template".

You guys know the drill: extends 'base.html.twig' then override the block body. I'll give us just a little bit of markup to get things rolling:

{% extends 'base.html.twig' %}
{% block body %}
<div class="container">
<div class="row">
<div class="col-xs-12">
<h1>Register!</h1>
... lines 8 - 15
</div>
</div>
</div>
{% endblock %}

Rendering the form is exactly how it always is: form_start(form), form_end(form), and inside, form_row(form.email):

... lines 1 - 2
{% block body %}
<div class="container">
<div class="row">
<div class="col-xs-12">
<h1>Register!</h1>
{{ form_start(form) }}
{{ form_row(form.email) }}
... lines 11 - 14
{{ form_end(form) }}
</div>
</div>
</div>
{% endblock %}

Then form_row(form.plainPassword) - but because we used the RepeatedType, this will render as two fields - so use form.plainPassword.first and form_row(form.plainPassword.second):

... lines 1 - 2
{% block body %}
<div class="container">
<div class="row">
<div class="col-xs-12">
<h1>Register!</h1>
{{ form_start(form) }}
{{ form_row(form.email) }}
{{ form_row(form.plainPassword.first) }}
{{ form_row(form.plainPassword.second) }}
... lines 13 - 14
{{ form_end(form) }}
</div>
</div>
</div>
{% endblock %}

Cool, right?

Finally show off your styling skills by adding a <button type="submit"> with some fancy Bootstrap classes. Don't forget the formnovalidate to disable HTML5 validation. And finally say, register:

... lines 1 - 2
{% block body %}
<div class="container">
<div class="row">
<div class="col-xs-12">
<h1>Register!</h1>
{{ form_start(form) }}
{{ form_row(form.email) }}
{{ form_row(form.plainPassword.first) }}
{{ form_row(form.plainPassword.second) }}
<button type="submit" class="btn btn-primary" formnovalidate>Register</button>
{{ form_end(form) }}
</div>
</div>
</div>
{% endblock %}

That oughta do it! Finish things by adding a link to this from the login page. After the button, add a link to path('user_register'):

... lines 1 - 2
{% block body %}
<div class="container">
<div class="row">
<div class="col-xs-12">
... lines 7 - 14
{{ form_start(form) }}
... lines 16 - 17
<button type="submit" class="btn btn-success">Login <span class="fa fa-lock"></span></button>
&nbsp;
<a href="{{ path('user_register') }}">Register</a>
{{ form_end(form) }}
</div>
</div>
</div>
{% endblock %}

Done! Refresh. Click "Register", and we're rendered.

Fixing the Password fields

Ooh - except for the labels: "First" and "Second": those are terrible! We can fix those real quick: pass a variables array to first with label set to Password. For the second one: Repeat Password:

... lines 1 - 2
{% block body %}
<div class="container">
<div class="row">
<div class="col-xs-12">
<h1>Register!</h1>
{{ form_start(form) }}
... line 10
{{ form_row(form.plainPassword.first, {
'label': 'Password'
}) }}
{{ form_row(form.plainPassword.second, {
'label': 'Repeat Password'
}) }}
... lines 17 - 18
{{ form_end(form) }}
</div>
</div>
</div>
{% endblock %}

Refresh. Looking good.

Saving the User

Since the registration form has nothing to do with security, let's just finish this! Type-hint the Request argument, and then do the normal $form->handleRequest($request):

... lines 1 - 8
use Symfony\Component\HttpFoundation\Request;
class UserController extends Controller
{
... lines 13 - 15
public function registerAction(Request $request)
{
$form = $this->createForm(UserRegistrationForm::class);
$form->handleRequest($request);
... lines 21 - 35
}
}

Then, if($form->isValid()) - to make sure that validation is passed:

... lines 1 - 10
class UserController extends Controller
{
... lines 13 - 15
public function registerAction(Request $request)
{
$form = $this->createForm(UserRegistrationForm::class);
$form->handleRequest($request);
if ($form->isValid()) {
... lines 22 - 30
}
... lines 32 - 35
}
}

In the forms tutorial, we also added $form->isSubmitted() in the if statement, but you technically don't need that: isValid() checks that internally.

Inside the isValid(), set $user = $form->getData():

... lines 1 - 4
use AppBundle\Entity\User;
... lines 6 - 10
class UserController extends Controller
{
... lines 13 - 15
public function registerAction(Request $request)
{
... lines 18 - 20
if ($form->isValid()) {
/** @var User $user */
$user = $form->getData();
... lines 24 - 30
}
... lines 32 - 35
}
}

We know this will be a User object, so I'll plan ahead and add some inline PHP documentation so I get auto-completion later. Add the $em = $this->getDoctrine()->getManager(), $em->persist($user), $em->flush():

... lines 1 - 10
class UserController extends Controller
{
... lines 13 - 15
public function registerAction(Request $request)
{
... lines 18 - 20
if ($form->isValid()) {
/** @var User $user */
$user = $form->getData();
$em = $this->getDoctrine()->getManager();
$em->persist($user);
$em->flush();
... lines 27 - 30
}
... lines 32 - 35
}
}

Now, what do we always do after a successful form submit? We set a flash: $this->addFlash('success') with 'Welcome '.$user->getEmail():

... lines 1 - 10
class UserController extends Controller
{
... lines 13 - 15
public function registerAction(Request $request)
{
... lines 18 - 20
if ($form->isValid()) {
/** @var User $user */
$user = $form->getData();
$em = $this->getDoctrine()->getManager();
$em->persist($user);
$em->flush();
$this->addFlash('success', 'Welcome '.$user->getEmail());
... lines 29 - 30
}
... lines 32 - 35
}
}

Finally, redirect - at least for right now - to the homepage route:

... lines 1 - 10
class UserController extends Controller
{
... lines 13 - 15
public function registerAction(Request $request)
{
... lines 18 - 20
if ($form->isValid()) {
/** @var User $user */
$user = $form->getData();
$em = $this->getDoctrine()->getManager();
$em->persist($user);
$em->flush();
$this->addFlash('success', 'Welcome '.$user->getEmail());
return $this->redirectToRoute('homepage');
}
... lines 32 - 35
}
}

That's it.

Try the whole thing out: weaverryan+15@gmail.com, Password foo. Whoops, and if we just fix my typo, and refresh again:

... lines 1 - 10
class UserController extends Controller
{
... lines 13 - 15
public function registerAction(Request $request)
{
... lines 18 - 20
if ($form->isValid()) {
... lines 22 - 24
$em->persist($user);
... lines 26 - 30
}
... lines 32 - 35
}
}

It's alive!

But notice it did not automatically log me in. That's something we'll fix in a second. But hey, registration. It's a form. It's easy! It's done.

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9",
        "symfony/symfony": "3.1.*", // v3.1.4
        "doctrine/orm": "^2.5", // v2.7.2
        "doctrine/doctrine-bundle": "^1.6", // 1.6.4
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
        "symfony/swiftmailer-bundle": "^2.3", // v2.3.11
        "symfony/monolog-bundle": "^2.8", // 2.11.1
        "symfony/polyfill-apcu": "^1.0", // v1.2.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.4", // 1.4.2
        "doctrine/doctrine-migrations-bundle": "^1.1" // 1.1.1
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.0.7
        "symfony/phpunit-bridge": "^3.0", // v3.1.3
        "nelmio/alice": "^2.1", // 2.1.4
        "doctrine/doctrine-fixtures-bundle": "^2.3" // 2.3.0
    }
}