无法删除CakePHP3树

时间:2017-06-01 08:35:21

标签: php cakephp cakephp-3.0

我有一个使用TreeBehavior的模型“Customers”,它运行良好:

例如:

id  | parent_id | type    | name
----|-----------|---------|--------------
1   | NULL      | husband | Jon Doe 
2   | 1         | wife    | Jane Doe 
3   | 1         | child   | Jim Doe 

客户可以拥有许多“合同”。所有协会都运作良好。

现在,我尝试删除客户。这仅适用于子元素(妻子,孩子):如果删除妻子,则所有相关合同也将被删除。但是,如果我尝试删除丈夫(parent_id = NULL),我会收到错误:

  

错误:SQLSTATE [23000]:完整性约束违规:1451不能   删除或更新父行:外键约束失败   (`contracts`,CONSTRAINT`contacts_fk0` FOREIGN KEY(`customer_id`)   参考文献`customers`(`id`))

SQL查询:

DELETE FROM customers WHERE ((lft) >= :c0 AND (lft) <= :c1)

我认为有一个简单的技巧,我必须做的,但我找不到解决方案。也许你们其中一个人可以帮忙吗?

提前致谢。

UPDATE1

如果没有儿童客户有合同,那么它似乎正在运作。

1 个答案:

答案 0 :(得分:1)

我认为没有办法让ORM自动处理这个问题,因为树行为使用Table::deleteAll()删除子树中的节点,这将创建一个类似于你的DELETE查询'显示,即记录被一次删除,逐个删除,这将是依赖/级联删除所必需的。

你可能想建议一个增强功能,我想如果这样做会很好,不确定它有多复杂。

您可能的解决方法可能是检索父节点的子节点,并从最外层,最内层逐个手动删除它们。这可能不会很好地执行,因为树会在每次删除时同步,这反过来又要求在删除实体之前重新加载实体,因为它们所持有的lftrgt值否则会过时

这是一个未经测试的例子来说明我在说什么:

$result = $this->Customers->connection()->transactional(function () {
    // retrieve the parent to delete
    $customer = $this->Customers->get(1);

    // retrieve the parents children
    $descendants = $this->Customers->find('children', ['for' => $customer->id]);

    // Reverse the childrens order, by default they are ordered
    // from the inner most to the outer most. This could probably
    // also be done on query level by sorting on the `lft` field.
    $entities = array_reverse($descendants->toArray());

    // append the parent so that it is being deleted last
    $entities[] = $customer;

    foreach ($entities as $entity) {
        $entity = $this->Customers->get($entity->id);
        if (!$this->Customers->delete($entity, ['atomic' => false])) {
            return false;
        }
    }

    return true;
});

理论上,这应该逐个删除节点,导致他们的dependend合约首先被删除。

或者,您可以收集树节点及其关联的合同,首先手动删除合同,然后删除父树节点。这可能会表现得更好。有点像这样:

$result = $this->Customers->connection()->transactional(function () {
    // retrieve the parent to delete
    $customer = $this->Customers->get(1, [
        'contain' => ['Contracts']
    ]);

    // retrieve the parents children
    $descendants = $this->Customers
        ->find('children', ['for' => $customer->id])
        ->contain(['Contracts']);

    // collect all contracts
    $contracts = $customer->contracts;
    foreach ($descendants as $entity) {
        $contracts = array_merge($contracts, $entity->contracts);
    }

    // delete contracts first
    // in case no callbacks are required for deleting contracts, 
    // you could also collect the customer or contract ids instead
    // and use `deleteAll()`
    foreach ($contracts as $entity) {
        if (!$this->Customers->Contracts->delete($entity, ['atomic' => false])) {
            return false;
        }
    }

    // then delete the customers
    if (!$this->Customers->delete($customer, ['atomic' => false])) {
        return false;
    }

    return true;
});

另见