0
votes

Symfony 4 - La contrainte UniqueEntity n'affiche pas d'erreur de message

J'utilise symfony 4.2 et ReactJS. J'ai un formulaire avec un mail. Ce mail doit être unique. J'ai donc un UniqueEntity inclus dans Entity.

Le problème est le suivant lorsque j'essaie de créer un compte avec le formulaire, cela me renvoie une erreur 500: "Une erreur s'est produite", "hydra: description": "Une exception s'est produite lors de l'exécution de \ u0027INSERT INTO app_users (id, username, email, is_active, firstname, lastname, api_link_key, facebook_id, facebook_picture_url) VALUES (?,?,?,?,?,?,?,?,?) \ u0027 avec les paramètres XXXXXX: violation unique: 7 ERREUR: la valeur de la clé en double enfreint la contrainte unique \ u0022uniq_c2502824f85e0677 \ u0022 \ nDETAIL: Key (username) = (XXXXXX) existing. " Donc, je n'ai pas d'erreur de message dans le formulaire peut-être à cause de cette erreur 500? Ou peut-être devrais-je définir l'erreur de message quelque part?

Dans mon entité, lorsque je définis le champ de messagerie, je définis également le champ de nom d'utilisateur avec la même valeur. L'UniqueEntity interdit d'avoir la même valeur dans deux champs de la même ligne?

Mon entité:

* @ORM\Table(name="app_users")
* @ORM\Entity(repositoryClass="App\Repository\UserRepository")
* @UniqueEntity("email")
*/
class User implements UserInterface, \Serializable
{
   /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=254, unique=true)
     * @Encrypted
     */
    private $username;

    /**
     * @ORM\Column(type="string", length=255, unique=true)
     * @Assert\Email()
     * @Assert\NotBlank()
     * @Encrypted
     */
    private $email;

   [...]

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email): void
    {
        $this->email = $email;
        $this->username = $email;
    }
}

Merci pour votre aide


7 commentaires

Avant d'enregistrer les données dans la base de données, vérifiez-vous la validation du formulaire / de l'entité? comme $ form-> isValid () ou $ validator-> validate ($ user)


Le front est généré avec ReactJS et j'utilise la plate-forme api. Je n'ai donc pas de contrôleur derrière pour valider les données. L'assertion NotBlank fonctionne car je ne peux pas publier le formulaire sans données


essayez d'utiliser un groupe de validation pour la création de compte et ajoutez les contraintes UniqueEntity aux groupes de validation


L'erreur SQL ne porte pas sur votre colonne email , mais sur le nom d'utilisateur que vous avez également déclaré comme unique. Donc, ce que vous voulez probablement faire est d'ajouter une contrainte supplémentaire qui impose également l'unicité du nom d'utilisateur: @UniqueEntity ("username")


@xabbuh J'ai laissé tomber l'unique = true dans le champ du nom d'utilisateur. Ensuite, j'ai eu la même erreur mais sur le champ email


hé, avez-vous rouge les avertissements dans la section d'utilisation de base (sous l'exemple de code) symfony.com/doc/current/reference/constraints/UniqueEntity.h‌ tml et avez-vous vérifié que cela ne posera pas de problèmes dans votre cas?


Salut @Alexis, participant au commentaire de xabbuh, s'il vous plaît, postez le bon message d'erreur. Cela aidera les autres à trouver la solution.


3 Réponses :


4
votes

Cela se produit parce que vous avez déclaré que le champ username doit être unique dans la base de données sous-jacente, mais vous ne validez pas dans votre application que la propriété username de chaque instance d'entité sera vraiment unique. - C'est pourquoi la valeur non unique atteint la couche de base de données.

Comme l'a commenté @Jakumi, vous devez ajouter une contrainte d'entité unique pour la propriété username de votre classe d'entité. Vous avez déjà inclus la propriété email , il vous suffit donc de la développer pour inclure également username .

Modifier

use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;

à

@UniqueEntity("username")
@UniqueEntity("email")

Oublié de mentionner, assurez-vous d'inclure la contrainte UniqueEntity dans votre fichier de classe, sinon cela gagnera. t fonctionne du tout:

@UniqueEntity("email")

Enfin, un autre problème que vous pouvez rencontrer est lorsque l'entité a un nom d'utilisateur vide. Étant donné que la propriété username n'est pas nullable, elle sera supposée être une chaîne vide. Les valeurs nulles ne sont pas prises en compte dans les vérifications uniques par les couches de base de données car nul signifie "aucune valeur définie du tout", mais les chaînes vides SONT vérifiées car une chaîne vide est une valeur définie. Je vous suggère également de valider que la propriété username ne peut pas être vide avec @Assert \ NotBlank () pour éviter ce problème.



1
votes

Enfin, j'ai créé un validateur personnalisé qui récupère tous les emails et vérifie que l'actuel n'est pas déjà dans la base de données.

La contrainte:

* @ORM\Table(name="app_users")
* @ORM\Entity(repositoryClass="App\Repository\UserRepository")
* @UniqueEntity("email")
*/
class User implements UserInterface, \Serializable
{
    /**
     * @ORM\Column(type="string", length=255, unique=true)
     * @Assert\Email()
     * @Assert\NotBlank()
     * @Encrypted
     * @CustomAssert\DuplicateUser
    */
    private $email;
}

Le validateur de contraintes :

class DuplicateUserValidator extends ConstraintValidator
{
    private $em;
    private $encryptor;

    /**
     * DuplicateUserValidator constructor.
     */
    public function __construct(EntityManagerInterface $em, Encryptor $encryptor)
    {
        $this->em = $em;
        $this->encryptor = $encryptor;
    }

    public function validate($value, Constraint $constraint)
    {
        if (!$constraint instanceof DuplicateUser) {
            throw new UnexpectedTypeException($constraint, DuplicateUser::class);
        }

       // custom constraints should ignore null and empty values to allow
        // other constraints (NotBlank, NotNull, etc.) take care of that
        if (null === $value || '' === $value) {
            return;
        }

        if (!is_string($value)) {
            // throw this exception if your validator cannot handle the passed type so that it can be marked as invalid
            throw new UnexpectedValueException($value, 'string');

            // separate multiple types using pipes
            // throw new UnexpectedValueException($value, 'string|int');
        }

        # Email is encrypted in database, we need to encrypt request's email value before.
        $encryptedEmail = $this->encryptor->encryptWithMarker($value);

        $qb = $this->em->createQueryBuilder();

        $qb->select('u.email')
            ->from(User::class, 'u');

        $arrayEmails = $qb->getQuery()->execute();

        foreach ($arrayEmails as $key => $arrayEmail) {
            foreach ($arrayEmail as $email) {
                if ($encryptedEmail == $email) {
                    $this->context->buildViolation($constraint->message)
                        ->addViolation();
                }
            }
        }
    }
}

Et dans l'entité j'ai ajouté @CustomAssert \ DuplicateUser (n'oubliez pas d'ajouter use App \ Validator \ Constraints as CustomAssert; ):

/**
 * @Annotation
 */
class DuplicateUser extends Constraint
{
    public $message = 'Email already exists.';
}

J'espère que cela pourra vous aider.


1 commentaires

Votre mise en œuvre pour vérifier les doublons deviendra très lente une fois que la quantité d'e-mails augmentera dans votre base de données. Il est plus efficace de rechercher directement le nouvel e-mail en utilisant une instruction where.



1
votes

J'utilise Api Platform et symfony @UniqueEntity fonctionne très bien. Vous n'avez pas besoin de contrainte personnalisée.

Cette annotation fonctionne pour moi.

On embed relations (just en example)
    /**
     * @ORM\OneToOne(targetEntity="App\Entity\User", cascade={"persist", "remove"})
     * @ORM\JoinColumn(nullable=true)
     * @Groups({"student:item:get", "student:write"})
     * @Assert\Valid(groups={"screate"})
     */
    private $user;

Vous pouvez définir tous les groupes de validation que vous souhaitez. Par exemple, le groupe de validation "ucreate" pour les actions POST sur l'entité utilisateur et le groupe "screate" pour les actions POST sur l'entité Etudiant, qui a une entité utilisateur (relation d'intégration OneToOne).

* @UniqueEntity(
 *     fields={"username"},
 *     errorPath="username",
 *     groups={"ucreate", "screate"},
 *     message="This username is already in use"
 * )

p>


0 commentaires