Skip to content

Instantly share code, notes, and snippets.

@BoShurik
Created April 3, 2019 19:26
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save BoShurik/df63d1f51bc01465e41e9c067bf2dac0 to your computer and use it in GitHub Desktop.
Save BoShurik/df63d1f51bc01465e41e9c067bf2dac0 to your computer and use it in GitHub Desktop.
UniqueModel
namespace App\Post\Model;
use App\Model\Validator\Constraints\UniqueModel;
/**
* @UniqueModel(class="App\Entity\Post", fields={"user": "user", "slug": "slug"})
*/
class PostModel
{
public $user;
public $slug;
public $content;
}
<?php
namespace App\Model\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* @Annotation
*/
class UniqueModel extends Constraint
{
public $class;
public $fields;
public $identifier;
public $message = 'This value is already used.';
/**
* {@inheritdoc}
*/
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
/**
* {@inheritdoc}
*/
public function getRequiredOptions()
{
return ['class', 'fields'];
}
}
<?php
namespace App\Model\Validator\Constraints;
use Doctrine\Common\Persistence\ManagerRegistry;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
class UniqueModelValidator extends ConstraintValidator
{
/**
* @var ManagerRegistry
*/
private $registry;
public function __construct(ManagerRegistry $registry)
{
$this->registry = $registry;
}
/**
* @inheritDoc
*/
public function validate($value, Constraint $constraint)
{
/** @var UniqueModel $constraint */
if (!is_array($constraint->fields)) {
throw new UnexpectedTypeException($constraint->fields, 'array');
}
if (0 === count($constraint->fields)) {
throw new ConstraintDefinitionException('At least one field has to be specified.');
}
$objectManager = $this->registry->getManagerForClass($constraint->class);
if (!$objectManager) {
throw new ConstraintDefinitionException(sprintf('Unable to find the object manager associated with an entity of class "%s".', $constraint->class));
}
/* @var \Doctrine\Common\Persistence\Mapping\ClassMetadata $class */
$class = $objectManager->getClassMetadata($constraint->class);
$criteria = [];
foreach ($constraint->fields as $fieldName) {
if (!$class->hasField($fieldName) && !$class->hasAssociation($fieldName)) {
throw new ConstraintDefinitionException(sprintf('The field "%s" is not mapped by Doctrine, so it cannot be validated for uniqueness.', $fieldName));
}
$criteria[$fieldName] = $value->$fieldName;
if (null !== $criteria[$fieldName] && $class->hasAssociation($fieldName)) {
/* Ensure the Proxy is initialized before using reflection to
* read its identifiers. This is necessary because the wrapped
* getter methods in the Proxy are being bypassed.
*/
$objectManager->initializeObject($criteria[$fieldName]);
}
}
$repository = $objectManager->getRepository($constraint->class);
$result = $repository->findBy($criteria);
if ($result instanceof \IteratorAggregate) {
$result = $result->getIterator();
}
/* If the result is a MongoCursor, it must be advanced to the first
* element. Rewinding should have no ill effect if $result is another
* iterator implementation.
*/
if ($result instanceof \Iterator) {
$result->rewind();
} elseif (is_array($result)) {
reset($result);
}
if (0 === count($result)) {
return;
}
$identifierField = $constraint->identifier;
if ($identifierField && $identifierValue = $value->$identifierField) {
$classMetadata = $objectManager->getClassMetadata($constraint->class);
$filterIdentifiers = array_filter(is_array($result) ? $result : iterator_to_array($result), function($value) use ($classMetadata, $identifierValue) {
$id = current($classMetadata->getIdentifierValues($value));
return $id !== $identifierValue;
});
if (0 === count($filterIdentifiers)) {
return;
}
$result = $filterIdentifiers;
}
foreach ($constraint->fields as $modelField => $objectField) {
$this->context->buildViolation($constraint->message)
->setCause($result)
->setInvalidValue(implode(', ', $criteria))
->atPath($modelField)
->addViolation()
;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment