Using the Symfony validation outside of the form context

Published on 2019-01-30 • Modified on 2019-01-30

It is sometimes useful to be able to use the Symfony validation services outside of the form context. In a controller for example or service when the data to validate do not come from a form.


<?php declare(strict_types=1);

namespace App\Controller\Snippet;

use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\Url;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\Validator\ValidatorInterface;

/**
 * I am using a PHP trait in order to isolate each snippet in a file.
 * This code should be called from a Symfony controller extending AbstractController (as of Symfony 4.2)
 * or Symfony\Bundle\FrameworkBundle\Controller\Controller (Symfony <= 4.1).
 * Services are injected in the main controller constructor.
 *
 * @property ValidatorInterface $validator
 */
trait Snippet14Trait
{
    /**
     * @see https://symfony.com/doc/current/reference/constraints/Url.html#relativeprotocol
     */
    public function snippet14(): void
    {
        $urls = [
            'toto/foo/notanURL', // NOK: Will violate both constraints (length = 17)
            '//example.com?foo=bar', // OK: valid with relative protocol URL
            'https://www.strangebuzz.com/en/snippets/using-the-symfony-validation-outside-of-the-form-context' // OK: Standard URL
        ];

        $constraints = [
            new Url(['relativeProtocol' => true,]), // Allow relative protocol
            new Length(['min' => 20, 'allowEmptyString' => false]), // Min len
        ];

        foreach ($urls as $url) {
            $violations = $this->validator->validate($url, $constraints);
            if ($violations->count()) {
                foreach ($violations as $violation) {
                    if ($violation instanceof ConstraintViolation) {
                        $message = $violation->getMessage();
                        echo sprintf('❌ The "%s" value is NOT valid: "%s"', $url, \is_string($message) ? $message : '').PHP_EOL;
                    }
                }
            } else {
                echo sprintf('✅ The "%s" value is valid.', $url).PHP_EOL;
            }
        }

        // That's it! 😁
    }
}

 Run this snippet  ≪ showUnitTestButtonLabel ≫  More on Stackoverflow

<?php declare(strict_types=1);

namespace App\Tests\Controller\Snippets;

use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\Url;
use Symfony\Component\Validator\Validator\ValidatorInterface;

/**
 * @covers Snippet14Trait
 */
final class Snippet14Test extends KernelTestCase
{
    /**
     * @var ValidatorInterface
     */
    private $validator;

    protected function setUp(): void
    {
        self::bootKernel();
        $this->validator = self::$container->get('validator');
    }

    public function provide(): array
    {
        return [
            ['toto/foo/notanURL', false, 2],
            ['//example.com?foo=bar', true, 0],
            ['https://www.strangebuzz.com/en/snippets/using-the-symfony-validation-outside-of-the-form-context', true, 0],
        ];
    }

    /**
     * @covers Snippet14Trait::snippet14
     *
     * @dataProvider provide
     */
    public function testSnippet14(string $url, bool $isValid, int $count): void
    {
        $constraints = [
            new Url(['relativeProtocol' => true,]),
            new Length(['min' => 20, 'allowEmptyString' => false]),
        ];

        $violations = $this->validator->validate($url, $constraints);
        $this->assertSame($isValid, $violations->count() === 0);
        $this->assertCount($count, $violations);
    }
}