If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
Guess what! Server-side validation is really, really fun. Google for Symfony validation, and find the book chapter.
There is one weird thing about validation... which I love. Here it is: you don't
apply validation to your form. Nope, there will be no validation code inside of
GenusFormType
. Instead, you add validation to the class that is bound to your
form. When the form is submitted, it automatically reads those validation rules
and uses them.
Validation is added with annotations. So copy the use
statement from the code block,
find Genus
and paste it on top:
... lines 1 - 6 | |
use Symfony\Component\Validator\Constraints as Assert; | |
... lines 8 - 141 |
Good start! Next, we'll add validation rules - called constraints - above each property. On the left side bar, find the "Constraints" link.
Check out this menu of validation rules: NotBlank
, NotNull
, Email
, Length
,
Regex
... so many things! Pretty much anything you can dream up is inside of this
list.
Let's start with an easy one: above the name
property, add @Assert\NotBlank
:
... lines 1 - 12 | |
class Genus | |
{ | |
... lines 15 - 21 | |
/** | |
* @Assert\NotBlank() | |
... line 24 | |
*/ | |
private $name; | |
... lines 27 - 139 | |
} |
Without doing anything else, refresh. Boom! Validation error. And, it looks nice.
Let's add some more. For subFamily
- that should be required, so add @NotBlank
:
... lines 1 - 12 | |
class Genus | |
{ | |
... lines 15 - 27 | |
/** | |
* @Assert\NotBlank() | |
... lines 30 - 31 | |
*/ | |
private $subFamily; | |
... lines 34 - 143 | |
} |
For speciesCount
, add @NotBlank
again:
... lines 1 - 12 | |
class Genus | |
{ | |
... lines 15 - 34 | |
/** | |
* @Assert\NotBlank() | |
... lines 37 - 38 | |
*/ | |
private $speciesCount; | |
... lines 41 - 143 | |
} |
But in addition to that, we want speciesCount
to be a positive number: we don't want
some funny biologist entering negative 10.
On the constraints list, there's one called Range
. Check that out.
Ok cool: just like the form field types, you can pass options to the constraints.
The Range
constraint has several: min
, max
, minMessage
and maxMessage
.
Add @Assert\Range
with min=0
and minMessage="Negative species! Come on..."
:
... lines 1 - 12 | |
class Genus | |
{ | |
... lines 15 - 34 | |
/** | |
* @Assert\NotBlank() | |
* @Assert\Range(min=0, minMessage="Negative species! Come on...") | |
... line 38 | |
*/ | |
private $speciesCount; | |
... lines 41 - 143 | |
} |
Ok, let's finish up. It's ok if funFact
is empty - so don't add anything there.
The same is true for isPublished
: we could add a constraint to make sure this
is a boolean, but the sanity validation on the form already takes care of that.
Finally, let's make sure firstDiscoveredAt
is also NotBlank
:
... lines 1 - 12 | |
class Genus | |
{ | |
... lines 15 - 51 | |
/** | |
* @Assert\NotBlank() | |
... line 54 | |
*/ | |
private $firstDiscoveredAt; | |
... lines 57 - 143 | |
} |
Ok, refresh! Leave everything blank and put -10 for the number of species. I love it!
I hav e a phone number to check (10 all digits), I want to make the validation serverside after form submission, all I know now is thrown new \Exception('blabla'), which results in a red screen, is there a way to do this in a more pretty way? I google but couldn't find an info about that. Thanks
Hey Benoit L.
It's easy, you have 2 options:
1. Use the force, Luke the Regex validator here you can find documentation: https://symfony.com/doc/cur...
2. Create your own bicycle custom validator. We have a good chapter about it in Symfony4 forms course, here is the link https://symfonycasts.com/sc...
Cheers and feel free to ask more questions :)
Hello, is there a tutorial on how to debug validation, when it does not validate? I mean validator code is not executed? For example in my case there is NotBlank() constraint, and NotBlankValidator is not called and form is valid when it should not be. Could you give a checklist what to check?
Hey Coder,
Hey, unfortunately we don't have a separate tutorial for this. But you can use Symfony Web Debug Toolbar to debug validation, and we talk about it in a few screenasts from Symfony Forms tutorial I think. But most probably it's due to a developer error. If validation constraint does not work, probably the best option is to check any misprints in namespace or class name first, and clear the cache - it might be a problem sometimes.
Cheers!
The problem was with validation groups. So one thing is to comment out validation groups for debugging - if they cause the problem.
Hey Coder,
Thank you for sharing your solution with others! Yeah, good idea... you can simplify your validation constraint as much as you can and then complicate it step by step. So, this way you'll find a step when it stops working for your case. And you'll have some context that helps you to understand why it does not work.
Cheers!
Hello Ryan!
I've been struggling in finding a way to disable CSRF protection when doing functional testing, I just can't set to false csrf_protection option in config_test.yml because I'm using FOS_USER Bundle and it has a dependency on the service (program crashes if I do that)
Do you know any other way to bypass the protection ?
What I'm trying to do is a DELETE request from a client instance
$client->request("DELETE", $deletePath);
Thanks in advance :]
Hey Diego!
Hmm. So usually, CSRF protection in a function test isn't a problem, because you're actually pressing "submit" on a form, and since that form has a CSRF token embedded in it, it works. This is also the "best" way because you're really testing your true setup - not trying to fake anything.
In your case, what's causing you to need to send a DELETE request directly to a URL? I think we could dig and get you an answer to this - i.e. using Symfony to actually fetch/fake what the token *should* be, then send it with the request - but let's see if we need to go down that road first :).
Cheers!
Hi Ryan, thanks for your answer
I was trying to save me a step by just calling the request method, like if it were an API call, but I guess it would be better to use your approach and actually submit the delete form
Definitely :). It will technically make your tests a *little* bit slower... but you're also testing more (e.g. that the form points to the correct location and contains the token).
Cheers!
Heya,
I have problem with displaying validation messages on page , ie they didn't appear after i turn off html5 validation.
So, the field is not bordered red, and no messages are shown :)
What i figure it out from link below , is that i have to do it manually somehow, because there is some check if there are errors , and do some stuff afterwards.
https://symfony.com/doc/3.4...
What i want to achieve is bordering out fields if validation is false, like in tutorial.
Any idea how to resolve this?
Cheers !
Hey Eddy,
Are you add validation constraints to your form? If I understand you right, your problem is that field's border is not red when some validation errors occurred, right? If so, do you use Twitter Bootstrap on your site? Symfony has integration with both Bootstrap 3 and 4, see http://symfony.com/doc/curr... - you just need to activate a proper theme in your configuration and you'll have red borders out of the box for fields that contain not valid values. if you don't use TB - you need to implement it by yourself, for example, adding some CSS styles at least.
Cheers!
(hopefully) simple question... I see that validation doesn't go inside the Form Type class (here GenusFormType.php) but in the Entity class (here, Genus.php).
However how does Symfony know that this Entity should be used for the form?
$form = $this->createForm(GenusFormType::class);
This line creates the form object, but there are no explicit references to Genus.php inside GenusFormType.php. Further, in the buildForm method, how does Symfony know to auto-suggest the properties within Genus.php (for instance, when you type $form->add( '....) it will suggest ".name", ".speciesCount" etc. ?
Does it look at ALL Entities and suggest fields for all of them? Maybe I'm overthinking it ... :)
Thanks!
Hey Goz
Good question, but you missed a tiny detail, in your FormType class, there is an option to bound an entity to the form, check at "configureOptions()" method.
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => 'AppBundle\Entity\Genus'
]);
}
And that's how Symfony Form component knows about your entity :)
Cheers!
Does anyone know how to get the breadcrumb path to the current file to display in phpstorm? (This is the line like \AppBundle\Entity\Genus etc. that appears right above the line numbers, and below the list of open files)
Thanks!
Thanks but I actually want it to display all the time, as in the videos above.
I just found it - it's just in a slightly different place in my version of phpstorm. (Right click --> breadcrumbs --> Top ).
Cheers
Say I have a form field where sometimes it can be blank and other times it needs to *not* be blank. For instance, say an admin can set the field to blank but a regular user can't. That would be two different annotations on the same Entity property. How might I go about accomplishing this dual need for a particular Entity property validation?
Yo Terry!
Great question, and (fortunately) a simple answer! Check out validation groups: https://knpuniversity.com/s... - it should be exactly what you need :).
Cheers!
Thanks much! ...I started that tutorial but hadn't gotten that far yet :)
Also, just to follow up, that tutorial is using a Form Class. Is there a way to add Validation Groups with the simple form generation (below)? Or is this starting to get into one of the many reasons why one would need to switch to creating a Form Class?
$form = $this->createFormBuilder($task)
->add('task', TextType::class)
->add('dueDate', DateType::class)
->add('save', SubmitType::class, array('label' => 'Create Post'))
->getForm();
Hi Terry,
Yes, it's possible too: use validation_groups
option for it like in the next example:
$form = $this->createFormBuilder($task)
->add('task', TextType::class, [
'validation_groups' => ['YourValidationGroup', 'AnotherValidationGroup'],
])
->add('dueDate', DateType::class)
->add('save', SubmitType::class, array('label' => 'Create Post'))
->getForm();
Cheers!
Hey i really need help. My annotation validation not working. $form->isValid always return true.. What can be possibly wrong in my project?
Hi Deividas!
Oh no! Hmm, could you post your annotation code in your entity class? And also the code in your controller? Usually, this either works, or you do something wrong and you get an error - I can't think of very many ways that it would just *not* actually apply validation correctly. Also, you could experiment by adding the Callback constraint to your class (http://symfony.com/doc/curr... and then add a die() statement in your callback function to see if Symfony sees (and calls) this constraint.
Either way, I'm sure we'll figure it out :).
Cheers!
Wow so quick respone!
http://pastebin.com/U5D6Y0Ap Here are some parts of my code.
EDIT: If i've done some painfull fundumental mistakes please let me know! I'm new on Symfony. (Few days only)
Excellent! I don't see any fundamental mistakes, and you're actually doing something a little bit complex, with the embedded forms. So, congrats :). I think the problem is that the PersonalPersonal class is the "top-level" class that's being validated. Then, the PersonalMainInfo is "embedded" inside of it. The validation system doesn't automatically validate child objects like this - but if you add @Assert\Valid (http://symfony.com/doc/curr... above the personMainInfo property in PersonalPersonal, it should fix the problem.
Let me know if it works! Cheers!
OH MY GOD!! THANK YOU! Really appreciate your help! That was the problem.
One more time big thanks. :)
Hi!
I'm fairly new web developer and I have a few questions (that could sound boring/stupid to you) about symfony forms.
1. This one is about security; The way i see it, we collect data ($genus = $form->getData()) and when i want to store it in db we use
$em->persist($genus);
$em->flush();
and like you say "boom they are in db!";
My question is where do we escape data (xss, sql injection) and with the form_end we have generated token on but I don't see where we check if token is valid?
2. If i decide not to use symfony generated form and use regular html form, how would i handle security for data coming from the form.
I've already looked at some SO questions/answers but I'm not sure if they are correct.
Hey Filip
Great questions!
1) When using Symfony form component, you only have to specify your constraints (e.g. not blank) for your entity fields, and when you execute `$form->isValid()`, Symfony will take care of everything and return false if there was any constraint violation. And, about SQL injection, if you are using DQL (Doctrine Query Language) you have nothing to worry about
2) Well, for that case (that I don't recommend), you would have to clean and validate every input from before inserting into the database
I hope it helps you :)
Cheers!
The range assert on speciesCount is absolutely refusing to work. I even cut and paste your code to ensure there was no hidden type. Suggestions? http://imgur.com/a/Rs7Wy https://gist.github.com/ril...
Hey Richard
Your annotations look's good to me
Are you getting any error ? can you show it to me ?
Cheers!
Hmmm, interesting, for some reason the validation is not been applied. Try clearing the cache "bin/console cache:clear" in dev / test environment it doesn't matter but just to be 100% sure that is not the case.
Hello,
I want to ask a question about client (CS) and server validation (SS).
For example the "name" field must be not blank and unique, we should to the unique constraint with server side validation but for "not blank" there are the 2 ways, should we do them both or just one? As I read that client validation can be disabled so SS is required and we need CS to provide a better User Experience.
Hey mehdi!
Great question :). Let me say this:
1) You 100% need SS validation, because (as you correctly said), CS validation can always be disabled
2) CS validation then is simply for a better use experience.
3) There are two types of CS validation: the built-in HTML 5 validation and custom JavaScript validation. HTML 5 validation is very simple (e.g. just add a required
attribute to a field to make it required), but it is not very flexible (difficult/impossible to customize the message or how it looks). Custom JavaScript validation, where you use a JS library or write some JS code, takes more work, but you have full control over how it looks. Since CS validation is for a better use-experience, I usually do it with custom JS. But mostly, I don't do a lot of CS validation: we only do it on our most important forms.
Cheers!
Thank you for your time.
So, as a conclusion:
1. SS validation for all forms.
2. Disable HTML5 validation.
3. CS validation (Custom JS) for most important forms.
For custom JS is jQuery Validation Plugin a good solution ??
Does the validation happen before or after the setter? I.E. Lets say I have
...
/**
* @ORM\Column(type="integer", options={"comment":"Multiplied by 1000x"})
* @Assert\NotBlank()
* @Assert\GreaterThan(value = 0)
*/
private $integerValue;
...
private setIntegerValue($integerValue)
{
$this->integerValue = round($integerValue * 1000);
}
...
Would the above work for setting the value to 0.004, or would it error out? Would it error out on 0.000005?
Hey Matt,
Symfony validation applies to the object, i.e. after setter calls. It means you can use object with invalid data like any other object, calling getters on it and getting current values it holds. That's why when you render the same object which fails validation - you'll see the same submitted (invalid) data. So I suppose in your example passed 0.004 value will pass validation, but 0.000005 won't, because the result value 0.005 will be rounded to 0 and GreaterThan(value = 0) will fail.
UPD: But keep in mind, that <a href="http://php.net/manual/en/function.round.php">round()</a> function returns a float value, so you probably should also do type casting:$this->integerValue = (int)round($integerValue * 1000);
Cheers!
Hey Stan!
Ah, interesting! I don't know what caused that, but it's an easy fix: run this from the command line:
bin/console assets:install
This should have been done for you when you originally created the product with the installer, but it's no problem, this does it. This basically symlinks (or copies, depending on your operating system) some web/bundles/* directories into some core directories. This effectively exposes the core Symfony exception CSS file. Btw, in the next version of Symfony, even this isn't needed: the CSS for the exception pages is just inlined to keep things simple.
Let me know if that helped! And Cheers!
Love the series, however I have ran into an issue and I cannot get the errors to appear on the form for some reason. I have altered how isPublished is stored since I am using Oracle, for some reason the boolean was not working so I am using just a Y or N. The config.yml also has the correct validation setting: validation: { enable_annotations: true }
So yeah, I'm kind of at a loss now as StackOverflow and Google are helping much after two days of searching. You are my only hope!
GenusFormType: https://pastebin.com/tPZBibfE
Genus.php: https://pastebin.com/YfP9HMTY
GenusAdminController: https://pastebin.com/1KN4ZAZM
new.html.twig: https://pastebin.com/AhH6B7UA
I am also using FireFox and XAMPP for my local development, if you need more just let me know!
Yo Chris G!
Oh man! Lame! Well, let's see if we can help :). I think I see the problem: in GenusController::newAction(), after the big if( $form->isSubmitted() && $form->isValid() ){
block, you are re-creating the form object on line 65. This is a problem :). Basically, after you call $form->handleRequest($request)
, the validation errors are attached to the Form object. So when you re-create the form object on line 65, you're creating a fresh Form object... which has no validation errors attached to it. This is what's passed into your template. You've probably also noticed that your form is not staying "filled in" when you submit your form.
Let me know if that's the problem :). And if not, then, we'll keep debugging! But remove that line 65 for sure. Any special reason you added it?
Cheers!
Oh goodness... I figured it would be something obvious like that! Always nice to have another pair of eyes helping out!
Thanks for the series, good quality stuff!
Hi Ryan
My validation messages do not appear on the form - they are captured in the profiler but no sign of them on the form itself ??
Doh! - my mistake - my form field names were not the same as the names in my associated class - inconsistent uppercase/lowercase naming convention - must try harder ... back of the class.
That's ok! It's a good thing to do wrong once :). And you did the right thing by checking out the profiler. Since it couldn't find the correct field to assign to, it "bubbles" to the top level. If I remember correctly, I *think* it might still tell you the "path" of where the error is coming from in the profiler to help. But anyways, you got it - nice debugging!
// 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
}
}
such endless posibilities of usability with this kind of validation....
i18n should be usable with the minMessage="error_message_min_message_whatever", I'll check on the translation services later lol
I'm loving this.