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.
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> | |
| |
<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.
Hi Ryan,
How do you get the "action" shortcut?
Thank you!