Buy Access to Course
18.

Registration Form

Share this awesome video!

|

Keep on Learning!

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.