Thanks to visit codestin.com
Credit goes to symfonycasts.com

This course is still being released! Check back later for more chapters.

Get Notified About this Course!

We will send you messages regarding this course only
and nothing else, we promise.
You can unsubscribe anytime by emailing us at:
[email protected]
Login to bookmark this video
Buy Access to Course
06.

Client-side vs Server-side Validation

|

Share this awesome video!

|

Keep on Learning!

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

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 only one asteroid away from total disaster. Therefore, let's get our form ready for any incoming asteroids.

We know that each StarshipPart should have at least a name and a price. Simple, right? But what happens when we accidentally (or on purpose?) submit an 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. First, not all browsers fully support it. Second, users can easily disable it. Also, as you can see in practice, it shows only one error at a time instead of all the errors at once, so users will press that button again and again until all the errors are gone. And... bots can easily bypass it, and we don't want bots messing with our starship parts database, right?

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, add the validate option set to false:

53 lines | src/Form/StarshipPartType.php
// ... lines 1 - 14
class StarshipPartType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
// ... lines 20 - 36
->add('createAndAddNew', SubmitType::class, [
'validate' => false,
// ... lines 39 - 41
])
;
}
// ... lines 45 - 51
}

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. You can try it yourself in Chrome's Inspector by editing the HTML code for the other button, save, then click it.

If I hit the submit button with that attribute on an empty form... 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 problem because we've just blindly tried to save invalid data to the database.

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. 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. The component comes with a lot of built-in validation constraints, which we can attach directly to our form fields.

Adding a Validation Constraint

Open our StarshipPartType class.

For the name field, pass null as the second argument which is the default value for it, and an empty array as the third. Inside, add a constraints key, and then add a new NotBlank constraint:

58 lines | src/Form/StarshipPartType.php
// ... lines 1 - 13
use Symfony\Component\Validator\Constraints\NotBlank;
class StarshipPartType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name', null, [
'constraints' => [
new NotBlank(),
],
])
// ... lines 26 - 47
;
}
// ... lines 50 - 56
}

Now, go back to the browser and try to submit the empty form again. Hmm... we still have the same database error, but look down at the web debug toolbar. There's a new section regarding validation.

Hover over this little icon, and you'll see "Validator calls: 1" and "Number of violations: 1". Next to the Validator icon, 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():

46 lines | src/Controller/AdminController.php
// ... lines 1 - 13
#[Route('/admin')]
class AdminController extends AbstractController
{
#[Route('/starship-part/new', name: 'app_admin_starship_part_new', methods: ['GET', 'POST'])]
public function newStarshipPart(
Request $request,
EntityManagerInterface $entityManager,
): Response {
// ... lines 22 - 23
if ($form->isSubmitted() && $form->isValid()) {
// ... lines 25 - 38
}
// ... lines 40 - 43
}
}

Refresh the page and resubmit the empty form. There you have it! No more 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... how about:

Every part should have a name!

58 lines | src/Form/StarshipPartType.php
// ... lines 1 - 15
class StarshipPartType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name', null, [
'constraints' => [
new NotBlank([], 'Every part should have a name!'),
],
])
// ... lines 26 - 47
;
}
// ... lines 50 - 56
}

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 the text-red-700 class and the invalid field has the border-red-700 class. 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 are we 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:

// ... lines 1 - 2
{% form_theme form 'tailwind_2_layout.html.twig' %}
// ... lines 4 - 18

Updating Tailwind CSS Configuration

Open tailwind_2_layout.html.twig and you'll see the CSS classes defined there. How can we ask Tailwind to 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 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:

10 lines | assets/styles/app.css
// ... lines 1 - 3
/* Tailwind CSS safelist */
@source "./../../vendor/symfony/twig-bridge/Resources/views/Form/tailwind_2_layout.html.twig";
// ... lines 6 - 10

We need to adjust the path by prefixing it with ./../../ in order to go start from the project root directory. And that's it.

Note that this requires Tailwind 4 and won't work in earlier versions. But we should be on the latest here. You can double-check the exact version in the config/packages/symfonycasts_tailwind.yaml config file - here it is, v4.1.11:

symfonycasts_tailwind:
# Specify the EXACT version of Tailwind CSS you want to use
binary_version: 'v4.1.11'
// ... lines 4 - 7

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:

@source cannot have a body.

Let's check app.css. Ah yes, hard to see, but I forgot to add the semicolon at the end:

10 lines | assets/styles/app.css
// ... lines 1 - 3
/* Tailwind CSS safelist */
@source "./../../vendor/symfony/twig-bridge/Resources/views/Form/tailwind_2_layout.html.twig";
// ... lines 6 - 10

At the terminal, run the build command again... and... success! No errors this time.

Go back to the browser... refresh... and there it is! The error message is now red, and so is the border around the associated field!

And that's it! We've learned how to add real, server-side, form validation in a Symfony project. In the next chapter, we will talk more about validation constraints. See you there!