Disable the HTML5 validation of all your Symfony forms with a feature flag

Published on 2019-06-29 • Modified on 2019-07-01

In this post, we will see how to implement a simple feature flag. This flag will help us to debug our forms and will allow to disable the HTML5 client side validation of all the forms of an application. Let's go! 😎

» Published in "A week of Symfony 653" (1-7 July 2019).

Configuration

  • PHP 8.3
  • Symfony 6.4.5

Introduction

When you are working on the backend validation of a form Type, it's easier to debug if the HTML5 validation is disabled. You can do this by adding to your form a novalidate attribute in your Twig template:

{{ form_start(form, {attr: {'novalidate': 'novalidate'}}) }}

A small trick here: You can declare an invalid attribute to restore the validation. It's easier than removing it entirely. But not very clean, as your form will have an invalid and useless attribute that will not be interpreted by the browser.

{{ form_start(form, {attr: {'novalidate-off': 'novalidate'}}) }}

You can also declare this attribute directly in your form type, in this case you can comment the line to activate or not the validation:

public function configureOptions(OptionsResolver $resolver): void
{
    $resolver->setDefaults([
        'attr' => [
            'novalidate' => 'novalidate', // comment me to reactivate the html5 validation!  🚥
        ]
    ]);
}

It works but it's a manual task and this "debugging code" could be forgotten. So let's see how we could automate this process, not only for a specific form but for all the forms of your application.

Creating the feature flag

So first, let's create the feature flag. Let make it very simple, it will be a boolean parameter in the service container.

# config/posts/29.yaml (imported in config/services.yaml)
parameters:
    html5_validation: '%env(bool:APP_HTML5_VALIDATION)%'

We need to bind this parameter so we can inject it in a service:

# config/services.yaml
services:
    _defaults:
        bind:
            $html5Validation: '%html5_validation%'

Now we can use this variable to inject it in a service. Let's create our type extension that will be responsible to enable or not the feature.

Creation of the new type extension

The goal here will be to add a global form type extension that will be used by all our FormType classes. Create the following file. Thanks to the Symfony auto-loading it will be automatically used by the application. Moreover, this service will be tagged as a form type extension as it implements the AbstractTypeExtension.

<?php

declare(strict_types=1);

// src/Form/Extension/NoValidateExtension.php

namespace App\Form\Extension;

use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;

class NoValidateExtension extends AbstractTypeExtension
{
    private bool $html5Validation;

    public function __construct(bool $html5Validation)
    {
        $this->html5Validation = $html5Validation;
    }

    /**
     * @param array<string,mixed> $options
     */
    public function buildView(FormView $view, FormInterface $form, array $options): void
    {
        $attr = !$this->html5Validation ? ['novalidate' => 'novalidate'] : [];
        $view->vars['attr'] = array_merge($view->vars['attr'], $attr);
    }

    public static function getExtendedTypes(): iterable
    {
        return [FormType::class];
    }
}

As you can see, we save the flag state and depending of its value we add the novalidate attribute to the form with the help of $view->vars['attr'] of the buildView method. The nominal value of the flag is true. When wanting to turn off the validation switch the flag to false. But do you see the problem? If you commit the value to false then your application in production will have all the client side validation disabled. Even if the website would work thanks to the backend validation, this is probably something you don't want to do.
To make this parameter dependent to the environment, we will move it in the .env file located at the root of the project. Thus, in production we will put this value to true (1) and won't touch it any-more. On the other hand, we will able to easily switch the value as we need. Add the following parameter in your .env file:

# .env and .env.dist
APP_HTML5_VALIDATION=true

In the services.yml file, let's modify the raw parameter value by the one we've just introduced:

# config/services.yaml
parameters:
    html5_validation: '%env(bool:APP_HTML5_VALIDATION)%'

You will notice here that we used the bool preprocessor in order to make the .env component to handle this variable as a boolean and not like a string.

@fabpot's quote: "An environment variable is a string by design".

Without this preprocessing, if we put false for the value, this one will be interpreted as true because the type hint of our extension constructor expects a boolean: __construct(bool $htm5Validation). Test to see! Or you can also put 0 or 1 without using the preprocessor.

Now we are sure this value will stay to true in production but we will able to easily modify it when developing.

That's it! I hope you like it. Check out the links below to have additional information related to the post. As always, feedback, likes and retweets are welcome. (see the box below) See you! COil. 😊

 The Symfony doc  More on Stackoverflow  More on the web

They gave feedback and helped me to fix errors and typos in this article; many thanks to ro0NL, jmsche.

  Work with me!


Call to action

Did you like this post? You can help me back in several ways: (use the Tweet on the right to comment or to contact me )

Thank you for reading! And see you soon on Strangebuzz! 😉

COil