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

Skip to content

[Validator] UniqueEntity constraint can fail when using inheritance #4087

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
datiecher opened this issue Apr 23, 2012 · 20 comments
Closed

[Validator] UniqueEntity constraint can fail when using inheritance #4087

datiecher opened this issue Apr 23, 2012 · 20 comments

Comments

@datiecher
Copy link

Currently, the UniqueEntity constraint can fail when used on an inherited Entity, depending on where the validated field was declared in the Inheritance chain. Let me give you an example, suppose we have defined the following classes, where both Student and Teacher extend Person and we have used Doctrine's Class Table Inheritance:

<?php

Class Person
{
    /**
     * @UniqueEntity("name")
     */
    private $name;
}

Class Student extends Person { }

Class Teacher extends Person { }

Now let's insert a new Teacher named "Fabien", the validator kicks in, see that no Teacher with that name exists and "Fabien" is happily inserted on the database (inside the person table).

Trying to insert another Teacher with the same name works as expected, stating that our unique constraint was violated.

Now, if we try to insert a new Student whose name is "Fabien" as well the validator fails to do its work, letting this violation pass unseen and then when Doctrine tries to persist the new Student (assuming your name is a unique field, which probably should) the database will yell back at you saying that another record with the same name already exists in the person table.

This happens because the validator uses the repository of the entity in which the validation was triggered, not the repository in which the field was defined. So a SQL similar to this is generated:

SELECT * FROM student INNER JOIN person ON student.id = person.id WHERE person.name = 'Fabien'

And because of the fact that no Student with the name "Fabien" exists (we have added a Teacher with the name "Fabien") the validation will not work properly.

I'm currently working on a fix for this issue so a PR will follow shortly.

@ehibes
Copy link

ehibes commented Oct 23, 2012

Any solution for this bug ? I've got same problem…

@oopsFrogs
Copy link

me too as @ehibes

@daniel-petrovic
Copy link

i have the same problem? does anyone know some fixture or workaround for this issue ??

@barretodavid
Copy link

I'm having the same problem using multiple table inheritance

@llwt
Copy link

llwt commented Jun 12, 2013

Was this ever addressed or a work around found?

@mariusz-kraj
Copy link

I'm having the same problem

@gentisaliu
Copy link

Is this issue ever going to be fixed? It's unbelievable that it's been open for over a year already!

@cordoval
Copy link
Contributor

@gentisaliu did you attempt at fixing it? this is FOSS

@gentisaliu
Copy link

I'm trying to right now.

@gentisaliu
Copy link

It seems this is rather a Doctrine issue as @datiecher rightfully points out. I don't have the time to go deep in the Doctrine code right now.

As a shortcut I would suggest adding an option in UniqueEntity declaring the entity whose repository is to be used or implement a repository method and set the 'repositoryMethod' in UniqueEntity accordingly.

@jamyouss
Copy link

I have the same problem. Has anyone found a solution ?

@gentisaliu
Copy link

@Razmo There is no solution for this yet, it appears. I'd suggest adding a custom repository to your entity and implementing the findBy"fieldname" method yourself in there.

@kassner
Copy link

kassner commented Nov 26, 2013

I dug into code and found that inside UniqueEntityValidator::validate, $className is populated with the child FQCN, not the parent which have the constraint. If I change $className to the right FQCN, everything works fine.

I have a piece of code that solve the problem, but is very far from a good code to commit inside source. Someone can give me some insights?

        $className = $this->context->getClassName();
        $class = $em->getClassMetadata($className);

        if($class->inheritanceType == \Doctrine\ORM\Mapping\ClassMetadataInfo::INHERITANCE_TYPE_JOINED) {
            $className = $class->rootEntityName;
            $class = $em->getClassMetadata($className);
        }

I'm not sure if this is related to Doctrine, since the $className is wrongly populated, but I could not track yet from where it is being populated. My guess is that $className should be populated with the FQCN of the Entity where @UniqueEntity is declared.

@adrienrusso
Copy link

+1

@DRainmaker
Copy link

Adding a boolean (inheritedFields) option to the constraint can maybe solve this problem :

UniqueEntity.php

class UniqueEntity extends Constraint
{
    public $message = 'This value is already used.';
    public $service = 'doctrine.orm.validator.unique';
    public $em = null;
    public $repositoryMethod = 'findBy';
    public $fields = array();
    public $errorPath = null;
    public $ignoreNull = true;
    public $inheritedFields = false;

UniqueEntityValidator.php

if($constraint->inheritedFields) {
    $repository = $em->getRepository($class->rootEntityName);
}
else {
    $repository = $em->getRepository(get_class($entity));
}
$result = $repository->{$constraint->repositoryMethod}($criteria);

User.php

Class User
{
    /**
     * @UniqueEntity("email", inheritedFields=true)
     */
    private $email;
}

@rolebi
Copy link

rolebi commented Aug 19, 2014

@kassner

In Symfony\Component\Validator\Mapping\ClassMetadataFactory the parent class constraints are merged to the child constraints.

public function getMetadataFor($value)
{
    // ...

    // Include constraints from the parent class
    if ($parent = $metadata->getReflectionClass()->getParentClass()) {
        $metadata->mergeConstraints($this->getMetadataFor($parent->name));
    }

Maybe check in UniqueEntityValidator::validate if the fields are only present in the parent class and, in that case, switch to the parent repository.

We have to consider multi level of inheritance too.

@rolebi
Copy link

rolebi commented Aug 19, 2014

This quick fix works good for me but is a BC break and support only one level of inheritance.

namespace Symfony\Bridge\Doctrine\Validator\Constraints;

class UniqueEntityValidator extends ConstraintValidator
{
    // ...

    public function validate($entity, Constraint $constraint)
    {
        // ...

        $involvedClasses = [];
        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));
            }

            $property = $class->reflFields[$fieldName];
            $involvedClasses[$property->getDeclaringClass()->getName()] = true;
            $criteria[$fieldName] = $property->getValue($entity);

            // ...
        }

        /*
         * We have to switch to parent repository if fields are only present in parent.
         * Does not support multi level inheritance.
         * @see https://github.com/symfony/symfony/issues/4087
         */
        if (count($involvedClasses) == 1) {
            $repository = $em->getRepository(key($involvedClasses));
        } else {
            $repository = $em->getRepository(get_class($entity));
        }

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

        // ...
}

@lecajer
Copy link

lecajer commented Oct 7, 2014

There is a workaround with repositoryMethod
See http://www.kassner.com.br/en/2013/11/27/symfony-2-inheritance-and-uniqueentity-workaround/

@webmozart webmozart changed the title [Doctrine Bridge] UniqueEntity constraint can fail when using inheritance [Validator] UniqueEntity constraint can fail when using inheritance Oct 23, 2014
@fiddike
Copy link

fiddike commented Jul 11, 2015

For everyone else affected by this: Another workaround is documented here:
http://jayroman.com/blog/symfony2-quirks-with-doctrine-inheritance-and-unique-constraints

lemoinem added a commit to lemoinem/symfony that referenced this issue Dec 16, 2015
lemoinem added a commit to lemoinem/symfony that referenced this issue Feb 2, 2016
lemoinem added a commit to lemoinem/symfony that referenced this issue Feb 2, 2016
lemoinem added a commit to lemoinem/symfony that referenced this issue Feb 2, 2016
lemoinem added a commit to lemoinem/symfony that referenced this issue Feb 2, 2016
lemoinem added a commit to lemoinem/symfony that referenced this issue Feb 3, 2016
@wholehogsoftware
Copy link

+1

The solution presented here worked for me:

http://jayroman.com/blog/symfony2-quirks-with-doctrine-inheritance-and-unique-constraints

I then ran into another issue after this which was caused by needing to validate at both levels which resulted in 2 validation errors instead of the anticipated 1 but that can be resolved via validation groups so that's not a big deal and certainly better than exceptions.

fabpot added a commit that referenced this issue Oct 14, 2016
…ed by the UniqueEntity validator (ogizanagi)

This PR was merged into the 3.2-dev branch.

Discussion
----------

[DoctrineBridge] Add a way to select the repository used by the UniqueEntity validator

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #12573, #4087, #12977
| License       | MIT
| Doc PR        | symfony/symfony-docs#7057

This is a cherry pick of #12977 on ~~2.8~~ 3.2 branch, as it is clearly a new feature, even if it was primary introduced in order to fix an inappropriate behavior that might be considered as a bug.

Commits
-------

00d5459 [Doctrine] [Bridge] Add a way to select the repository used by the UniqueEntity validator
@fabpot fabpot closed this as completed Oct 14, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests