依赖于其他 table/model 的虚拟字段

Virtual Field with dependency on other table/model

几年前,我用 CakePHP 2.2 开发了一些东西。在我的 Bill 模型中,我有一个 virtualField 计算给定账单是否已经“完成”:

scheduled_payment = 1
&& (payed_beihilfe IS NOT NULL && payed_beihilfe > 0)
&& (payed_pkv IS NOT NULL && payed_pkv > 0)

这在出现 payed_beihilfe 始终为零的情况之前一直运行良好,因为这部分不再发挥任何作用。有关分布的信息在字段 Person.beihilfe_part 中,范围从 0 到 100。如果它是极端之一,我不想考虑那里的数量。所以我首先尝试了这个:

scheduled_payment = 1
&& ((payed_beihilfe IS NOT NULL && payed_beihilfe > 0) || Person.beihilfe_part = 0)
&& ((payed_pkv IS NOT NULL && payed_pkv > 0) || Person.beihilfe_part = 100)

这在查看单个 Bill 对象时有效。但是,一旦有人查看 Person,我就会得到 SQL 错误 are mentioned in the manual。基本上这就是说不能有需要 JOIN 操作的 virtualFields。这是一个无赖,因为我真的需要扩展我拥有的这个“完成”属性。

这是我的 Bill class:

class Bill extends AppModel {
    public $displayField = 'amount';

    public $virtualFields = array(
            # XXX There is a limitation in the `virtualFields` that it cannot
            # use any sort of `JOIN`. Therefore I have to make due with the
            # data that is available in the `bills` table. This is a real
            # kludge but it seems to work.
            "done" =>
                "scheduled_payment = 1
                && ((payed_beihilfe IS NOT NULL && payed_beihilfe > 0) || (person_id = 7 && date > '2016-06-01'))
                && (payed_pkv IS NOT NULL && payed_pkv > 0)"
    );

    public $validate = array(
        'amount' => array(
            "rule" => array("comparison", ">", 0),
            "message" => "Der Rechnungsbetrag muss größer als € 0 sein."
        )
    );


    public $belongsTo = array(
        'Doctor' => array(
            'className' => 'Doctor',
            'foreignKey' => 'doctor_id',
            'conditions' => '',
            'fields' => '',
            'order' => ''
        ),
        'Person' => array(
            'className' => 'Person',
            'foreignKey' => 'person_id',
            'conditions' => '',
            'fields' => '',
            'order' => ''
        )
    );
}

这是Person型号:

class Person extends AppModel {
    public $displayField = 'name';

    public $hasMany = array(
        'Bill' => array(
            'className' => 'Bill',
            'foreignKey' => 'person_id',
            'dependent' => false,
            'conditions' => '',
            'fields' => '',
            'order' => '',
            'limit' => '',
            'offset' => '',
            'exclusive' => '',
            'finderQuery' => '',
            'counterQuery' => ''
        )
    );
}

查询发生在 PeopleController::view 函数中:

public function view($id = null) {
    $this->Person->id = $id;
    if (!$this->Person->exists()) {
        throw new NotFoundException(__('Invalid person'));
    }
    $this->set('person', $this->Person->read(null, $id));
}

这也将绘制相关的 Bill 对象,并在那里崩溃,因为 Person table 没有 JOIN 进入获得 [= 的查询16=] 与给定 Person 相关的对象。这在 CakePHP 中隐式发生,我只是使用了脚手架。

我已经有很长一段时间没有使用 CakePHP 了,因为我已经远离了 Web 开发。在一个非常小的 Django 项目中,我看到来自 SQL table 的行被实例化为真正的 Python 对象。在那里添加一个函数或字段真的很容易。在 CakePHP 中,所有内容都存储在关联数组中。因此,我真的不知道如何添加一个复杂的函数来计算这个“完成”属性。

处理此问题的明智方法是什么?

不幸的是,没有非常干净的方法来克服这个限制。没有关联的 Model.beforeFind 事件,可以在其中修改查询,以及构建关联数据查询的点,如果不完全重新实现部分数据源逻辑 (DboSource::generateAssociationQuery() ).

我过去解决了类似的问题,方法是将自定义数据源与重写的 buildStatement() 方法结合使用,该方法会发送 Model.beforeBuildStatement 事件。该事件将为所有查询分派,因此关联模型可以收听它并在必要时修改查询。

像这样:

app/Model/Datasource/Database/AppMysql.php

<?php
App::uses('CakeEvent', 'Event');
App::uses('Mysql', 'Model/Datasource/Database');

class AppMysql extends Mysql
{
    public function buildStatement($query, Model $Model)
    {
        $targetModel = $Model;
        $linkedModel = $Model->{$query['alias']};
        if ($linkedModel !== null) {
            $targetModel = $linkedModel;
        }

        $event = new CakeEvent('Model.beforeBuildStatement', $this, array($query, $Model));
        $targetModel->getEventManager()->dispatch($event);

        if (is_array($event->result)) {
            $query = $event->result;
        }

        return parent::buildStatement($query, $Model);
    }
}

然后在模型中可以添加联接,以便虚拟字段可以访问它们。在您的情况下,它可能类似于

class Bill extends AppModel
{
    // ...

    public function implementedEvents()
    {
        return parent::implementedEvents() + array(
            'Model.beforeBuildStatement' => array(
                'callable' => 'beforeBuildStatement',
                'passParams' => true
            )
        );
    }

    public function beforeBuildStatement(array $query, Model $Model)
    {
        if ($Model->name === 'Person') {
            $query['joins'][] = array(
                'table' => 'persons',
                'alias' => 'Person',
                'type' => 'LEFT',
                'conditions' => array(
                    'Person.id = Bill.person_id',
                )
            );
        }

        return $query;
    }
}

如果$Model不是当前模型,这表明当前模型正在作为关联查询的一部分进行查询。每当作为 Person 模型的关联查询的一部分查询账单 table 时,此片段将加入 persons table。

需要注意的是,这个例子可能存在缺陷,因为唯一的条件是是否 "parent model = Person model"。您可能需要实施进一步的措施,以避免 duplicate/non-unique 连接等问题,具体取决于您的应用中发生的情况。

另见