Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Beautiful Form Validation

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.

Start your All-Access Pass
Buy just this tutorial for $12.00

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

The Giant List of Constraints

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.

Constraint Options

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!

Leave a comment!

57
Login or Register to join the conversation

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.

1 Reply

You're totally right - you would do exactly this for i18n :)

Reply
Default user avatar
Default user avatar Maksym Minenko | weaverryan | posted 4 years ago

I hope there'll be a tutorial on *that*. :)

Reply
Benoit L. Avatar
Benoit L. Avatar Benoit L. | posted 2 years ago

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

Reply

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

Reply
Lijana Z. Avatar
Lijana Z. Avatar Lijana Z. | posted 3 years ago

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?

Reply

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!

Reply
Lijana Z. Avatar

The problem was with validation groups. So one thing is to comment out validation groups for debugging - if they cause the problem.

Reply

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!

3 Reply

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 :]

Reply

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!

Reply

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

Reply

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!

Reply
Default user avatar

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 !

Reply

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!

Reply
Default user avatar

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

Reply
MolloKhan Avatar MolloKhan | SFCASTS | Goz | posted 4 years ago | edited

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!

Reply
Default user avatar
Default user avatar Goz | MolloKhan | posted 4 years ago | edited

BOOM! Doh - I knew that and totally missed it. Thanks MolloKhan !

Reply
Default user avatar

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!

Reply
MolloKhan Avatar MolloKhan | SFCASTS | Goz | posted 4 years ago | edited

Hey Goz

you can right click on the file tab, there should appear a "copy path" option

Cheers!

Reply
Default user avatar

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

Reply
Default user avatar
Default user avatar Terry Caliendo | posted 4 years ago

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?

Reply

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!

Reply
Default user avatar
Default user avatar Terry Caliendo | weaverryan | posted 4 years ago

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();

Reply

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!

Reply
Default user avatar
Default user avatar Deividas Pekunas | posted 4 years ago

Hey i really need help. My annotation validation not working. $form->isValid always return true.. What can be possibly wrong in my project?

Reply

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!

Reply
Default user avatar
Default user avatar Deividas Pekunas | weaverryan | posted 4 years ago

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)

Reply

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!

Reply
Default user avatar
Default user avatar Deividas Pekunas | weaverryan | posted 4 years ago

OH MY GOD!! THANK YOU! Really appreciate your help! That was the problem.

One more time big thanks. :)

Reply

Awesome! Good luck and keep killing it!

Reply

URL for Validation doc has changed. https://symfony.com/doc/cur...

In case anyone gets lost :)

Reply

Hey somecallmetim ,

Yes, Symfony docs were rewritten a lot. Thanks for sharing it ;)

Cheers!

Reply
Default user avatar

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.

Reply

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!

Reply
Richard Avatar
Richard Avatar Richard | posted 4 years ago

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...

Reply

Hey Richard

Your annotations look's good to me
Are you getting any error ? can you show it to me ?

Cheers!

Reply
Richard Avatar

No error. That I can see. http://imgur.com/a/84Vkl

Reply

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.

Reply
mehdi Avatar

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.

Reply

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!

1 Reply
mehdi Avatar

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 ??

Reply
GDIBass Avatar
GDIBass Avatar GDIBass | posted 4 years ago

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?

Reply

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 round() function returns a float value, so you probably should also do type casting:
$this->integerValue = (int)round($integerValue * 1000);

Cheers!

Reply

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!

Reply
Default user avatar
Default user avatar Chris G | posted 4 years ago

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!

Reply

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!

1 Reply
Default user avatar

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!

Reply
Default user avatar
Default user avatar jabbergabber | posted 4 years ago

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.

Reply

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!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// 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
    }
}