原则2:插入多对多关系:不插入被引用行


Doctrine 2: Insert with to-many relation: referenced row is not inserted

我有两个实体A和B,其中A有许多B(从A到B的单向多对多)。当创建新的a并为其分配Bs时,联接表上的约束失败,因为条令试图为新的a插入ID为0的关联。这让我相信当时还没有执行新a的insert。

/**
 * @Entity
 * @Table(name="a")
 */
class A {
    /**
     * @var int
     * @Id @Column(type="integer") @GeneratedValue
     */
    protected $id;
    /**
     * @var B[]
     * @ManyToMany(targetEntity="B", fetch="LAZY")
     * @JoinTable(name="jointable",
     *     joinColumns={@JoinColumn(name="a_id", referencedColumnName="id")},
     *     inverseJoinColumns={@JoinColumn(name="b_id", referencedColumnName="id")}
     * )
     */
    protected $bs;
    public function getBs() { return $this->bs; }
}
// I omit B here, because it is not important
// controller code:
$a = new A();
$a->getBs()->add($em->find(B::class, 8));
$em->persist($a);
$em->flush();

这就是我得到的错误:

An exception occurred while executing 'INSERT INTO jointable (a_id, b_id) VALUES (?, ?)' with params [0, 8]:
SQLSTATE[23000]: Integrity constraint violation...

查看数据库(和查询日志),永远不会创建A。结果,与B#8的关联失败。

我怎样才能使教义正确?

事实证明,学说做的一切都是正确的。问题是我报告的这个PHP Bug:https://bugs.php.net/bug.php?id=71059

为了使用条令进行补偿,你有两种选择:

覆盖布尔类型

如果DB中的所有布尔字段都是TINYINT(对我来说就是这样),那么您可以简单地重写布尔类型:

Type::overrideType(Type::BOOLEAN,    'Doctrine'DBAL'Types'TinyintBooleanType::class);

为TINYINT布尔添加自定义类型

如果数据库中并非所有布尔字段都是TININT,则必须向条令添加一种新类型:

Type::addType(
    'Doctrine'DBAL'Types'TinyintBooleanType::NAME,
    'Doctrine'DBAL'Types'TinyintBooleanType::class
);
$entityManager->getConnection()->getDatabasePlatform()->registerDoctrineTypeMapping(
    Type::SMALLINT, 'Doctrine'DBAL'Types'TinyintBooleanType::NAME
);  

然后你可以在实体中使用这种类型,比如:

@Column(type="tinyintasbool")

TinyintBooleanType的来源

<?php
namespace Doctrine'DBAL'Types;
use Doctrine'DBAL'Platforms'AbstractPlatform;
/**
 * This type is appropriate when storing boolean values in TINYINT columns.
 * @author Tobias Marstaller
 */
class TinyintBooleanType extends Type
{
    const NAME = "tinyintasbool";
    public function convertToDatabaseValue($value, AbstractPlatform $platform)
    {
        if ($value === null)
        {
            return null;
        }
        else return $value? 1 : 0;
    }
    public function convertToPHPValue($value, AbstractPlatform $patform)
    {
        if ($value === null)
        {
            return null;
        }
        else return ((int) $value) === 0? false : true;
    }
    /**
     * Gets the SQL declaration snippet for a field of this type.
     *
     * @param array $fieldDeclaration The field declaration.
     * @param 'Doctrine'DBAL'Platforms'AbstractPlatform $platform The currently used database platform.
     *
     * @return string
     */
    public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
    {
        return $platform->getSmallIntTypeDeclarationSQL($fieldDeclaration);
    }
    /**
     * Gets the name of this type.
     *
     * @return string
     *
     * @todo Needed?
     */
    public function getName()
    {
        return static::NAME;
    }
    public function getBindingType()
    {
        return 'PDO::PARAM_INT;
    }
}