Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -664,23 +664,6 @@ public function testValidateInheritanceUniqueness()
->assertRaised();
}

/**
* @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException
* @expectedExceptionMessage The "Symfony\Bridge\Doctrine\Tests\Fixtures\SingleStringIdEntity" entity repository does not support the "Symfony\Bridge\Doctrine\Tests\Fixtures\Person" entity. The entity should be an instance of or extend "Symfony\Bridge\Doctrine\Tests\Fixtures\SingleStringIdEntity".
*/
public function testInvalidateRepositoryForInheritance()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see my other comment: I think there was a reason for this: #15002

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should add new tests for sure.

{
$constraint = new UniqueEntity(array(
'message' => 'myMessage',
'fields' => array('name'),
'em' => self::EM_NAME,
'entityClass' => 'Symfony\Bridge\Doctrine\Tests\Fixtures\SingleStringIdEntity',
));

$entity = new Person(1, 'Foo');
$this->validator->validate($entity, $constraint);
}

public function testValidateUniquenessWithCompositeObjectNoToStringIdEntity()
{
$constraint = new UniqueEntity(array(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class UniqueEntity extends Constraint
public $em = null;
public $entityClass = null;
public $repositoryMethod = 'findBy';
public $isEntityToUpdateMethod = null;
Copy link
Member

@yceruto yceruto Nov 15, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is not just about update event, on create it is called too. Maybe isEqualToEntityMethod? Basically it is what the validator asks to throw the violation or not.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DTO can't be equal to an entity

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would make no sense to call it if an entity is created: if an entity exists in database with fields of same value it cannot be the one which comes from the DTO as it isn't in the database yet.

Copy link
Member

@yceruto yceruto Nov 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we are creating a new entity through a DTO, we also need to call to the comparison method to check the equality.

Take the example below, on create $this->authorId is null, on update $this->authorId is the author ID that is being modified:

/**
 * @UniqueEntity(
 *     fields={"email"}, 
 *     entityClass="App\Entity\Author", 
 *     isEqualToMethod="isEqualTo"
 * )
 */
class AuthorDTO
{
    private $authorId;
    private $email;

    // ...

    public function isEqualTo(Author $author)
    {
        return $author->getId() === $this->authorId;
    }
}

Results:

  • On create it always return false, so throws the violation.
  • On update it depends of the equality, if true pass, else throws the violation.

The validator doesn't know nothing about the current event, so the method must be called always.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also think something like isEqualToEntityMethod would make more sense.

public $fields = array();
public $errorPath = null;
public $ignoreNull = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ public function __construct(ManagerRegistry $registry)
}

/**
* @param object $entity
* @param object $object
* @param Constraint $constraint
*
* @throws UnexpectedTypeException
* @throws ConstraintDefinitionException
*/
public function validate($entity, Constraint $constraint)
public function validate($object, Constraint $constraint)
{
if (!$constraint instanceof UniqueEntity) {
throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\UniqueEntity');
Expand All @@ -60,36 +60,42 @@ public function validate($entity, Constraint $constraint)
throw new ConstraintDefinitionException('At least one field has to be specified.');
}

if (null === $entity) {
if (null === $object) {
return;
}

$objectClass = get_class($object);
$entityClass = $constraint->entityClass ?: $objectClass;

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($entity));
$em = $this->registry->getManagerForClass($entityClass);

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should use $entityClass instead of $objectClass

}
}

$class = $em->getClassMetadata(get_class($entity));
/* @var $class \Doctrine\Common\Persistence\Mapping\ClassMetadata */
$class = $em->getClassMetadata($entityClass);

$criteria = array();
$hasNullValue = false;

foreach ($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));
foreach ($fields as $objectField => $entityField) {
if (!$class->hasField($entityField) && !$class->hasAssociation($entityField)) {
throw new ConstraintDefinitionException(sprintf('The field "%s" is not mapped by Doctrine, so it cannot be validated for uniqueness.', $entityField));
}

$fieldValue = $class->reflFields[$fieldName]->getValue($entity);
$field = new \ReflectionProperty($objectClass, is_int($objectField) ? $entityField : $objectField);
if (!$field->isPublic()) {
$field->setAccessible(true);
}
$fieldValue = $field->getValue($object);

if (null === $fieldValue) {
$hasNullValue = true;
Expand All @@ -99,14 +105,14 @@ public function validate($entity, Constraint $constraint)
continue;
}

$criteria[$fieldName] = $fieldValue;
$criteria[$entityField] = $fieldValue;

if (null !== $criteria[$fieldName] && $class->hasAssociation($fieldName)) {
if (null !== $criteria[$entityField] && $class->hasAssociation($entityField)) {
/* 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.
*/
$em->initializeObject($criteria[$fieldName]);
$em->initializeObject($criteria[$entityField]);
}
}

Expand All @@ -121,22 +127,7 @@ public function validate($entity, Constraint $constraint)
return;
}

if (null !== $constraint->entityClass) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess there was a reason for this?

#15002

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well.. given $entity now can be any $object, it doesnt make sense to validate the inheritance anymore. I dont think it hurts.

cc @ogizanagi

/* Retrieve repository from given entity name.
* We ensure the retrieved repository can handle the entity
* by checking the entity is the same, or subclass of the supported entity.
*/
$repository = $em->getRepository($constraint->entityClass);
$supportedClass = $repository->getClassName();

if (!$entity instanceof $supportedClass) {
throw new ConstraintDefinitionException(sprintf('The "%s" entity repository does not support the "%s" entity. The entity should be an instance of or extend "%s".', $constraint->entityClass, $class->getName(), $supportedClass));
}
} else {
$repository = $em->getRepository(get_class($entity));
}

$result = $repository->{$constraint->repositoryMethod}($criteria);
$result = $em->getRepository($entityClass)->{$constraint->repositoryMethod}($criteria);

if ($result instanceof \IteratorAggregate) {
$result = $result->getIterator();
Expand All @@ -152,14 +143,29 @@ public function validate($entity, Constraint $constraint)
reset($result);
}

/* If no entity matched the query criteria or a single entity matched,
* which is the same as the entity being validated, the criteria is
* unique.
*/
if (0 === count($result) || (1 === count($result) && $entity === ($result instanceof \Iterator ? $result->current() : current($result)))) {
if (0 === count($result)) {
return;
}

if (1 === count($result)) {
$entity = $result instanceof \Iterator ? $result->current() : current($result);

if ($object === $entity) {
return;
}

$method = $constraint->isEntityToUpdateMethod;
if (null !== $method) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (null !== $method = $constraint->isEntityToUpdateMethod) {

if (!method_exists($object, $method)) {
throw new ConstraintDefinitionException(sprintf('Method "%s" does not exist in class %s', $method, $objectClass));
}

if (call_user_func([$object, $method], $entity)) {
return;
}
}
}

$errorPath = null !== $constraint->errorPath ? $constraint->errorPath : $fields[0];
$invalidValue = isset($criteria[$errorPath]) ? $criteria[$errorPath] : $criteria[$fields[0]];

Expand Down