Skip to content

Instantly share code, notes, and snippets.

@escees
Last active October 12, 2022 06:16
Show Gist options
  • Save escees/1fe94a6ba9e14db7e984fc2fab114a65 to your computer and use it in GitHub Desktop.
Save escees/1fe94a6ba9e14db7e984fc2fab114a65 to your computer and use it in GitHub Desktop.
UniqueDto Symfony constraint against entity passed as an argument.

UniqueDto Symfony constraint against entity passed as an argument.

UniqueEntity constraint is not able to validate field between associated DTO and entity and this is simple constraint that will check it uniqueness of the same field in DTO and Entity

This constraint is an easy way to implement validation against an entity for your DTO. You just need to remember that your fields in DTO should be named exactly the same as in the entity you want to validate against. Of course it could be more sophisticated like using map for field names in DTO and entity but for now it's enough.

class UniqueDTO extends Constraint
{
    public ?string $atPath = null;
    public string $entityClass;
    public string|array $fields;
    public string $message = 'error.duplicate';

    public function getTargets(): string
    {
        return parent::CLASS_CONSTRAINT;
    }
}
class UniqueDTOValidator extends ConstraintValidator
{
    private EntityManagerInterface $em;
    private PropertyAccessorInterface $accessor;

    public function __construct(EntityManagerInterface $em, PropertyAccessorInterface $accessor)
    {
        $this->em = $em;
        $this->accessor = $accessor;
    }

    public function validate($value, Constraint $constraint): void
    {
        if (!$constraint instanceof UniqueDTO) {
            throw new UnexpectedTypeException($constraint, UniqueDTO::class);
        }

        if(!$constraint->entityClass) {
            throw new InvalidArgumentException('Entity class is required.');
        }

        $repository = $this->em->getRepository($constraint->entityClass);

        $fields = (array) $constraint->fields;
        $criteria = [];

        foreach ($fields as $from => $to) {
            $criteria[$to] = $this->accessor->getValue($value, $from);
        }

        if ($repository->count($criteria)) {
            $cvb = $this->context->buildViolation($constraint->message);

            if ($constraint->atPath) {
                $cvb->atPath($constraint->atPath);
            }

            $cvb->addViolation();
        }
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment