Client-side vs Server-side Validation
Lucky you! You found an early release chapter - it will be fully polished and published shortly!
This Chapter isn't quite ready...
Rest assured, the gnomes are hard at work
completing this video!
It's time to talk about form validation. Because just like a starship without shields, a form without validation is impressive to look at... but is one asteroid away from a total disaster. Therefore, let's get our starship ready for any incoming asteroids.
We know that each part of our starship at least should have a name and
a price. Simple, right? But what happens when we accidentally (or someone
on purpose?) submit the empty form?
HTML5 Validation
Interestingly, the browser jumps in to save the day by showing a validation error. This is HTML5 validation at work. It's client-side validation that's handled completely by our browser. It's fast, user-friendly, and quite neat. However, it's not something we can fully rely on!
There are a few reasons for this. Firstly, not all browsers fully support it. Secondly, users can easily disable it. Also, as you can see on practice, it shows only one error at a time instead of all the errors at once, so user should press that button again and again until all the errors are gone. And fairly speaking, bots don't really care about it.
If HTML5 validation is skipped, our form gets submitted to the server. Let's first see what happens then.
Disabling HTML5 Validation
We can disable HTML5 validation in the Twig template, but let's try
another, more nerdy way. Open our StarshipPartType, and for the
button, let's add the validate option set to false.
Now, refresh the page and inspect the button in your browser's HTML inspector.
Aha, it added a special HTML attribute to the button:
formnovalidate="formNoValidate".
And this is how anyone can disable HTML5 validation on our website, or any website. Really, you can try to do the same in the Chrome's Inspector by editing the HTML code for the other button, save, and hit it.
If I hit the submit button with that attribute on an empty form... There you have it, a database error:
An exception occurred while executing a query: Integrity constraint violation: NOT NULL constraint field: starship_part.name
This error came straight from our server, and that's a big problem because we've just blindly tried to save an invalid data to the DB.
Server-side Validation with Symfony Validator Component
The good news is we can fix this with server-side validation. Symfony has a dedicated Validator Component for this purpose. And it works harmoniously with the Form Component. Let's install it first. I head back to the terminal and run:
symfony composer require validator
Let's start small. We want to see a validation error if the name field is
empty. Symfony's Validator Component comes with a lot of built-in
validation constraints, which we can attach directly to our form fields.
Adding a Validation Constraint
For the name field, I will pass null as the second argument which is the
default value for that arg, and an empty array as the third. Inside, let's
add a constraints key, and then add a new NotBlank constraint.
Now, go back to the browser and try to submit the empty form again. At first, you might think we still have the same database error, but if you look closely, there's a new section regarding validation in the WDT.
However over this little icon and you'll see "Validator calls: 1" and "Number of violations: 1". Next to Validator icon, it there's a Forms icon that shows us "Number of forms: 1" and "Number of errors: 1".
If you click on the Validator icon, you'll see our NotBlank error with
its default message:
This value should not be blank.
Handling Form Errors
So what's happening here? Even if the form is invalid, we're still trying
to save the object to the database, which is not what we want, right?
In the admin controller, we should not persist any data
if the form is invalid. Instead, we should just re-render the form with
errors so the user can fix them and resubmit the form. To do this, add
another condition in the if statement after we checked the form was
submitted: && $form->isValid.
Refresh the page and resubmit the empty form. There you have it! No more thrown exception, and the error is nicely rendered directly inside the form.
Customizing Error Messages
But can we customize the default message? Absolutely! Open the StarshipPartType,
and set the second argument of NotBlank to an empty string. How about:
Every part should have a name!
Back to the browser, submit the empty form again and there's our custom message. But wait, why isn't it red?
Troubleshooting Tailwind CSS Classes
If you open Chrome's HTML Inspector and look at the rendered error,
you'll see that the error text has text-red-700 and the invalid field
has border-red-700 CSS classes. These classes come from the built-in
Tailwind CSS form theme we applied earlier in this course, and those are
a valid Tailwind CSS classes, why we're missing styles on them? Don't we
have the SymfonyCasts/tailwind-bundle installed?
Here's the catch. These CSS classes are added dynamically and Tailwind didn't pick them up during compilation because they live in a vendor file which Tailwind ignores by default.
Updating Tailwind CSS Configuration
Open tailwind_2_layout.html.twig and you'll see the CSS classes defined
there. How can we ask Tailwind watch this external file as well while it's
compiling our CSS?
Since we're on Tailwind 4, there's no tailwind.config.js anymore.
Instead, open app.css file in the assets/styles/ directory. After
@import and @plugin, add the @source. I will go copy the long path
to the tailwind_2_layout.html.twig template and paste it here.
We need to adjust the path by adding ./../../ to it in order to
go to the project root dir. And that's it.
Note that this requires Tailwind 4.1 and won't work in earlier versions.
But we should be on the latest 4.1 here. You can double-check the exact
version in config/packages/symfonycasts_tailwind.yaml config file - here it
is, v4.1.11.
Ok, time to try it - refresh the browser aaaand... the text is still not red? Hm, I copy/pasted the path so it should be the correct. Probably due to the browser's cache? Let's try "Empty Cache and Hard Reload" first - still nothing changed.
OK, put on your debug hat and go to your terminal. We can manually run:
symfony console tailwind:build
In order to see if it can build our final CSS successfully. Aha, there is an error, so go check your CSS file. Ah yes, hard to see, but I forgot to add the semicolon at the end. Now refresh the browser, and you'll see your error message in red, also with the read border around the related field, and a few more extra polish touches.
And that's it! We've learned how to add real, server-side, form validation in Symfony project. In the next chapter, we will talk more about validation constraints. See you there!