-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
Description
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.