Symfony 4 - UniqueEntity 约束不显示消息错误

Posted

技术标签:

【中文标题】Symfony 4 - UniqueEntity 约束不显示消息错误【英文标题】:Symfony 4 - UniqueEntity constraint doesn't display message error 【发布时间】:2019-09-22 02:15:12 【问题描述】:

我正在使用 symfony 4.2 和 ReactJS。 我有一个带有邮件的表格。这封邮件应该是唯一的。所以我在 Entity 中包含了一个 UniqueEntity。

当我尝试使用表单创建帐户时,出现以下问题,它会抛出错误 500:"An error occurred","hydra:description":"An exception occurred while executing \u0027INSERT INTO app_users (id, username, email, is_active, firstname, lastname, api_link_key, facebook_id, facebook_picture_url) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\u0027 with params XXXXXX : Unique violation: 7 ERROR: duplicate key value violates unique constraint \u0022uniq_c2502824f85e0677\u0022\nDETAIL: Key (username)=(XXXXXX) already exists." 所以我在表单中没有消息错误,可能是因为这个错误 500 ?或者也许我应该在某处设置消息错误?

在我的实体中,当我设置邮件字段时,我也将用户名字段设置为相同的值。 UniqueEntity 禁止在同一行的两个字段中具有相同的值?

我的实体:

* @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;
    

感谢您的帮助

【问题讨论】:

在将数据保存到数据库之前,您是否检查了表单/实体验证?像 $form->isValid() 或 $validator->validate($user) 前面是用ReactJS生成的,我用的是api-platform。所以我没有一个控制器来验证数据。 NotBlank 断言正在工作,因为我无法在没有数据的情况下发布表单 尝试使用验证组创建帐户并将 UniqueEntity 约束添加到验证组 SQL 错误不是抱怨您的email 列,而是抱怨您也声明为唯一的用户名。所以你可能想要做的是添加一个额外的约束,它也强制用户名的唯一性:@UniqueEntity("username") 嘿,您是否将基本用法部分(代码示例下方)symfony.com/doc/current/reference/constraints/UniqueEntity.html 中的警告标记为红色并检查过,这些警告不会对您的情况造成问题? 【参考方案1】:

发生这种情况是因为您已声明字段 username 在底层数据库中必须是唯一的,但您没有在应用程序中验证每个实体实例的 username 属性确实是唯一的。 -- 这就是非唯一值到达数据库层的原因。

正如@Jakumi 所评论的,您需要为您的实体类的username 属性添加一个唯一的实体约束。您已经包含了email 属性,因此您只需将其扩展为也包含username

改变

@UniqueEntity("email")

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

忘了提一下,确保在类文件中包含UniqueEntity 约束,否则它根本不起作用:

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

最后,您可能会遇到的另一个问题是实体的用户名是空白的。因为username 属性不可为空,所以将假定它是一个空白字符串。数据库层在唯一检查中不考虑空值,因为 null 表示“根本没有定义值”,但会检查空字符串,因为空字符串是已定义的值。我建议您还验证username 属性不能为@Assert\NotBlank() 的空白以避免此问题。

【讨论】:

根据@UniqueEntity fields 的手册 如果您需要两个字段分别唯一(例如,唯一的电子邮件和唯一的用户名),您可以使用两个 UniqueEntity 条目,每个条目带有单个字段。 谢谢,但在您发表评论之前的 12 分钟内,我已经对其进行了编辑。 我引用了您的解释来源,因为UniqueEntity 可以接受fields 的数组或字符串。 eg:@UniqueEntity("email", "username") 效果不一样。 您好,感谢您的帮助。我在用户名字段上删除了 unique=true ,因为它不是强制性的。但我现在仍然在电子邮件字段上收到错误:ERROR: duplicate key value violates unique constraint \u0022uniq_c2502824f85e0677\u0022\nDETAIL: Key (email)=(XXXXXX) already exists.【参考方案2】:

最后,我创建了一个自定义验证器,用于检索所有电子邮件并检查当前电子邮件是否已在数据库中。

约束:

/**
 * @Annotation
 */
class DuplicateUser extends Constraint

    public $message = 'Email already exists.';

约束验证器:

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();
                
            
        
    

在实体中我添加了@CustomAssert\DuplicateUser(不要忘记添加use App\Validator\Constraints as CustomAssert;):

* @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;

希望对你有帮助。

【讨论】:

一旦您的数据库中的电子邮件数量增加,您检查重复的实施将变得非常缓慢。使用 where 语句直接检查新邮件效率更高。【参考方案3】:

我使用 Api 平台和 symfony @UniqueEntity 工作正常。 您不需要自定义约束。

这个注释对我有用。

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

您可以定义您想要的所有验证组。例如,“ucreate”验证组用于用户实体上的 POST 操作,“screate”组用于具有用户实体(嵌入关系 OneToOne)的学生实体上的 POST 操作。

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;

【讨论】:

以上是关于Symfony 4 - UniqueEntity 约束不显示消息错误的主要内容,如果未能解决你的问题,请参考以下文章

Symfony UniqueEntity vs UniqueConstraint vs unique=true

如何在加密字段上使用 UniqueEntity 约束?

Symfony 验证器的注释返回“注释从未导入异常”

@UniqueEntity 自定义消息未翻译

唯一实体消息

FOSUserBundle:UniqueEntity 注释抛出:字段“用户”没有被 Doctrine 映射,因此无法验证其唯一性