禁用 Doctrine 外键约束
Posted
技术标签:
【中文标题】禁用 Doctrine 外键约束【英文标题】:Disable Doctrine foreign key constraint 【发布时间】:2017-01-14 12:00:36 【问题描述】:我与我的一个模型有关系:
/**
* @ORM\ManyToOne(targetEntity="Page", cascade="persist")
* @ORM\JoinColumn(name="page_id", referencedColumnName="id")
*/
private $parentPage;
当我删除父页面时,我得到这个错误:
Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails
基本上我的模型是一个页面和页面修订。当我删除页面时我不想删除修订。我还想在页面修订中保留page_id
(即不将其设置为空)。
我怎样才能用 Doctrine 做到这一点?
【问题讨论】:
很抱歉错误的近距离投票,我误读了您的问题。您需要做的是删除实际的外键约束。如果约束是通过 Doctrine 创建的,您需要将其配置为不再这样做,否则您只需要继续删除它 另一种方法可能是添加一个属性(例如,enabled
),将页面从搜索中排除。这样做会产生页面被删除而不违反外键约束的效果。
@Phil 我认为即便如此,从 Doctrine 的角度来看,数据不一致可能还会存在更多问题。我建议实现页面的软删除(在这种情况下我最喜欢)或将page_id
列复制为“备份”,然后在删除页面时在page_id
上设置null
。
软删除可能是最正确的解决方案,但是更改应用程序的所有其他部分以过滤掉已删除的实体会很耗时。切换到 MyISAM 也可以,这是我所做的,但并不理想。
@Petah 如果您使用gedmo 实现软删除,则无需更改任何内容即可过滤掉已删除的实体,只需在配置文件doctrine: orm: entity_managers: default: filters: softdeleteable: [class: Gedmo\SoftDeleteable\Filter\SoftDeleteableFilter & enabled: true]
中添加默认过滤器即可。然后,每当您想获取已删除的实体时,只需暂时禁用过滤器$filters = ...->get('doctrine')->getEntityManager()->getFilters(); $filters->disable('softdeleteable'); query then enable
【参考方案1】:
通过定义,如果不将键设置为空(onDelete="SET NULL"
)或级联删除操作(There are two options - ORM 级别:@987654324,则无法删除外键指向的记录@ | 数据库级别:onDelete="CASCADE"
)。 有setting a default value of a still existing record 的替代方案,但您必须手动执行此操作,我认为 Doctrine 不支持这种“开箱即用”(如果我错了,请纠正我,但在这种情况下无论如何都不需要设置默认值)。
这种严格性反映了具有外键约束的概念;就像@Théo 说的:
FK 是为了保证数据的一致性。
软删除(已经提到)是一种解决方案,但您还可以添加一个额外的 removed_page_id
列,在您在 preRemove
事件处理程序(生命周期打回来)。我想知道这些信息是否有任何价值,但我想你对它有一些用处,否则你不会问这个问题。
我绝对 并不是说这是一种好的做法,但它至少可以用于你的边缘情况。所以有些东西是:
在你的Revision
:
/**
* @ORM\ManyToOne(targetEntity="Page", cascade="persist")
* @ORM\JoinColumn(name="page_id", referencedColumnName="id", onDelete="SET NULL")
*/
private $parentPage;
/**
* @var int
* @ORM\Column(type="integer", name="removed_page_id", nullable=true)
*/
protected $removedPageId;
然后在你的Page
:
/**
* @ORM\PreRemove
*/
public function preRemovePageHandler(LifecycleEventArgs $args)
$entityManager = $args->getEntityManager();
$page = $args->getEntity();
$revisions = $page->getRevisions();
foreach($revisions as $revision)
$revision->setRemovedPageId($page->getId());
$entityManager->persist($revision);
$entityManager->flush();
或者,您当然可以在构造 Revision
期间设置正确的 $removedPageId
值,然后您甚至不需要在删除时执行生命周期回调。
【讨论】:
保证约束需要额外的计算资源。应用服务器比数据库服务器更容易横向扩展,所以约束应该放在业务逻辑中。【参考方案2】:我通过覆盖 symfony 4.3 中的一个学说类解决了这个问题,对我来说它看起来像这样:
<?php declare(strict_types=1);
namespace App\DBAL;
use Doctrine\DBAL\Platforms\mysqlPlatform;
/**
* Class MySQLPlatformService
* @package App\DBAL
*/
class MySQLPlatformService extends MySQLPlatform
/**
* Disabling the creation of foreign keys in the database (partitioning is used)
* @return false
*/
public function supportsForeignKeyConstraints(): bool
return false;
/**
* Disabling the creation of foreign keys in the database (partitioning is used)
* @return false
*/
public function supportsForeignKeyOnUpdate(): bool
return false;
【讨论】:
【参考方案3】:您可以禁用特定模型的外键导出:
User:
attributes:
export: tables
columns:
现在它只会导出表定义而不导出任何外键。您可以使用:无、表、约束、插件或全部。
【讨论】:
【参考方案4】:您明确要求数据不一致,但我很确定您真的不希望这样。我想不出这种情况是可以辩护的。这是一种不好的做法,肯定会导致问题。例如:$revision->getPage()
的预期结果是什么?
有一个非常简单优雅的解决方案:softdeletable。它基本上为您的实体添加了一个名为deletedAt
的属性(换句话说:将列添加到您的表中)以存储该实体是否(或更好:何时)被删除。因此,如果该属性是 null
,则不会删除该实体。
您唯一需要做的就是添加this bundle,为您的实体添加一个特征(Gedmo\SoftDeleteable\Traits\SoftDeleteableEntity
)并更新您的数据库。实现起来非常简单:这个包将为您完成工作。阅读 the documentation 以了解此扩展。
或者,您可以添加“已启用”布尔属性或状态字段(例如“已发布”、“草稿”、“已删除”)。
【讨论】:
【参考方案5】:当我删除页面时,我不想删除修订。我还想在页面修订中保留 page_id(即不将其设置为 null)。
我想你已经得到了答案:Doctrine 不会那样做,仅仅是因为它与外键的概念格格不入。 FK的原则是保证数据的一致性,所以如果你有一个FK,它必须引用一个已有的ID。在删除时,某些数据库引擎(例如 InnoDB for MySQL)允许您将 FK 放入 NULL
(假设您确实将 FK 列设置为可空)。但是引用不存在的 ID 是不可行的,或者它不是 FK。
如果您真的想这样做,请不要在这种特定情况下使用 Doctrine,它不会阻止您在代码库的其他地方使用 Doctrine。另一种解决方案是在查询之前手动删除 FK 约束或使用 DB 语句来跳过 FK 检查。
【讨论】:
MySQL 不是数据库引擎,它是一个 DBMS。 MySQL 的流行数据库引擎(InnoDB 和 MyISAM)处理外键的方式不同。如果您想了解更多信息,请阅读此问题:***.com/questions/12614541/…$objectManager->getConnection()->exec('SET FOREIGN_KEY_CHECKS = 0;');
以上是关于禁用 Doctrine 外键约束的主要内容,如果未能解决你的问题,请参考以下文章