Symfony2/Doctrine 忽略 ManyToOne 实体上的 UniqueEntity

Symfony2/Doctrine UniqueEntity on ManyToOne entity ignored

我有 "roles" 关联到 "projects"。 我不在乎角色名称是否重复,但我想确保每个项目的角色名称都不能重复。

这是我认为应该起作用的方法:

<?php
// src/AppBundle/Entity/Role.php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;

/**
 * @ORM\Entity(repositoryClass="AppBundle\Entity\RoleRepository")
 * @ORM\Table(name="roles")
 * @UniqueEntity(fields={"name","project"}, message="Duplicated role for this project")
 */
class Role
{
/**
 * @ORM\Column(type="integer")
 * @ORM\Id
 * @ORM\GeneratedValue(strategy="AUTO")
 */
protected $id;

/**
 * @ORM\Column(type="string", length=100)
 */
protected $name;

/**
 * @ORM\Column(type="text")
 */
protected $description;

...
other fields
...

/**
 * @ORM\ManyToOne(targetEntity="Project")
 */
protected $project;
}

根据文档 here 这正是我需要的:

This required option is the field (or list of fields) on which this entity should be unique. For example, if you specified both the email and name field in a single UniqueEntity constraint, then it would enforce that the combination value where unique (e.g. two users could have the same email, as long as they don't have the same name also).

约束被简单地忽略(我的意思是如果我尝试为同一个项目使用相同的角色名称,它会存储重复的角色名称和项目 ID)。

我错过了什么?

编辑:在我用 "php app/console doctrine:schema:update --force" 更新数据库后,我尝试用 SQL 直接生成错误,但没有抛出异常。现在,我不知道这个 "UniqueEntity" 验证是在数据库级别完成的还是 Doctrine 的验证器。

EDIT2:我尝试只有一个字段 ("name") 并且验证工作正常(当然只在那个字段上)。 我还尝试对字段 "name" 和 "description" 进行验证,并且有效!!!所以基本上它不会验证要验证的字段是否是指向另一个 table.

的 ID

无论如何,这是控制器:

/**
 * @Route("/role/create/{projectID}", name="role_create")
 */
public function createRoleAction(Request $request, $projectID)
{
    $prj = $this->getDoctrine()->getRepository('AppBundle:Project')->findOneById($projectID);

    $role = new Role();
    $form = $this->createForm(new RoleFormType(), $role);

    $form->handleRequest($request);

    if ($form->isValid())
        {
        $em = $this->getDoctrine()->getManager();
        $role->setProject($prj);
        $em->persist($role);
        $em->flush();
        return $this->redirect($this->generateUrl('hr_manage', array('projectID' => $projectID)));
        }

return $this->render('Role/createForm.html.twig', array('projectID' => $projectID, 'form' => $form->createView(),));
}

未执行验证,实体保留在数据库中,"project" 列指向正确的项目。这是 2 个相关字段的快照:

这是 RoleFormType(相关字段的摘录):

<?php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class RoleFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
    // add your custom field
    $builder->add('name', 'text')
            ->add('description', 'text')
            ...lots of other fields, but "project" is not present as it's passed automatically from the controller
            ->add('save', 'submit', array('label' => 'Create'));

}

public function getName()
{
    return 'role';
}

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(array('data_class' => 'AppBundle\Entity\Role',));
}

}

问题是您实际上并没有验证实体以检查是否违反唯一约束。当您调用 $form->isValid() 时,它会调用 Role 实体的验证程序,因为您将其作为表单数据 class 传递。但是,由于 project 直到之后才设置,因此 project 字段不会进行任何验证。

当你调用 $em->persist($role);$em->flush(); 时,这只是告诉 Doctrine 将实体插入到数据库中。这 2 个调用 不会 自行执行验证,因此重复项 被插入。

尝试在创建表单之前设置项目:

$role = new Role();
$role->setProject($prj);

$form = $this->createForm(new RoleFormType(), $role);

现在将在实体上设置项目,因此当调用 $form->isValid() 时,Symfony 验证器将检查唯一性。

如果这不起作用,您还需要将 project 类型作为 hidden 字段添加到表单中,以便传回,但我不认为将是必要的。

我要说的另一件事是你肯定想在你的数据库本身上添加唯一约束——这样即使你试图插入一个副本,数据库也会抛出一个异常给你并且不允许它,无论您的代码如何。