【发布时间】:2015-07-06 16:08:10
【问题描述】:
如果我有两个表,比如foos 和bars,在多对多关系中(在这种情况下通过bars_foos 连接表),CakePHP 3.0 中包含的最佳方法是什么RequestHandler 通过 _serialize 属性返回的 JSON 中与每个 Foo 关联的 Bars 的 ID 的数组?
具体来说,我希望能够访问site/foos.json 并看到类似这样的内容:
{
"foos": [
{
"id": 1,
"name": "A foo",
...
"bar_ids": [1, 3, 5]
},
{
"id": 2,
"name": "Foo too",
...
"bar_ids": [2]
]
}
作为奖励,如果每个 Foo 也属于ToMany Bazzes,这应该仍然有效,因此我最终会得到,例如,
...
"bar_ids": [1, 3, 5],
"baz_ids": [37, 42]
...
是否有一种直接的方法可以实现这一点,易于应用于许多模型并且不会导致过多的数据库查询?
到目前为止我尝试过的:
我已经设法通过两种方式大致实现了这一点,但似乎都不适合快速轻松地推广到许多模型。如果我只能找到正确的语法将其显示出来,我对添加新查询或查询字段以获取可能已经存在于 CakePHP 某处的信息的效率持谨慎态度。
虚拟场
一种方法是通过_getBarIds() 函数创建一个虚拟字段并在Foo 实体中设置protected $_virtual = ['bar_ids']。该函数使用 TableRegistry 查找和查询与当前 Foo 关联的 Bar 实体的 bars_foos 连接表,并将它们作为 PHP 数组返回。关键功能大致是这样的:
// In src/Model/Entity/Foo.php, protected function _getBarIds:
$bars_foos = TableRegistry::get('Bookmarks_Tags');
$these_bars = $bars_foos->find()
->select(['bar_id'])
->where(['foo_id' => $this->id]);
这工作得相当好,但是为我的数据库中的每个关联手动添加这些 _get<Model>Ids() 函数并不吸引人,并且为我想要检索的每一行执行一个或多个新的数据库命中肯定不理想。
控制器查询中的聚合
另一种方法是在 Foos 控制器中向索引查询添加连接,以聚合连接表。这里的关键组件如下所示:
// In src/Controller/FoosController.php, public function index:
$query = $this->Foos->find('all')
->select(['Foos.id', 'Foos.name',
'bar_ids' => "'[' || string_agg(BarsFoos.bar_id::text, ',') || ']'"
])
->autofields(false) // don't include other fields from bars_foos
->join([
'table' => 'bars_foos',
'alias' => 'BarsFoos',
'type' => 'LEFT',
'conditions' => 'BarsFoos.foo_id = Foos.id'
])
->group(['Foos.id', 'Foos.name']);
$this->set('foos', $query);
$this->set('_serialize', ['foos']);
当没有任何关联的 Bars 时,您可以将 coalesce(..., '') 包裹在 string_agg(...) 周围以返回 "[]" 而不是 NULL,并在 string_agg 的第一个参数的开头抛出 distinct 如果多个连接在bar_id 列中返回重复项,但这是基本思想。
这种方法对我来说更有吸引力,因为它可以在单个查询中获取所有内容到数据库,但它也感觉有点过于手动,并且具有将数组作为字符串返回的额外缺点,以便在 JSON 中它显示为 "bar_ids": "[1, 3, 5]",用引号括住应该是数组而不是字符串的内容(无论如何,在我当前使用 PostgreSQL 的 string_agg 实现时)。
这似乎不是一个特别疯狂的功能——我是不是错过了一些明显的东西,它提供了一种更简单的方法来完成一般任务?
【问题讨论】:
标签: cakephp has-and-belongs-to-many cakephp-3.0