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.
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.
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.
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.
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.
Hey Vlad,
It's called Live Templates in PhpStorm and you can create your own templates! Here's a screencast about it:
https://knpuniversity.com/s...
Cheers!
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?
Hey Aditya Kothadiya!
Hmm, so, we do 2 things:
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/screencast/symfony-security/dynamic-roles).
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!
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.
Hey Marcin,
Thank you for your interest in SymfonyCasts tutorials! Yes, it's on our plan to release a new Security tutorial for Symfony 5 track. Not sure will it be the next Symfony5-related course or after one more course, but we will definitely release this some day. We also will try to cover reset password feature there, actually we have a new bundle that helps with resetting passwords, you can check it our here: https://github.com/SymfonyC... . Unfortunately, I don't have estimations for you yet when the new Symfony 5 security course might be released, sorry.
Btw, you're looking at Symfony3 course about security, we also have an updated one for Symfony 4 that is the latest available course about Symfony security. You can find it here: https://symfonycasts.com/sc... . Feel free to follow it on Symfony 5 project and if you will have any problems - aks them in comments below the video, we will definitely try to help you solving them!
Cheers!
Hey Abdelamine,
So, you're trying to render a form, and even if you have $email property on User entity you need to have a form field in your form you're rendering here. I mean, open the form class you that you are passing to form_row(), do you have email field there? I guess you don't. Otherwise, make sure you're passing the proper form to the form_row() function.
Cheers!
Hi Guys,
I've got a question. I want to NOT have a label for the password fields. I've tried the following (as shown in the docs):
->add('plainPassword', RepeatedType::class, [
'type' => PasswordType::class,
'options' => ['label' => false],
'first_options' => [
'label' => false,
'attr' => [
'placeholder' => 'Password'
]
],
'second_options' => [
'label' => false,
'attr' => [
'placeholder' => 'Repeat password'
]
]
]);
The placeholder show, but the labels do too! I don't want that..
Hey Dennis E.!
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?
Also, is it any different if you render the fields individually?
{{ form_row(form.plainPassword.first) }}
{{ form_row(form.plainPassword.second) }}
And finally, if you click into the form profiler, and find the plainPassword.first and plainPassword.second fields, under "View Variables", what is the value of the "label" variable for each?
Cheers!
Hi Ryan,
Silly me. In my twig template file I had the following:
{{ form_row(form.plainPassword.first, {
'label': 'Password'
}) }}
{{ form_row(form.plainPassword.second, {
'label': 'Repeat password'
}) }}
That's why the labels were being set. Sorry!
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!
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!
Thanks Victor,
I had installed this, but looking into it this morning it looks like it was not enabled. Crazy!
Hello,
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.
\src\AppBundle\Form\UserRegistrationForm:
<?php
namespace AppBundle\Form;
use AppBundle\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class UserRegistrationForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('firstName')
->add('lastName')
->add('email', EmailType::class)
->add('plainPassword', RepeatedType::class, [
'type' => PasswordType::class
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
'validation_groups' => ['Default', 'Registration']
]);
}
public function getBlockPrefix()
{
return 'UserRegistrationForm';
}
\src\AppBundle\Form\OrganisationCreationForm.php:
<?php
namespace AppBundle\Form;
use AppBundle\Entity\Organisation;
use AppBundle\Entity\User;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class OrganisationCreationForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('address')
->add('website')
->add('phoneNumber')
->add('admin', EntityType::class, [
'class' => 'AppBundle\Entity\User',
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Organisation::class,
]);
}
public function getBlockPrefix()
{
return 'OrganisationCreationForm';
}
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:
// \src\AppBundle\Form\OrganisationCreationForm.php:
public function buildForm(FormBuilderInterface $builder, array $options)
{
// other fields...
$builder->add('admin', UserRegistrationForm::class);
}
You can read more about embeding forms here:
http://symfony.com/doc/current/form/embedded.html
Have a nice day!
Hey Taco!
You are totally right, calling **isValid()** directly is a bad practice, we will add a note about the deprecation.
Btw, nice name! Cheers.
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?
Great video as always!
I'm feeling really dangerous and have to more dangerous questions! :D
1.) I don't want non anonymous user to access register/login. I have used your code from a previous video to check that and put it into the loginAction and registerAction:
`
// Check if user is logged in (not anonymous)
$token = $this->get('security.token_storage')
->getToken();
$user = $token->getUser();
if (is_object($user)) {
// They are not anonymous
return $this->redirectToRoute('homepage');
}
`
Is this best practice? Is it ok to have this code multiple times (in two controllers) or should it be into another file and called with a "one liner"?
2.) Why have you chosen to set the /login and /register Action in two different Controllers and not both actions in one, by example SecurityController?
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.
Cheers!
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.
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!
Thanks victor , Brilliant. I hadn't got that far in the series.
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 victor are a lot to do with how quickly I have fallen in love with the framework.
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.
I just wonder what framework did you use before Symfony?
Cheers!
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.
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()".
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!
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.
Exactly that Patrick
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 :)
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.
Hi Ryan, I am using the exact same code of the tutorial but get the error: "A form can only be submitted once" what could be the problem?
Hey Hank
Look's like something is calling "$form->handleRequest($request);" twice, I would need to check your controller's action code so I can help you debugging
Cheers!
Hi!
I get this weird error when I try to load the Registration form page, any ideas?
The option "0" does not exist. Defined options are: "action", "allow_extra_fields", "attr", "auto_initialize", "block_name", "by_reference", "compound", "constraints", "csrf_field_name", "csrf_message", "csrf_protection", "csrf_token_id", "csrf_token_manager", "data", "data_class", "disabled", "empty_data", "error_bubbling", "error_mapping", "extra_fields_message", "first_name", "first_options", "inherit_data", "invalid_message", "invalid_message_parameters", "label", "label_attr", "label_format", "mapped", "method", "options", "post_max_size_message", "property_path", "required", "second_name", "second_options", "translation_domain", "trim", "type", "upload_max_size_message", "validation_groups".
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-documentation
edit2: 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:
->add('plainPassword', RepeatedType::class, [
'type' => PasswordType::class,
'first_options' => ['label' =>'Password'],
'second_options' => ['label' => 'Repeat Password'],
'invalid_message' => 'The password fields must match',
'error_mapping' => [
'.' => 'second'
]
]);
so, what would be the "best practice" here?
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!
Regarding 2)
Do I see it right that the 'invalid_message' parameter *can't* be set in the twig template and *only* in the form? That would be presentation logic as well if I see it correctly.
Hey Mike P.
You are correct, that would be considered presentation logic aswell, but, it can be changed in a twig's template. You can override almost every value the form has, you can try something like this:
{{ form_row(form.password.first, {
'invalid_message': 'password custom error message'
}) }}
Only check that the keys passed to form_row matches to the ones from the Form object.
Cheers!
Thanks for that fast reply!
I can tell you, this doesn't work:
{{ form_row(form.plainPassword.first, {
'label': 'Password',
'invalid_message' : 'The password fields must match.'
}) }}
{{ form_row(form.plainPassword.second, {
'label': 'Repeat Password',
'invalid_message' : 'The password fields must matchs.' }) }}```
Label works, but error message is " This value is not valid.". If I type invalid_message into the ->add() method it works perfectly. Maybe its a quirk due to using the RepeatedClass?
->add('plainPassword', RepeatedType::class, [
'type' => PasswordType::class,
]);
Hey Mike P.
You are right, that doesn't work, sorry for miss leading you. Error messages comes from Constraints, like:
// User.php
/**
* @Assert\NotBlank(message="This field should not be blank")
*/
private $plainPassword;
You can change them right there or using a translation file
// 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
}
}
Hi Ryan,
How do you get the "action" shortcut?
Thank you!