indexby可以直接在查询中用于关联吗?

时间:2015-07-09 21:29:27

标签: cakephp-3.0

我有很多情况需要按ID交叉引用各种记录,并且在数组被该ID索引时发现最容易这样做。例如,分部有很多团队,分部有很多游戏,游戏属于HomeTeam和AwayTeam。当我想阅读一个部门的所有团队和游戏时,我会这样做:

$division = $this->Divisions->get($id, [
    'contain' => ['Teams', 'Games']
]);

我不做

$division = $this->Divisions->get($id, [
    'contain' => ['Teams', 'Games' => ['HomeTeam', 'AwayTeam']]
]);

因为它似乎会增加内存需求,特别是当我在团队中进一步包含其他模型(人员等)时。所以,我做了

$division->teams = collection($division->teams)->indexBy('id')->toArray();

在重新索引该数组后,然后当我在$division->games进行迭代时,为了得到主队记录,我使用了$division->teams[$game->home_team_id]。这一切都很好(除了它将团队财产设置为脏,轻微的不便)。

但似乎ORM的queryBuilder功能非常神奇,我知道我可以做到

$teams = $this->Divisions->Teams->find()
    ->where(['division_id' => $id])
    ->indexBy('id')
    ->toArray();

让一系列团队索引我想要的方式,所以我想知道是否有某种方法可以在关联中包含indexBy。我试过了

$division = $this->Divisions->get($id, [
    'contain' => [
        'Teams' => [
            'queryBuilder' => function (Query $q) {
                return $q->indexBy('id');
            },
        ],
        'Games',
    ]
]);

但不出所料,这不起作用。有什么想法吗?

1 个答案:

答案 0 :(得分:0)

只是为了记录,猜猜你已经知道了,indexBy()不属于查询,而是属于结果集,因此能够调用它需要先执行查询。将它用于关联查询构建器是不可能的,因为它必须返回查询而不是结果集。

虽然可以使用result formattes进行关联并相应地修改结果集,但问题是结果集将保存所有所有团队结果 all < / em> divisions,当团队实体分布在它们所属的各个分区实体上时,数组将分别“重新编制索引”,数组将被填充而不考虑结果集的索引,所以简而言之,这是行不通的。

全局结果格式化程序

但是,主查询的结果格式化程序应该可以正常工作,正如您可能已经想到的那样,您可以简单地重置脏状态,以防它出现任何问题,例如

$division = $this->Divisions
    ->find()
    ->contain([
        'Teams'
    ])
    ->where([
        'Divisions.id' => $id
    ])
    ->formatResults(function($results) {
        /* @var $results \Cake\Datasource\ResultSetInterface|\Cake\Collection\CollectionInterface */
        return $results
            ->map(function ($row)  {
                if (isset($row['teams'])) {
                    $row['teams'] = collection($row['teams'])->indexBy('id')->toArray();
                }
                if ($row instanceof EntityInterface) {
                    $row->dirty('teams', false);
                }
                return $row;
            });
    })
    ->firstOrFail();

自定义关联和关联特定结果格式化程序

另一种选择是使用自定义关联类,它覆盖ExternalAssociationTrait::_buildResultMap(),以便它尊重结果集的索引,因为这是问题的开始。

默认情况下,从结果集中提取关联实体并将其附加到新数组,该数组稍后将分配给结果所属实体上的相应关联属性。因此,这是可能的自定义索引结果集中的键丢失的地方。

这是一个例子,变化非常小,但我不确定可能的副作用!

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

namespace App\Model\Association;

use Cake\ORM\Association\HasMany;

class IndexAwareHasMany extends HasMany
{
    protected function _buildResultMap($fetchQuery, $options)
    {
        $resultMap = [];
        $key = (array)$options['foreignKey'];

        // grab the index here
        foreach ($fetchQuery->all() as $index => $result) {
            $values = [];
            foreach ($key as $k) {
                $values[] = $result[$k];
            }
            // and make use of it here
            $resultMap[implode(';', $values)][$index] = $result;
        }
        return $resultMap;
    }
}

原文: https://github.com/cakephp/...ORM/Association/ExternalAssociationTrait.php#L109

现在你当然必须使用新的关联来简化这个例子,让我们只是覆盖表类的'default hasMany()方法

public function hasMany($associated, array $options = [])
{
    $options += ['sourceTable' => $this];
    $association = new \App\Model\Association\IndexAwareHasMany($associated, $options);
    return $this->_associations->add($association->name(), $association);
}

现在,最后,您可以使用结果格式化器进行关联:

$division = $this->Divisions->get($id, [
    'contain' => [
        'Teams' => [
            'queryBuilder' => function (Query $query) {
                return $query
                    ->formatResults(function($results) {
                        /* @var $results \Cake\Datasource\ResultSetInterface|\Cake\Collection\CollectionInterface */
                        return $results->indexBy('id');
                    });
            }
        ],
        'Games',
    ]
]);