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.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeHead back over to /register
. We built this in our security tutorial. It does work... but we kind of cheated. Back in your editor, open src/Controller/SecurityController.php
and find the register()
method. Yep, it's pretty obvious: we did not use the form component. Instead, we manually read and handled the POST data. The template - templates/security/register.html.twig
- is just a hardcoded HTML form.
Ok, first: even if you use and love the Form component, you do not need to use it in every single situation. If you have a simple form and want to skip it, sure! You can totally do that. But... our registration form is missing one key thing that all forms should have: CSRF protection. When you use the Form component. you get CSRF protection for free! And, usually, that's enough of a reason for me to use it. But, you can add CSRF protection without the form system: check out our login from for an example.
make:form
Let's refactor our code to use the form system. Remember step 1? Create a form class... like we did with ArticleFormType
. That's pretty easy. But to be even lazier, we can generate it! Find your terminal and run:
php bin/console make:form
Call the class, UserRegistrationFormType
. This will ask if you want this form to be bound to a class. That's usually what we want, but it's optional. Bind our form to the User
class.
Nice! It created one new file. Find that and open it up!
// ... lines 1 - 9 | |
class UserRegistrationFormType extends AbstractType | |
{ | |
public function buildForm(FormBuilderInterface $builder, array $options) | |
{ | |
$builder | |
->add('email') | |
->add('roles') | |
->add('firstName') | |
->add('password') | |
->add('twitterUsername') | |
; | |
} | |
public function configureOptions(OptionsResolver $resolver) | |
{ | |
$resolver->setDefaults([ | |
'data_class' => User::class, | |
]); | |
} | |
} |
Customizing & Using UserRegistrationFormType
Cool. It set the data_class
to User
and even looked at the properties on that class and pre-filled the fields! Let's see: we don't want roles
or twitterUsername
for registration. And, firstName
is something that I won't include either - the current form has just these two fields: email
and password
.
// ... lines 1 - 11 | |
public function buildForm(FormBuilderInterface $builder, array $options) | |
{ | |
$builder | |
->add('email') | |
->add('password') | |
; | |
} | |
// ... lines 19 - 27 |
Ok: step 2: go the controller and create the form! And, yes! I get to remove a "TODO" in my code - that never happens! Use the normal $form = this->createForm()
and pass this UserRegistrationFormType::class
. But don't pass a second argument: we want the form to create a new User
object.
Then, add $form->handleRequest($request)
and, for the if, use $form->isSubmitted() && $form->isValid()
.
// ... lines 1 - 14 | |
class SecurityController extends AbstractController | |
{ | |
// ... lines 17 - 44 | |
public function register(Request $request, UserPasswordEncoderInterface $passwordEncoder, GuardAuthenticatorHandler $guardHandler, LoginFormAuthenticator $formAuthenticator) | |
{ | |
$form = $this->createForm(UserRegistrationFormType::class); | |
$form->handleRequest($request); | |
if ($form->isSubmitted() && $form->isValid()) { | |
// ... lines 51 - 72 | |
} | |
} |
Beautiful, boring, normal, code. And now that we're using the form system, instead of creating the User
object like chumps, say $user = $form->getData()
. I'll add some inline documentation so that PhpStorm knows what this variable is. Oh, and we don't need to set the email
directly anymore: the form will do that! And I'll remove my firstName
hack: we'll fix that in a minute.
// ... lines 1 - 44 | |
public function register(Request $request, UserPasswordEncoderInterface $passwordEncoder, GuardAuthenticatorHandler $guardHandler, LoginFormAuthenticator $formAuthenticator) | |
{ | |
// ... lines 47 - 49 | |
if ($form->isSubmitted() && $form->isValid()) { | |
/** @var User $user */ | |
$user = $form->getData(); | |
// ... lines 53 - 67 | |
} | |
// ... lines 69 - 72 | |
} | |
// ... lines 74 - 75 |
About the password: we do need to encode the password
. But now, the plain text password will be stored on $user->getPassword()
. Hmm. That is a little weird: the form system is setting the plaintext password on the password
field. And then, a moment later, we're encoding that and setting it back on that same property! We're going to change this in a few minutes - but, it should work.
// ... lines 1 - 49 | |
if ($form->isSubmitted() && $form->isValid()) { | |
// ... lines 51 - 52 | |
$user->setPassword($passwordEncoder->encodePassword( | |
$user, | |
$user->getPassword() | |
)); | |
// ... lines 57 - 67 | |
} | |
// ... lines 69 - 75 |
Down below when we render the template, pass a new registrationForm
variable set to $form->createView()
.
// ... lines 1 - 44 | |
public function register(Request $request, UserPasswordEncoderInterface $passwordEncoder, GuardAuthenticatorHandler $guardHandler, LoginFormAuthenticator $formAuthenticator) | |
{ | |
// ... lines 47 - 69 | |
return $this->render('security/register.html.twig', [ | |
'registrationForm' => $form->createView(), | |
]); | |
} | |
// ... lines 74 - 75 |
Awesome! Let's find that template and get to work. Remove the TODO - we're killing it - then comment out all the old markup: I want to keep it for reference. Render with {{ form_start(registrationForm) }}
, form_end(registrationForm)
and, in the middle, render all of the fields with form_widget(registrationForm)
. Oh, and we need a submit button. Steal that from the old code and move it here.
// ... lines 1 - 10 | |
{% block body %} | |
// ... lines 12 - 13 | |
<div class="col-sm-12"> | |
{{ form_start(registrationForm) }} | |
{{ form_widget(registrationForm) }} | |
<button class="btn btn-lg btn-primary btn-block" type="submit"> | |
Register | |
</button> | |
{{ form_end(registrationForm) }} | |
// ... lines 22 - 40 | |
</div> | |
// ... lines 42 - 43 | |
{% endblock %} |
Perfect! Let's go check this thing out! Refresh! Oh... wow... it looks terrible! Our old form code was using Bootstrap... but it was pretty customized. We will need to talk about how we can get back our good look.
Making firstName Optional
But, other than that... it seems to render fine! Before we test it, open your User
entity class. We originally made the firstName
field not nullable
. That's the default value for nullable
. So if you don't see nullable=true
, it means that the field is required in the database.
Now, I do want to allow users to register without their firstName
. No problem: set nullable=true
.
// ... lines 1 - 13 | |
class User implements UserInterface | |
{ | |
// ... lines 16 - 33 | |
/** | |
* @ORM\Column(type="string", length=255, nullable=true) | |
// ... line 36 | |
*/ | |
private $firstName; | |
// ... lines 39 - 245 | |
} |
Then, find your terminal and run:
php bin/console make:migration
Let's go check out that new file. Yep! No surprises: it just makes the column not required.
// ... lines 1 - 10 | |
final class Version20181018165320 extends AbstractMigration | |
{ | |
public function up(Schema $schema) : void | |
{ | |
// ... lines 15 - 17 | |
$this->addSql('ALTER TABLE user CHANGE first_name first_name VARCHAR(255) DEFAULT NULL'); | |
} | |
// ... lines 20 - 27 | |
} |
Move back over and run this with:
php bin/console doctrine:migrations:migrate
Excellent! Let's try to register! Register as geordi@theenterprise.org
, password, of course, engage
. Hit enter and... nice! We are even logged in as Geordi!
Next: we have a problem! We're temporarily storing the plaintext password on the password
field... which is a big no no! If something goes wrong, we might accidentally save the user's plaintext password to the database.
To fix that, we, for the first time, will add a field to our form that does not exist on our entity. An awesome feature called mapped
will let us do that.
When i'm updating the user enitity, when logged in (for example changhing user information)
I keep getting logged out and the changes won't persist.
How does this work? What do I have to do to edit the currenty logged in User without triggering the logout.