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:

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:

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:

23 lines app/Resources/views/user/register.html.twig

... lines 1 - 2

{% block body %}

<divclass="container">

<divclass="row">

<divclass="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):

38 lines src/AppBundle/Controller/UserController.php

... lines 1 - 8

useSymfony\Component\HttpFoundation\Request;

classUserControllerextendsController

{

... lines 13 - 15

publicfunctionregisterAction(Request $request)

{

$form = $this->createForm(UserRegistrationForm::class);

$form->handleRequest($request);

... lines 21 - 35

}

}

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

38 lines src/AppBundle/Controller/UserController.php

... lines 1 - 10

classUserControllerextendsController

{

... lines 13 - 15

publicfunctionregisterAction(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():

38 lines src/AppBundle/Controller/UserController.php

... lines 1 - 4

useAppBundle\Entity\User;

... lines 6 - 10

classUserControllerextendsController

{

... lines 13 - 15

publicfunctionregisterAction(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():

38 lines src/AppBundle/Controller/UserController.php

... lines 1 - 10

classUserControllerextendsController

{

... lines 13 - 15

publicfunctionregisterAction(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():

38 lines src/AppBundle/Controller/UserController.php

... lines 1 - 10

classUserControllerextendsController

{

... lines 13 - 15

publicfunctionregisterAction(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:

38 lines src/AppBundle/Controller/UserController.php

... lines 1 - 10

classUserControllerextendsController

{

... lines 13 - 15

publicfunctionregisterAction(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:

38 lines src/AppBundle/Controller/UserController.php

... lines 1 - 10

classUserControllerextendsController

{

... lines 13 - 15

publicfunctionregisterAction(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!

2018-04-03Dan Abrey

Thanks for you reply, Diego. Ah, okay. Yes, that's exactly what I was pointing out - that it may be useful to note that the tip given in this tutorial doesn't apply to SF4.

In older Symfony's version you could call "$form->isValid()" without calling "isSubmitted()" and nothing would break, but in Symfony4 (probably since version 3.4?) it throws an exception. So, just don't forget to call both methods whenever you are working with a forms :)

2018-04-03Dan Abrey

Thanks for your reply Victor. I must be completely misunderstanding the note in this tutorial. It says "In the forms tutorial, we also added $form->isSubmitted() in the if statement, but you technically don't need that: isValid() checks that internally."

The note tells us to use

if ($form->isValid())

instead of

if ($form->isSubmitted() && $form->isValid())

.

As you say, isValid() does check internally if the form is submitted, but if it's not, it throws an exception, which is not what we want here.

2018-04-02Victor Bocharsky

Hey Dan,

Is it? Let's figure it out... You'll see this "Cannot check if an unsubmitted form is valid..." error only when the form is not submitted, see this inverted logic in if statement inside isValid(): https://github.com/symfony/... . In other words, if you call isValid() on the form which was not sent, i.e. when "isSubmitted = false" - you'll get that exception which means you should call isSubmitted() first before isValid() to avoid throwing that exception, right? Because we totally do not want to see that exception. So note is valid as for me :)

Cheers!

2018-03-31Dan Abrey

Hey Ryan, thanks for another excellent tutorial!

FYI, it looks like the note regarding not needing to call isSubmitted() before isValid() may be out of date when using Symfony 4. If one leaves out isSubmitted(), a LogicException is thrown with the message "Cannot check if an unsubmitted form is valid. Call Form::isSubmitted() before Form::isValid()".

2017-08-30Victor Bocharsky

Hey Mike,

1) Inside controllers, you can use shortcut "$this->getUser()" - that's a better way to get currently logged in user. And one line of code is not a code duplication ;)

2) That's actually up to you - do as you like most ;) There's no a strong reason doing so, except when you want to group those things separately.

Huh, interesting! From everything I'm looking at, this *should* work. Actually, the important part is the label => false inside first_options and second_options - the label => false on the top level does nothing (it's fine to keep it - just pointing out where things do and don't matter). Question: if you set 'label' => 'Foo', does the label show up as Foo?

You are totally right, calling **isValid()** directly is a bad practice, we will add a note about the deprecation.

Btw, nice name! Cheers.

2017-04-26Taco

"In the forms tutorial, we also added $form->isSubmitted() in the if statement, but you technically don't need that: isValid() checks that internally."I think this is no longer true ;)"Call Form::isValid() with an unsubmitted form is deprecated since version 3.2 and will throw an exception in 4.0. Use Form::isSubmitted() before Form::isValid() instead"

2017-03-31Diego Aguiar

Hey Javier!

Do you want to add many users at a time or only one ?If only one is the case, then you can embed User's form into Organization form, like this:

I have 2 entities: Organisation and User. I have a form for user registration and another form for organisation creation. I want to be able to create new users from the organisation creation form. I've tried with EntityType::class but it just pulls existing users and what I want is to add new users that don't existing in the database. How can I achieve this? thanks.

2017-03-21Simon Carr

I still use Codeigniter for the primary project i work on. I am starting to look at how i might rewrite that in Symfony. The problem is the 150 existing tables i have. it would take some time to re write them in Doctrine.

2017-03-21Victor Bocharsky

You're welcome!

Haha, I agree with you :) It's not so scary when you have a good resource for learning. Btw, Symfony has pretty good docs and most of them is also written by Ryan.

It's not obvious that it exists before someone tells you, once you know about it, it's another amazing feature of Symfony!

I can't believe I have stayed away from Symfony for so many years, I was scared that it would eat me alive, but in fact you get so much power for very little effort. I think the amazing teaching skills of weaverryan are a lot to do with how quickly I have fallen in love with the framework.

2017-03-20Victor Bocharsky

Hey Simon,

You can use different validation groups for it, check out this screencast: https://knpuniversity.com/s... . This should fix your problem. Or you can add NotBlank validation constraint to the form field not to the property of your entity. But validation groups is the right way here, I think.

Cheers!

2017-03-19Simon Carr

I have made my password field NotBlank in the entiy. How would you implement a edit profile form that does not include the password in the form and still allow the changes to the rest of the fields to be saved. I keep getting errors that password must not be empty even though the field is not included in the form.

2016-12-17Aditya Kothadiya

Yes, I didn't not initialize $roles as an empty array. I had below -

/** * @ORM\Column(type="json_array") */ private $roles;

I initialized it to an empty array and now the problem is solved! Thanks a ton for a prompt reply.

1) We add some code to getRoles() to guarantee that the array returned always contains ROLE_USER. But, that's not your problem - that has nothing to do with how the roles property is stored (source: https://knpuniversity.com/s....

2) Do you have private $roles = array();? The fact that it is trying to insert as NULL tells me that this property may not have been initialized to an empty array. Also, are you using the json_array type?

Let me know! I'm sure it's something small :).

Cheers!

2016-12-17Aditya Kothadiya

I'm getting "An exception occurred while executing 'INSERT INTO user (email, password, roles)" error after submitting the form as "Roles" value is Null. How do I set default role ROLE_USER?

2016-12-12Victor Bocharsky

Hey Roberto,

I'm glad you had found the bug quicker than I replied to you :)

Cheers!

2016-12-11Roberto Santana

Sorry, my fault, there was a typo in my buildForm method. :(

2016-12-11Roberto Santana

Hi!

I get this weird error when I try to load the Registration form page, any ideas?

If I recall correctly, the form submits itself to the same route ("/register") and then the isValid check is performed? If the user goes directly to /register, then isValid fails and the registration form is rendered?

2016-10-10Vlad

That's terrific, Victor! I haven't gotten to that tutorial yet, but I certainly will.73,

I had installed this, but looking into it this morning it looks like it was not enabled. Crazy!

2016-10-03Victor Bocharsky

Hey Jay,

Yea, it's a Symfony Plugin - you should install it first and most probably restart your PhpStorm after installation.

Oh, and btw, if you already have this template - you won't see this "Create template" option - it works only for non-existent templates.

Cheers!

2016-10-03Jay Drake

Hi Ryan (and other Aquanauts),

I noticed here you used a great shortcut with Option-Enter where you chose 'Create template'. On my PC I can use Alt-Enter to get the same menu, but the 'Create template' option isn't available. Is there a particular plugin that is being used for that handy little shortcut?

Thanks!

2016-09-23weaverryan

Hey Yang!

Ah, thanks! Always happy to see you going through things and asking good questions :).

1) You're right! Actually, the href and template are correct, but the language is wrong. It should say "Register" (this is actually what we do in the video). I've just updated the code block to show this. Thanks for pointing that out! (ref https://github.com/knpunive...

2) Glad you already found the solution about the repeated fields - good digging! About the labels, labels - whether you're dealing with a normal field or a repeated field - can *always* be modified in either the form class or the template. Which is better? Probably the template, because I consider this - along with other things like setting a "class" on your element - to be *presentation* logic. Also, on larger teams that have frontend devs, you will want to keep this stuff in your template so that frontend devs can find it without searching around for crazy PHP files :).

Cheers!

2016-09-22Yang Liu

Hi Ryan,

great tutorial like always, thx!

two things I like to know though:

1. just out of curiosity: shouldn't this < a href="{{ path('user_register') }}">I already have an account

be

< a href="{{ path('user_login') }}"> I already have an account

furthermore, shouldn't this be in the register.html.twig (not in login.html.twig like in the script)? would make more sense to me :-)

2. now when I type 2 different passwords in the 2 fields, it gives me an ugly error "This value is not valid." under the first "Password"-field. Is it possible to change the error to something like "your passwords are not the same!!" and show this under the "Repeat Password"-field?

thanks for your help... again...

edit: first read, then ask... solved the second issure by reading repeatedtype-documentationedit2: new question after reading the repeatedtype-documentation :-): you defined the 2 passwordfield labels in twig, it seems thats its although possible to do this in the form builder: