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 SubscribeDoes our form have any validation yet? Well... sort of? The form is going through a validation process. When we POST to this endpoint, handleRequest()
reads the data and executes Symfony's validation system. If validation fails, then $form->isValid()
returns false and we immediately render the template, except that now errors will be displayed by each field with an error.
Of course we haven't seen this yet... because we haven't added any validation rules!
But, check this out: leave the form completely blank and try to submit. It stops us! Wait... who... stopped us? Actually, it was the browser. Many of you may recognize this: it's HTML5 validation.
When Symfony renders a field, depending on our config, it often adds a required="required"
attribute. This isn't real validation - there's nothing on our server that's checking to make sure this value isn't blank. It's just nice client-side validation. HTML5 is cool... but limited. There are a few other things it can validate. Like, a datetime-local
field will require you to enter a valid date. Or, an <input type="number">
will require a number. But, not much more.
To control whether or not you want that required
attribute, every field type has an option called required
: just set it to true
or false
. Actually, this option is kinda confusing. It defaults to true for every field... no matter what... which can be kind of annoying & surprising. However, when you bind your form to an entity class, the form field guessing system uses the nullable
Doctrine option to choose the correct required
option value for you. In fact, if we look at the textarea field... yep! This has no required
attribute. Oh, by the way, all those extra attributes are coming from a browser plugin I have installed - not the form system.
To make things a bit more confusing, the required
option is only "guessed" from your Doctrine config if you omit or pass null
to the second argument of add()
. If you specify the type manually, the form type guessing system does nothing and you'll need to configure the required
option manually. Honestly, the required
option is kind of a pain in the butt. Be careful to make sure that an optional field doesn't accidentally have this attribute.
Anyways, even if you use HTML5 validation, you will still need proper server-side validation so that a "bad" user can't just disable that validation and send weird data. To do that, well, first we need to install the validator!
Find your terminal and run:
composer require validator
Validation is a separate component in Symfony, which is cool because it means that you can use it independent of the form system.
And... done! There are actually two types of server-side validation: what I call "sanity validation" versus "business rules validation".
Let's talk about sanity validation first. Sanity validation is built into the form fields themselves and makes sure that the submitted value isn't completely... insane! For text fields like title
and content
, there is no sanity validation: we can submit anything to those fields and it basically makes sense: it's a string. But the EntityType
does have built-in sanity validation.
Check this out: inspect element in your browser and find the select field. Let's change one of these values to be something that's not in the database, like value=100
.
Select this user and hit Create. Oh, duh! The HTML5 validation on the other fields stops us. To work around this, find the form class and add a novalidate
attribute: that tells the browser to get a hobby and skip HTML5 validation. It's a nice trick when you're testing your server-side validation. Hit Create again.
Yay! Our first, real validation error!
This value is not valid
This error comes from the "sanity" validation that's built into EntityType
: if you try to submit a value that should not be in the drop-down, boom! You get an error. Sanity validation is great: it saves us, and... we don't need to think about it! It just works.
To control the message, pass an option called invalid_message
. Set it to:
Symfony is too smart for your hacking!
... lines 1 - 14 | |
class ArticleFormType extends AbstractType | |
{ | |
... lines 17 - 23 | |
public function buildForm(FormBuilderInterface $builder, array $options) | |
{ | |
$builder | |
... lines 27 - 33 | |
->add('author', EntityType::class, [ | |
... lines 35 - 40 | |
'invalid_message' => 'Symfony is too smart for your hacking!' | |
]) | |
... line 43 | |
} | |
... lines 45 - 51 | |
} |
Move back and refresh to re-POST the form. Nice! I don't usually set the invalid_message
, only because, for most fields, you only see these errors if you're doing something really weird - like messing with the HTML.
We've talked about HTML5 validation and learned about sanity validation. Next, let's get to the good stuff: the real validation that we need to add.
Hey denizgelion!
Excellent question! So here's what happened / how this works in Symfony:
A) At some point, Symfony decided to make the getUser() method "final" (so that you can't extend it). Symfony does this kind of thing to keep the "hook points" in Symfony intentional. Basically, they're telling us "Hey! If you need to customize the getUser() method in some way, you should do it some other way than extending it". But instead of just adding the final
keyword on the function (which would break our code), they add this warning. This warns you that someday they may add the final
keyword (which would break our code) so you should stop using it.
B) In our case, we did it just so we could add the @return User
with our User class so that PHPStorm would be happy. So then, how can we do this without overriding getUser()
? You can do this by removing our custom getUser() method entirely in BaseController, and instead doing this:
// ....
use App\Entity\User;
// ...
/**
* @method User getUser()
*/
class BaseController extends AbstractController
{
}
That will have the same effect, but no deprecation. By the way, about this:
And why "without further notice"? Are these changes not going to be listed in a patch list of some sort
That's not really true :). If Symfony decided to add the final
keyword, it would only ever do it on a major release - like Symfony 5.0.0, 6.0.0, etc. It would ever be done, for example, between 4.4.5 and 4.4.6 (a patch release or a minor release).
Cheers!
Thanks a bunch for the quick reply, I appreciate it alot! Keep up the good work, I've seen plenty tutorials in my life, and eventough you keep using those corny jokes, I can't help myself but love it.
Hey Eugem!
If you're using the form system for the login form, then the "sanity validation" (the validation that's automatically built-in to the form field types [just repeating that to help others]) should happen automatically. Well, what I mean is, in your login authenticator, just make sure to handle your form like normal - $form->handleRequest()
. That will run normal validation and sanity validation. If any sanity validation fails, $form->isValid()
will return false. You shouldn't need to do anything special to get sanity validation.
Let me know if that helps! And sorry for the slow reply - we were missing notifications from our comment system for a few days!
Cheers!
$form->isValid() will return false. And after that the form got rendered with with errors 'This is not valid email'. And all of this happen at LoginFormAuthenticator object inside getCredentials() method. After that SecurityController creates new form and previous 'rendered with with errors form' is lost. How can I got this errors from login form? Errors like This is not valid email' or 'password is too short'
Hey Eugem
Since the login action is a bit different from any other form submit, you have to fetch the errors by using a special Symfony service AuthenticationUtils
. You can watch how Ryan does it by following this chapter: https://symfonycasts.com/screencast/symfony-security/auth-errors
Cheers!
the `invalid_message` option does only exist if the symfony/validator is installed, which at this point isn't :)
Hey Kim W.!
You're right! But we install the symfony/validator in the middle of this chapter - so it should be available after that :). But, you do bring up a good point. Until about a week ago, "sanity validation" wasn't applied at *all* unless symfony/validator was installed. But, thanks to this fix - https://github.com/symfony/... - any newer versions will always apply sanity validation... but you can't control the message with this option unless symfony/validator is installed.
Cheers!
// composer.json
{
"require": {
"php": "^7.1.3",
"ext-iconv": "*",
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"knplabs/knp-markdown-bundle": "^1.7", // 1.7.0
"knplabs/knp-paginator-bundle": "^2.7", // v2.8.0
"knplabs/knp-time-bundle": "^1.8", // 1.8.0
"nexylan/slack-bundle": "^2.0,<2.2.0", // v2.0.0
"php-http/guzzle6-adapter": "^1.1", // v1.1.1
"sensio/framework-extra-bundle": "^5.1", // v5.2.1
"stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
"symfony/asset": "^4.0", // v4.1.6
"symfony/console": "^4.0", // v4.1.6
"symfony/flex": "^1.0", // v1.17.6
"symfony/form": "^4.0", // v4.1.6
"symfony/framework-bundle": "^4.0", // v4.1.6
"symfony/orm-pack": "^1.0", // v1.0.6
"symfony/security-bundle": "^4.0", // v4.1.6
"symfony/serializer-pack": "^1.0", // v1.0.1
"symfony/twig-bundle": "^4.0", // v4.1.6
"symfony/validator": "^4.0", // v4.1.6
"symfony/web-server-bundle": "^4.0", // v4.1.6
"symfony/yaml": "^4.0", // v4.1.6
"twig/extensions": "^1.5" // v1.5.2
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.0", // 3.0.2
"easycorp/easy-log-handler": "^1.0.2", // v1.0.7
"fzaninotto/faker": "^1.7", // v1.8.0
"symfony/debug-bundle": "^3.3|^4.0", // v4.1.6
"symfony/dotenv": "^4.0", // v4.1.6
"symfony/maker-bundle": "^1.0", // v1.8.0
"symfony/monolog-bundle": "^3.0", // v3.3.0
"symfony/phpunit-bridge": "^3.3|^4.0", // v4.1.6
"symfony/profiler-pack": "^1.0", // v1.0.3
"symfony/var-dumper": "^3.3|^4.0" // v4.1.6
}
}
Today this deprecation notice popped up for me:
"The "Symfony\Bundle\FrameworkBundle\Controller\AbstractController::getUser()" method is considered final. It may change without further notice as of its next major version. You should not extend it from "App\Controller\BaseController"."
What should we exactly do / change now? Didn't *we* create the BaseController? What does "considered final" mean? Is "getUser()" going to be changed? And why "without further notice"? Are these changes not going to be listed in a patch list of some sort? Thanks!