Skip to content

[Doctrine] [Bridge] Add a "repository" option to the uniqueEntity validator #12573

@ogizanagi

Description

@ogizanagi

Related to #4087

I encountered kind of the same issue today.

Here is the inheritance scheme of my use case:

FOSUser
 └─ User
     ├─ Customer
     └─ Society

The BaseUser, handled by FOSUserBundle, defines an UniqueEntity constraint on both emailCanonical and usernameCanonical fields. But this constraint use the entity repository depending of the real child type of the user I want to register (i.e CustomerRepository when trying to register a new Customer).

Therefore, trying to register a new Customer with the same email or username as a Society will not fail the validation, but fails at database level.

Given the fact that a new repositoryMethod option was added in #4979, an easy workaround is to create a dedicated method in both Customer and Society repositories, calling internally the UserRepository, as for example:

# CustomerRepository | SocietyRepository

    /**
     * @param array $criteria
     * @return array
     */
    public function findByInRootUser(array $criteria)
    {
        return $this->_em->getRepository('ACMEUserBundle:User')
            ->findBy($criteria);
    }

and use it in UniqueEntity constraint definition in both entities :

#ACME\UserBundle\Entity\Customer
/**
 * @ORM\Entity(repositoryClass="ACME\UserBundle\Entity\Repositories\CustomerRepository")
 *
 * @UniqueEntity(fields="usernameCanonical", repositoryMethod="findByInRootUser",
 * message="fos_user.username.already_used", errorPath="username", groups={"Registration", "Profile"})
 * @UniqueEntity(fields="emailCanonical", repositoryMethod="findByInRootUser",
 * message="fos_user.email.already_used", errorPath="email", groups={"Registration", "Profile"})
 *
 */
class Customer extends User
{
}

#ACME\UserBundle\Entity\Society
/**
 * @ORM\Entity(repositoryClass="ACME\UserBundle\Entity\Repositories\SocietyRepository")
 *
 * @UniqueEntity(fields="usernameCanonical", repositoryMethod="findByInRootUser",
 * message="fos_user.username.already_used", errorPath="username", groups={"Registration", "Profile"})
 * @UniqueEntity(fields="emailCanonical", repositoryMethod="findByInRootUser",
 * message="fos_user.email.already_used", errorPath="email", groups={"Registration", "Profile"})
 *
 */
class Society extends User
{
}

Although it works, this induces useless code duplication (beside the fact that the FOSUser defined constraints are useless, but triggered, because we cannot overload the validation/orm.xml. But that is another story :) )
I certainly could have define the UniqueEntity constraint into my own User class. But then, when adding a new user type, I must not forget to create the findByInRootUser method into the new repository (or create a repository class to extend).

Here comes my suggestion:
Add a repository option to the UniqueEntity validator.

This will ensure the proper repository will be used by the constraint, and I could define it only at the User entity level:

#ACME\UserBundle\Entity\User
/**
 * @ORM\Entity(repositoryClass="ACME\UserBundle\Entity\Repositories\UserRepository")
 *
 * @UniqueEntity(fields="usernameCanonical", repository="ACME\UserBundle\Entity\Repositories\UserRepository",
 * message="fos_user.username.already_used", errorPath="username", groups={"Registration", "Profile"})
 * @UniqueEntity(fields="emailCanonical", repository="ACME\UserBundle\Entity\Repositories\UserRepository",
 * message="fos_user.email.already_used", errorPath="email", groups={"Registration", "Profile"})
 *
 */
class User extends FOSUser
{
}

If a value for the repository option isn't provided, the behavior remains the same: The UniqueConstraintValidator will get the repository from the real class at runtime.
Do you see any issue with that approach ?

I'm not sure of the component ability to guess the class on which the constraint was defined, but if it is possible, it could not be considered, because of the BC promise. That's why a suggested another approach.
Maybe for the 3.0 ? But it might lead to more unwanted behaviors than benefits.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions