我有很多情况需要按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',
]
]);
但不出所料,这不起作用。有什么想法吗?
答案 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',
]
]);