Salut tout le monde,

Je suis sur SF5 et en train d’essayer d’écrire un custom validator

J’aimerai utiliser par exemple l’entity Manager mais, il ne semble pas aimer le fait que j’y mette un constructeur avec des arguments et donc j’ai l’erreur suivante :

Too few arguments to function App\Validator\UniqueValidator::__construct(), 0 passed in /var/www/project/symfony/vendor/symfony/validator/ContainerConstraintValidatorFactory.php on line 52 and exactly 1 expected

J'ai regardé un peu ce que Grafikart avait fait pour le site mais à priori dans SF5, cette façon de faire ne semble plus fonctionner et je bloque totalement sur ce sujet. Il y a un cas similaire sur Stackoverflow mais il n'y aucune solution :/

Dans l'idée j'aurais pensé le déclarer dans service.yaml mais dans la doc officielle ils n'en parlent pas.
J'ai tenté aussi en me basant sur un validator existant mais ça ne m'a pas aidé.

je suis un peu largué sur ce point :(
Je vous remercie par avance

5 réponses


Selmac
Auteur
Réponse acceptée

C'est résolu, en fait, j'ai fait une erreur dans le namespace

Erreur : namespace App\Instrastructure\Validator;

Solution : namespace App\Infrastructure\Validator;

Merci quand même pour ton aide :)

Salut, je n'ai pas de code mais à prioris tu as un constructeut sans arguments à l'intérieur.

Pour crée un custom validator et pouvoir ajouter des contraintes supplémentaires, il faut :

  • créer un dossier Validator à la racine du dossier "src"
  • créer ta class "UserUsernameConstraintValidator.php"
    -créer ta class UserUsernameConstraint.php

1 -> src/Validator/UserUsernameConstraintValidator.php

<?php

namespace App\Validator;

use App\Entity\User;
use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;

class UserUsernameConstraintValidator extends ConstraintValidator
{

    /**
     * @var EntityManagerInterface
     */
    private $em;

    /**
     * UserEmailValidator constructor.
     * @param EntityManagerInterface $entityManager
     */
    public function __construct(EntityManagerInterface $entityManager)
    {
        $this->em = $entityManager;

    }

    /**
     * @param mixed $value
     * @param Constraint $constraint
     */
    public function validate($value, Constraint $constraint) : void
    {

        if(!$constraint instanceof UserUsernameConstraint){
            throw new UnexpectedTypeException($constraint,UserUsernameConstraint::class);
        }

        if(null === $value || '' === $value){
            return;
        }

        if($value != $constraint->value){
            /** @var UserRepository $repo */
            $repo = $this->em->getRepository(User::class);

            if($repo->findOneBy(['username'=>$value])){
                $this->context->buildViolation($constraint->errorMessage)->setParameter('username',$value)->addViolation();
            }
        }

    }

1 -> src/Validator/UserUsernameConstraint.php


<?php

namespace App\Validator;

use Symfony\Component\Validator\Constraint;

/**
 * @Annotation
 */
class UserUsernameConstraint extends Constraint
{
    public $message = 'erreur etccccccc';

    public function validatedBy()
    {
        return static::class.'Validator';

Et ensuite tu ajoute cette contrainte personnalisé dans ton entité

use App\Validator;

@Validator\UserUsernameConstraint; // sur ta propriété name par exemple en annotation

Voilà la documentation symfony, c'est bien expliqué, mais c'est vrai que la première fois des fois si ce n'est pas compris on a beaucoup de mal ^^
https://symfony.com/doc/current/validation/custom_constraint.html

J'espère que je répond à ta question, n'hésite pas ^^

Selmac
Auteur

Autant pour moi, j'ai oublié de l'insérer ^^

La clairement, je me suis basé sur le fonctionnement de Grafikart pour essayer voir si c'était pas ce que j'avais fait qui merdait

Je code actuellement en PHP8. Mais j'ai essayé avec ta méthode mais ça n'a rien changé

Unique.php

<?php

declare(strict_types=1);

namespace App\Instrastructure\Validator;

use Attribute;
use Symfony\Component\Validator\Constraint;

/**
 * @Annotations
 * @Target({"CLASS", "ANNOTATION"})
 */
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
class Unique extends Constraint
{
    /**
     * Description $message field
     *
     * @var string $message
     */
    public string $message = 'This value is already used.';
    /**
     * Description $entityClass field
     *
     * @var string|null $entityClass
     */
    public ?string $entityClass = null;
    /**
     * Description $field field
     *
     * @var string $field
     */
    public string $field = '';

    /**
     * Description getRequiredOptions function
     *
     * @return string[]
     */
    public function getRequiredOptions(): array
    {
        return ['field'];
    }

    /**
     * Description getTargets function
     *
     * @return array|string
     */
    public function getTargets(): array|string
    {
        return self::CLASS_CONSTRAINT;
    }

    /**
     * Description validatedBy function
     *
     * @return string
     */
    public function validatedBy(): string
    {
        return static::class . 'Validator';
    }
}

UniqueValidator.php

<?php

declare(strict_types=1);

namespace App\Instrastructure\Validator;

use App\Http\Admin\Data\CrudDataInterface;
use App\Infrastructure\Orm\AbstractRepository;
use Doctrine\ORM\NonUniqueResultException;
use Doctrine\Persistence\ManagerRegistry;
use RuntimeException;
use stdClass;
use Symfony\Component\PropertyAccess\PropertyAccessor;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;

class UniqueValidator extends ConstraintValidator
{
    /**
     * UniqueValidator constructor
     *
     * @param ManagerRegistry $registry
     */
    public function __construct(
        private ManagerRegistry $registry
    ){
    }

    /**
     * Description validate function
     *
     * @param mixed      $value
     * @param Constraint $constraint
     *
     * @return void
     * @throws NonUniqueResultException
     */
    public function validate(mixed $value, Constraint $constraint): void
    {
        if (null === $value) {
            return;
        }

        if (!$constraint instanceof Unique) {
            throw new RuntimeException(
                sprintf('%s ne peut pas valider des contraintes %s', self::class, get_class($constraint))
            );
        }

        if (!method_exists($value, 'getId')) {
            throw new RuntimeException(
                sprintf(
                    '%s ne peut pas être utilisé sur l\'objet %s car il ne possède pas de méthode getId()',
                    self::class,
                    get_class($value)
                )
            );
        }

        if ($constraint->em) {
            $em = $this->registry->getManager($constraint->em);

            if (!$em) {
                throw new ConstraintDefinitionException(sprintf('Object manager "%s" does not exist.', $constraint->em));
            }
        } else {
            $em = $this->registry->getManagerForClass(\get_class($value));

            if (!$em) {
                throw new ConstraintDefinitionException(sprintf('Unable to find the object manager associated with an entity of class "%s".', get_debug_type($entity)));
            }
        }

        /** @var PropertyAccessor $accessor */
        $accessor = new PropertyAccessor();
        /** @var class-string<stdClass> $entityClass */
        $entityClass = $constraint->entityClass;
        if ($value instanceof CrudDataInterface) {
            $entityClass = get_class($value->getEntity());
        }
        $value      = $accessor->getValue($value, $constraint->field);
        $repository = $em->getRepository($entityClass);
        if ($repository instanceof AbstractRepository) {
            $result = $repository->findOneByCaseInsensitive([$constraint->field => $value]);
        } else {
            $result = $repository->findOneBy([$constraint->field => $value]);
        }

        if (null !== $result && (!method_exists($result, 'getId') || $result->getId() !== $value->getId())) {
            $this->context->buildViolation($constraint->message)->setParameter('{{ value }}', $value)->atPath(
                $constraint->field
            )->addViolation();
        }
    }
}

Tu as une class Entité User ?
C'est là que tu dois ajouter use App\Instrastructure\Validator;
Puis @Validator\Unique

Selmac
Auteur

Je suis en train de tester et comprendre le fonctionnement du DTO donc ça donne ceci :


<?php

declare(strict_types=1);

namespace App\Domain\Profile\Dto;

use App\Domain\Auth\User;
use App\Instrastructure\Validator\Unique;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * Class ProfileDTO
 *
 * @package   App\Domain\Profile\Dto
 *
 * @Unique(entityClass="App\Domain\Auth\User", field="email") 
 * @Unique(entityClass="App\Domain\Auth\User", field="username")
 */
class ProfileDTO
{
    /**
     * Description $email field
     *
     * @var string $email
     *
     * @Assert\NotBlank()
     * @Assert\Length(min=5, max=100)
     * @Assert\Email()
     */
    public string $email;
    /**
     * Description $username field
     *
     * @var string|null $username
     *
     * @Assert\NotBlank(normalizer="trim")
     * @Assert\Length(min=3, max=40)
     */
    public ?string $username = '';
    /**
     * Description $user field
     *
     * @var User $user
     */
    public User $user;

    /**
     * ProfileDTO constructor
     *
     * @param User $user
     */
    public function __construct(User $user)
    {
        $this->email    = $user->getEmail();
        $this->username = $user->getUsername();
        $this->user     = $user;
    }

    /**
     * Description getId function
     *
     * @return int
     */
    public function getId(): int
    {
        return $this->user->getId() ?: 0;
    }

    /**
     * Description setUsername function
     *
     * @param string|null $username
     *
     * @return $this
     */
    public function setUsername(?string $username): self
    {
        $this->username = $username ?: '';

        return $this;
    }
}