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();
}
}
}