返回CakePHP 3.0 belongsToMany关系中关联实体ID的JSON数组

Mic*_*ael 1 cakephp has-and-belongs-to-many cakephp-3.0

如果我有两个表,比如说,foos并且bars在多对多关系中(通过bars_foos连接表,在这种情况下),CakePHP 3.0中包含与每个Foo关联的Bars的ID数组的最佳方式是什么? RequestHandler通过_serialize属性返回的JSON?

具体来说,我希望能够访问site/foos.json并看到这样的事情:

{
  "foos": [
    {
      "id": 1,
      "name": "A foo",
      ...
      "bar_ids": [1, 3, 5]
    },
    {
      "id": 2,
      "name": "Foo too",
      ...
      "bar_ids": [2]
  ]
}
Run Code Online (Sandbox Code Playgroud)

作为奖励,如果每个Foo也属于ToMany Bazzes,这应该仍然有用,所以我最终得到了,例如,

      ...
      "bar_ids": [1, 3, 5],
      "baz_ids": [37, 42]
      ...
Run Code Online (Sandbox Code Playgroud)

是否有一种直接的方法来实现这一点,很容易应用于许多模型,并不会导致过多的数据库查询?


到目前为止我尝试过的:

我已经设法以两种方式粗略地完成了这项工作,但是对于快速且容易地推广到许多模型而言似乎都不成熟.我担心添加新查询或查询字段以获取可能已经存在于CakePHP中的信息的效率,如果我只能找到正确的语法来实现它.

虚拟领域

一种方法是通过Foo实体中的_getBarIds()函数和设置创建虚拟字段protected $_virtual = ['bar_ids'].该函数使用TableRegistry查找和查询bars_foos与当前Foo关联的Bar实体的连接表,并将它们作为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]);
Run Code Online (Sandbox Code Playgroud)

这样做效果相当不错,但是_get<Model>Ids()为我的数据库中的每个关联手动添加这些函数并不吸引人,并且对于我想要检索的每一行执行一个或多个新的数据库命中肯定不是理想的.

控制器查询中的聚合

另一种方法是将连接添加到Foos Controller中的索引查询以聚合连接表.这里的关键组件如下所示:

// 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']);
Run Code Online (Sandbox Code Playgroud)

你可以用一个coalesce(..., '')string_agg(...)"[]"时,有没有任何关联酒吧,而不是NULL,并抛出一个distinct在第一个参数的开始string_agg如果多个连接在正在返回重复bar_id列,但是这是基本的想法.

这种方法对我来说更有吸引力,因为它在数据库的单个查询中获取所有内容,但它也感觉有点过于手动,并且还有一个缺点,即将数组作为字符串返回,以便在JSON中显示为"bar_ids": "[1, 3, 5]",引号周围应该是一个数组而不是一个字符串(string_agg无论如何,在我目前的PostgreSQL实现中).

这似乎不是一个特别疯狂的特征 - 我是否只是错过了一些明显的东西,它提供了一种更简单的方法来完成一般的任务?

ndm*_*ndm 5

结果格式化程序

我可能会使用包含结果格式化程序.包含关联将只需要每个关联一个附加查询来检索关联数据,然后您可以使用它来创建您喜欢的任何其他属性.

这是一个基本的例子,它应该是非常自我解释的,它只是迭代所有检索到的行,并添加一个包含相关记录的ID的新属性.

$query = $this->Foos
    ->find()
    ->contain(['Bars'])
    ->formatResults(
        function ($results) {
            /* @var $results \Cake\Datasource\ResultSetInterface|\Cake\Collection\CollectionInterface */
            return $results->map(function ($row) {
                /* @var $row array|\Cake\DataSource\EntityInterface */
                $ids = [];
                foreach ($row['bars'] as $barRow) {
                    $ids[] = $barRow['id'];
                }
                $row['bar_ids'] = $ids;
                return $row;
            });
        }
    );      
Run Code Online (Sandbox Code Playgroud)

可重复使用的格式化程序和自定义查找器

为了保持干燥,你可以让你的表格提供格式化程序,甚至可以在自定义查找程序中包含所有这些内容.

public function formatWhateverResults($results) {
    // ...
}

public function findWhatever(Query $query, array $options)
{
    $query
        ->contain(['Bars'])
        ->formatResults([$this, 'formatWhateverResults']);
    return $query;
}
Run Code Online (Sandbox Code Playgroud)
$query = $this->Foos->find('whatever');
Run Code Online (Sandbox Code Playgroud)

进一步自动化

此外,您当然可以进一步自动化,例如通过检查表关联并处理所有包含belongsToMany的ID的ID ,例如

/**
 * @param \Cake\Datasource\ResultSetInterface|\Cake\Collection\CollectionInterface $results
 */
public function formatWhateverResults($results) {
    $associations = $this->associations()->type('BelongsToMany');
    return $results->map(function ($row) use ($associations) {
        /* @var $row array|\Cake\DataSource\EntityInterface */
        foreach ($associations as $assoc) {
            /* @var $assoc \Cake\ORM\Association */
            $property = $assoc->property();
            if (isset($row[$property])) {
                $ids = [];
                foreach ($row[$property] as $assocRow) {
                    $ids[] = $assocRow['id'];
                }
                $row[Inflector::singularize($property) . '_ids'] = $ids;
            }
        }
        return $row;
    });
}
Run Code Online (Sandbox Code Playgroud)

也可以看看