Christophe Meneses - Développeur
  • Liste des articles
  • Contact
  • Linkedin
  • Github
  • Flux RSS
Tous les articles PHP / Symfony Comment créer une contrainte de validation personnalisée dans Symfony
PHP Symfony

Comment créer une contrainte de validation personnalisée dans Symfony

Publié le 28/12/2014. Dernière mise à jour le 11/08/2015.

De base, Symfony met à disposition un nombre important de contrainte. Cependant, il peut être utile de créer ses propres contraintes. Pour illustrer le propos, nous allons créer une contrainte sur la position des images présentes dans un article d’une page web. Dans un article, chaque image a une position et chaque position ne peut être occupée que par une seule image à la fois.

L’entité Image

Notre contrainte est une contrainte de classe qui va porter sur l’entité Image et en particulier l’attribut position. Je ne mets qu’une partie du code de l’entité :

[…]

class Image
{
    […]

    /**
     * @var integer
     *
     * @ORM\Column(name="position", type="smallint")
     */
    private $position;

    […]

    /**
     * Set position
     *
     * @param integer $position
     * @return Image
     */
    public function setPosition($position)
    {
        $this->position = $position;

        return $this;
    }

    /**
     * Get position
     *
     * @return integer
     */
    public function getPosition()
    {
        return $this->position;
    }

    […]

}

Création de la classe de contrainte

La classe va se nommer ImagePosition. Elle doit étendre la classe Constraint :

<?php

namespace Xxx\YyyBundle\Validator\Constraints;

use Symfony\Component\Validator\Constraint;

class ImagePosition extends Constraint
{
    public $message = 'La position "%position%" est déjà occupée par une autre image.';

    /**
     * @inheritdoc
     */
    public function validatedBy()
    {
        return 'validator.image_position';
    }

    /**
     * @inheritdoc
     */
    public function getTargets()
    {
        return self::CLASS_CONSTRAINT;
    }
}

2 méthodes sont surchargées :
- validatedBy() : cette méthode permet d’indiquer l’alias du service de validation qui va valider la contrainte. Ce service va être créée dans le chapitre suivant.
- getTargets() : cette méthode permet de définir s’il s’agit d’une contrainte qui porte uniquement sur un attribut de classe (PROPERTY_CONSTRAINT) ou bien sur la classe entière (CLASS_CONSTRAINT). Dans cet exemple, nous créons une contrainte de classe.

On stocke dans l’attribut de classe message le message d’erreur qui va apparaitre si une entité Image n’est pas conforme à notre contrainte.

Création du service de validation

Notre classe de validation va se nommer ImagePositionValidator. Elle doit étendre la classe ConstraintValidator :

<?php

namespace Xxx\YyyBundle\Validator\Constraints;

use Xxx\YyyBundle\Entity\Image;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

class ImagePositionValidator extends ConstraintValidator
{
    private $em;


    public function __construct(ObjectManager $em)
    {
        $this->em = $em;
    }

    /**
     * Vérifie si la position d'une image est valide.
     * Elle est valide si elle n'est pas déjà utilisée par une autre image du même article.
     *
     * @param Image $image Instance d'Image qui doit être validée
     * @param Constraint $constraint Instance de ImagePosition
     *
     * @api
     */
    public function validate($image, Constraint $constraint)
    {
        $positionExiste = $this->em->getRepository('XxxYyyBundle:Image')->isPositionExiste($image);

        if ($positionExiste) {
            $this->context->buildViolation($constraint->message, array('%position%' => $image->getPosition()))
                ->atPath('position')
                ->addViolation();
        }
    }
}

Comme vous pouvez le voir, vous pouvez injecter d’autre service de manière classique. Ici, on récupère le manager d'entité.

Au minimum, il faut implémenter la méthode validate(…) qui est responsable de la validation ou non de la contrainte. Comme nous sommes en train de créer une contrainte sur la classe Image, le premier argument de la méthode est donc une instance de la classe Image. Le deuxième argument est quant à lui, une instance de notre classe de contrainte.
Si la contrainte n’est pas respecté, on envoie une violation avec le message d’erreur contenu dans la classe de la contrainte grâce à la méthode buildViolation(...). La méthode atPath(...) permet d'indiquer l'attribut de la classe qui ne respecte pas la contrainte. Ici la validation se fait grâce à une requête sur la base de données : on vérifie que la position n’est pas déjà utilisée par une autre image du même article.

Une fois la classe créée, il faut la déclarer en tant que service :

<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

    <services>
		
        [...]
		
        <service id="xxx_yyy.validator.image_position" class="Xxx\YyyBundle\Validator\Constraints\ImagePositionValidator">
            <argument type="service" id="doctrine.orm.default_entity_manager" />
            <tag name="validator.constraint_validator" alias="validator.image_position" />
        </service>

       [...]
	   
    </services>

</container>

Il faut taguer le service en tant que validator.constraint_validator et donner le même alias que celui défini dans la méthode validatedBy() de notre classe de contrainte ImagePosition.

Utilisation de notre contrainte personnalisée

Il ne reste plus qu’à utiliser notre contrainte. Dans le fichier de validation Resources/config/validation.yml, il faut appliquer notre contrainte au niveau de la classe Image :

Xxx\YyyBundle\Entity\Image:
    constraints:
        - Xxx\YyyBundle\Validator\Constraints\ImagePosition: ~
    properties:
        position:
            - NotNull:
                message: "La position est obligatoire."

Dorénavant, si un utilisateur indique dans un formulaire de création d’image la même position qu’une image existante, le formulaire ne se validera pas.

Version

Cet article a été testé avec :

Symfony : 2.7
Début de l'article

Tous les articles PHP / Symfony Comment créer une contrainte de validation personnalisée dans Symfony

Liste des articles par catégorie

  1. Tous 124
  2. Apache2
  3. APC1
  4. Assetic2
  5. Bash2
  6. CentOS9
  7. Composer6
  8. CSS1
  9. Debian1
  10. Deployer1
  11. Design Pattern11
  12. Docker6
  13. Doctrine14
  14. Elasticsearch3
  15. Git6
  16. Google Charts1
  17. Hardware1
  18. Hébergement1
  19. JavaScript1
  20. jQuery4
  21. Kibana2
  22. Logstash1
  23. Machine Learning1
  24. MariaDB2
  25. Memcached2
  26. MySQL3
  27. Nginx2
  28. Panther3
  29. PHP59
  30. PHP_CodeSniffer1
  31. PHP-FPM2
  32. PhpMyAdmin1
  33. PhpStorm3
  34. PHPUnit6
  35. PostgreSQL2
  36. RabbitMQ2
  37. SQL1
  38. SVN4
  39. Sybase ASE1
  40. Symfony56
  41. Twig3
  42. Ubuntu14
  43. Vue.js2
  44. Vuex1
  45. Webpack Encore1
  46. Xdebug5

Derniers articles publiés

PHP
Différence entre les mots clés self et static dans le langage PHP

Symfony / Docker / Nginx
Migrer un projet Symfony du protocole HTTP 1.1 au protocole HTTP 2

Vue.js
Utiliser un filtre Vue.js directement dans le JavaScript

PHP / Symfony
Supprimer les messages de dépréciation de type : The "sensio_framework_extra.routing.loader.annot_*" service is deprecated since version 5.2

Symfony / RabbitMQ
Exemple d'utilisation du composant Messenger pour envoyer des emails en asynchrone avec RabbitMQ

PHP Symfony MariaDB HTML5 CSS3 JavaScript Bootstrap

© 2014 - 2019 réalisé par Christophe MENESES