Validation de la présence et du type des clés d'un tableau avec Symfony

Publié le 07/08/2021 • Mis à jour le 07/08/2021


English language detected! 🇬🇧

  We noticed that your browser is using English. Do you want to read this post in this language?

Read the english version 🇬🇧 Close

Dans ce bout de code, nous voyons comment valider la présence et le type des clés d'un tableau avec le composant de validation Symfony. C'est assez direct à faire grâce aux contraintes collection et type. La documentation Symfony (lien en dessous) nous indique qu'on peut caster la variable $violations en tant que chaine. Cela fonctionne, mais PHPStan, à partir du niveau deux, lève une erreur. C'est pourquoi l'utilise une fonction fléchée pour extraire et afficher les erreurs.


<?php

declare(strict_types=1);

// src/Controller/Snippet/Snippet158Trait.php

namespace App\Controller\Snippet;

use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\Validation;

/**
 * J'utilise un trait PHP afin d'isoler chaque snippet dans un fichier.
 * Ce code doit être apellé d'un contrôleur Symfony étendant AbstractController (depuis Symfony 4.2)
 * ou Symfony\Bundle\FrameworkBundle\Controller\Controller (Symfony <= 4.1).
 * Les services sont injectés dans le constructeur du contrôleur principal.
 */
trait Snippet158Trait
{
    public function snippet158(): void
    {
        $validator = Validation::createValidator();
        $inputs = [
            [ // valid
                'bar' => 'string', // string
                'babar' => true,   // bool
                'john' => 555,     // int
                'fab' => ['4545'], // array
            ],
            [ // NOT valid
                'bar' => false,           // wrong type
                'babar' => 'true',        // wrong type
                'john' => 0.1,            // wrong type
                'fab' => new \stdClass(), // wrong type
                'extra' => null,          // extra field
            ],
        ];

        $constraints = new Assert\Collection([
            'bar' => new Assert\Type('string'),
            'babar' => new Assert\Type('bool'),
            'john' => new Assert\Type('int'),
            'fab' => new Assert\Type('array'),
        ]);

        foreach ($inputs as $input) {
            $violations = $validator->validate($input, $constraints);
            if (!$violations instanceof ConstraintViolationList) {
                throw new \RuntimeException('Invalid violations type.'); // this is also required by PHPStan to avoid a warning
            }
            if ($violations->count() > 0) {
                // echo $violations; // or this if ignoring the PHPStan warning! 😛
                $errors = array_map(static fn (ConstraintViolation $violation) => $violation->getPropertyPath().': '.$violation->getMessage(), iterator_to_array($violations->getIterator(), false));
                echo sprintf("❌ needed keys of the array\n\%s\nare NOT correct:\n%s", var_export($input, true), implode("\n", $errors)).PHP_EOL;
            } else {
                echo sprintf("✅ All needed keys of the array:\n%s\nare present and of the good type.\n", var_export($input, true)).PHP_EOL;
            }
        }

        // That's it! 😁
    }
}

 Exécuter le snippet  ≪ this.showUnitTest ? this.trans.hide_unit_test : this.trans.show_unit_test ≫  Plus sur Stackoverflow   Lire la doc  Plus sur le web

<?php

declare(strict_types=1);

namespace App\Tests\Controller\Snippets;

use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Validation;

/**
 * @covers Snippet158Trait
 */
final class Snippet158Test extends KernelTestCase
{
    /**
     * @return iterable<int, array>
     */
    public function provide(): iterable
    {
        yield [
            '✅ valid case' => [
                'bar' => 'string',
                'babar' => true,
                'john' => 555,
                'fab' => ['4545'],
            ],
            true,
            0,
        ];

        yield [
            '❌ invalid case' => [
                'bar' => false,
                'babar' => 'true',
                'john' => 0.1,
                'fab' => new \stdClass(),
                'extra' => null,
            ],
            false,
            5,
        ];
    }

    /**
     * @covers Snippet158Trait::snippet158
     *
     * @param array<int, mixed> $array
     *
     * @dataProvider provide
     */
    public function testSnippet158(array $array, bool $isValid, int $errorsCount): void
    {
        $validator = Validation::createValidator();
        $constraints = new Assert\Collection([
            'bar' => new Assert\Type('string'),
            'babar' => new Assert\Type('bool'),
            'john' => new Assert\Type('int'),
            'fab' => new Assert\Type('array'),
        ]);

        $violations = $validator->validate($array, $constraints);
        self::assertSame($isValid, $violations->count() === 0);
        self::assertCount($errorsCount, $violations);
    }
}