如何在CakeORM hasMany条件中引用父/当前对象?

时间:2016-05-17 19:24:46

标签: php cakephp cakephp-3.0

所以,使用CakeORM 3.2,我有一个对象(Cabinet),它有Siblings,它们是位于相同位置的其他CabinetsCabinets相同的xylocation_id)。在hasMany关联中,我试图从Siblings中过滤出原始对象。这就是我所拥有的

class CabinetsTable extends Table { 
    public function initialize(arary $config)
    {
        $this->hasMany('Siblings', [
            'className' => CabinetsTable::class,
            'foreignKey' => ['x', 'y', 'location_id'],
            'bindingKey' => ['x', 'y', 'location_id']
        ]);
    }
}

我已尝试添加

'conditions' => [
    'cabinet_id <> Cabinets.cabinet_id'
]

作为选项,但我收到错误Unknown column 'Cabinets.cabinet_id' in 'where clause'

所以我正在寻找的是如何在条件中引用父Cabinet对象。我该怎么说Siblings that don't have the same ID as the parent

2 个答案:

答案 0 :(得分:1)

不可开箱即用

正如评论中已经提到的那样,你想要做的事情是不可能开箱即用的。

目前,ORM将创建一个新查询来检索关联的记录,并且它不会将有关父/主查询的任何信息传递给可能用于过滤关联记录的关联条件。然而,后者是必需的,因为没有关于实际父母的信息,就不可能将它们排除在外。

通过自定义关联类

使父查询可用

可以使用自定义/扩展关联类来解决问题,这使得父查询可用。或者,可以组合使用自定义/扩展的eager加载器,它将提取并传递父查询结果的主键,但这将需要重新实现一堆逻辑,这不是很好,所以让&# 39; s跳过这一点。

自定义关联类可以挂钩例如\Cake\ORM\Association\SelectableAssociationTrait::_buildQuery(),它将接收一个选项数组,该数组在query键处保存父/主查询。然后可以将该值传递给conditions回调,您可以在其中提取主键,或使用查询作为子查询来过滤掉您不想要的父Cabinets收到Siblings

一个例子

我前段时间做了类似的事情,所以我只是在这里转储一个稍微修改过的版本作为例子:

<强>的src /型号/协会/ ParentAwareHasMany.php

namespace App\Model\Association;

use Cake\ORM\Association\HasMany;

class ParentAwareHasMany extends HasMany
{
    public function find($type = null, array $options = [])
    {
        $conditions = $this->conditions();
        if (!is_callable($conditions)) {
            return parent::find($type, $options);
        }

        $this->conditions([]);
        $query = parent::find($type, $options);
        $this->conditions($conditions);

        return $query;
    }

    protected function _buildQuery($options)
    {
        $fetcherQuery = parent::_buildQuery($options);

        $callback = $this->conditions();
        if (is_callable($callback)) {
            $fetcherQuery->andWhere(function ($exp, $query) use ($callback, $options) {
                return $callback($exp, $query, $options['query']);
            });
        }

        return $fetcherQuery;
    }
}

这应该是相当自我解释的,这个类的作用是避免Association::find()调用conditions选项中定义的可能回调,而是在_buildQuery方法中调用它,在包装的回调中,通过第三个参数使父查询可用于内部回调,因此通过conditions选项定义的回调将接收表达式对象,Siblings fetcher查询和{{ 1}} fetcher父查询。

Cabinets类需要使用新的关联类,例如覆盖CabinetsTable

Table::hasMany()

然后您可以在use App\Model\Association\ParentAwareHasMany; // ... public function hasMany($associated, array $options = []) { $options += ['sourceTable' => $this]; $association = new ParentAwareHasMany($associated, $options); return $this->_associations->add($association->name(), $association); } 关联配置的conditions选项回调中进行魔术。如上所述,例如从条件中提取主键:

Siblings

使用父查询作为子查询:

use Cake\Database\Expression\Comparison;
use Cake\Database\ExpressionInterface;
use Cake\ORM\Query;

// ...

$this->hasMany('Siblings', [
    'className' => CabinetsTable::class,
    'foreignKey' => ['x', 'y', 'location_id'],
    'bindingKey' => ['x', 'y', 'location_id'],
    'conditions' => function (ExpressionInterface $exp, Query $query, Query $parentQuery) {
        $ids = [];

        /* @var $where ExpressionInterface */
        $where = $parentQuery->clause('where');
        $where->traverse(function (ExpressionInterface $expression) use (&$ids) {
            if (
                $expression instanceof Comparison &&
                $expression->getField() === 'Cabinets.id'
            ) {
                $ids = (array)$expression->getValue();
            }
        });

        // note that an empty set of IDs will trigger an error

        return [
            'Siblings.id NOT IN' => $ids
        ];
    }
]);

或者甚至可以事先执行查询(再次)并从结果中提取主键:

'conditions' => function (ExpressionInterface $exp, Query $query, Query $parentQuery) {
    $parentQuery = $parentQuery->cleanCopy();
    // the usage with `IN` requires only a single column to be selected
    $parentQuery->select('Cabinets.id', true);

    return [
        'Siblings.id NOT IN' => $parentQuery
    ];
}

注意

请注意,这些示例都有点脆弱,因为复杂的父查询可能会破坏事物,例如当涉及'conditions' => function (ExpressionInterface $exp, Query $query, Query $parentQuery) { $parentQuery = $parentQuery->cleanCopy(); // the contained `Siblings` would cause an infite loop $parentQuery->contain([], true); $ids = $parentQuery->all()->extract('id')->toArray(); return [ 'Siblings.id NOT IN' => $ids ]; } 回调时,当附加条件依赖于特定选定字段时,当存在其他比较表达式时也正在使用主键,等等。但最后这些只是让你开始的例子。

答案 1 :(得分:0)

如果你想创建Tree Cabinets(Cabinets belongsTo Cabinets),这可能会帮助你:

重要:数据库机柜必须包含字段 parent_id :整数

<强>模型/表/ CabinetsTable.php

class CabinetsTable extends Table
{

    public function initialize(array $config)
    {
        parent::initialize($config);

        $this -> table('cabinets');

        $this -> addBehavior('Tree');  // <---

        $this -> belongsTo('ParentCabinets', [ // <---
            'className' => 'Cabinets',
            'foreignKey' => 'parent_id'
        ]);

        $this -> hasMany('ChildCabinets', [ // <---
            'className' => 'Cabinets',
            'foreignKey' => 'parent_id'
        ]);


    }
}

Controller / CabinetsController.php

中的查看方法示例
class CabinetsController extends AppController
{
    ...
    public function view($id = null)
    {    

        $cabinet = $this -> Cabinets-> get($id, [
          'contain' => ['ParentCabinets',  'ChildCabinets']
        ]);

        $this -> set(compact('cabinet'));
    }
    ...

}

您可以在http://book.cakephp.org/3.0/en/orm/associations.html(分类示例)

中获得更多信息