如何通过 belongsToMany 关联的翻译进行过滤?

How to filter by translations of a belongsToMany association?

我想在关联的 belongsToMany 关系中查询翻译。根据文档和 应该可以在翻译中查询关联。我尝试了以下(简化的)代码:

    $result = $this->table()->find()
        ->where([
            $this->Activities->Tags->translationField('name') . ' LIKE' => 
                '%' . $request->filter . '%'
            ])
        ->leftJoinWith('Tags')
        ->contain(['Tags'])
        ->all()
        ->toArray();

标签和活动具有多对多关系。

活动:

    $this->belongsToMany('Tags', [
        'foreignKey' => 'activity_id',
        'targetForeignKey' => 'tag_id',
        'joinTable' => 'activities_tags'
    ]);

    $this->addBehavior('Translate', ['fields' => ['name', 'description']]);

标签:

    $this->belongsToMany('Activities', [
        'foreignKey' => 'tag_id',
        'targetForeignKey' => 'activity_id',
        'joinTable' => 'activities_tags'
    ]);

    $this->addBehavior('Translate', ['fields' => ['name']]);

活动标签:

    $this->belongsTo('Activities', [
        'foreignKey' => 'activity_id',
        'joinType' => 'INNER'
    ]);
    $this->belongsTo('Tags', [
        'foreignKey' => 'tag_id',
        'joinType' => 'INNER'
    ]);

但是,我生成了以下 SQL:

SELECT 
    ...
FROM `activities` `Activities` 
LEFT JOIN `activities_tags` `ActivitiesTags` ON `Activities`.`id` = (`ActivitiesTags`.`activity_id`) 
LEFT JOIN `tags` `Tags` ON `Tags`.`id` = (`ActivitiesTags`.`tag_id`) 
LEFT JOIN `i18n` `Activities_name_translation` ON (
    `Activities_name_translation`.`model` = :c0 
    AND `Activities_name_translation`.`field` = :c1 
    AND `Activities_name_translation`.`locale` = :c2 
    AND `Activities`.`id` = (`Activities_name_translation`.`foreign_key`)
) 
LEFT JOIN `i18n` `Activities_description_translation` ON (
    `Activities_description_translation`.`model` = :c3 
    AND `Activities_description_translation`.`field` = :c4 
    AND `Activities_description_translation`.`locale` = :c5 
    AND `Activities`.`id` = (`Activities_description_translation`.`foreign_key`)
) 
WHERE `Tags_name_translation`.`content` like :c6

这导致我出现以下错误:

QLSTATE[42S22]: Column not found: 1054 Unknown column 'Tags_name_translation.content' in 'where clause'

缺少以下连接:

LEFT JOIN `i18n` `Tags_name_translation` ON (
   `Tags_name_translation`.`model` = :c6 
    AND `Tags_name_translation`.`field` = :c7 
    AND `Tags_name_translation`.`locale` = :c8 
    AND `Tags`.`id` = (`Tags_name_translation`.`foreign_key`)
) 

现在我的question/Edit:

为了生成缺少的连接,我缺少什么来配置 CakePHP?我的目的是通过翻译的 Tags 过滤 Activities。它适用于非翻译。

如链接问题中所述,就像包含的 hasMany 关联一样,belongsToMany 关联是在单独的查询中检索的,这就是翻译行为将跳入并包含翻译关联(每个字段都由一个单独的 hasOne 关联表示),因此翻译 table 将被加入。

*joinWith()*matching() 也是如此,虽然它将在主查询上应用连接和条件,但实际的关联内容及其相关翻译将再次在单独的查询中检索,即翻译行为不会参与主查询,因此翻译 table 不会被加入。人们可能会称之为 ORM 的缺点,也许是 [=37= 的某种挂钩] 在行为可以相应地修改查询的情况下会有帮助,但目前还没有这样的东西。

因此,无需过多考虑,您可以使用相关子查询(是的,我知道,它可能不会执行得太好)作为过滤条件,即通过 Tags table,其中将包含翻译,并在 Activities 上使用例如 EXISTS 条件,类似于:

$tagsQuery = $this->Activities->Tags
    ->find()
    ->select(['id'])
    ->innerJoinWith('ActivitiesTags')
    ->where(function (\Cake\Database\Expression\QueryExpression $exp) use ($request)  {
        return $exp
            ->equalFields('ActivitiesTags.activity_id', 'Activities.id')
            ->like(
                $this->Activities->Tags->translationField('name'),
                '%' . $request->filter . '%'
            );
    });

$activitiesQuery = $this->Activities
    ->find()
    ->where(function ($exp) use ($tagsQuery) {
        return $exp->exists($tagsQuery);
    });

正如所见,这将需要手动加入 table (ActivitiesTags)(在早期的 CakePHP 版本中,您可能需要手动添加该关联 IIRC),以便您可以匹配 ActivitiesTags.activity_id。结果查询应如下所示:

SELECT 
  `Activities`.`id` AS `Activities__id`, ...
FROM 
  `activities` `Activities` 
WHERE 
  EXISTS (
    SELECT 
      `Tags`.`id` AS `Tags__id` 
    FROM 
      `tags` `Tags` 
      INNER JOIN `activities_tags` `ActivitiesTags` ON
        `Tags`.`id` = (`ActivitiesTags`.`tag_id`) 
      LEFT JOIN `i18n` `Tags_name_translation` ON (
        `Tags_name_translation`.`model` = 'Tags' 
        AND `Tags_name_translation`.`field` = 'name' 
        AND `Tags_name_translation`.`locale` = 'en_US' 
        AND `Tags`.`id` = (`Tags_name_translation`.`foreign_key`)
      ) 
    WHERE 
      (
        `ActivitiesTags`.`activity_id` = (`Activities`.`id`) 
        AND `Tags_name_translation`.`content` LIKE '%foobarbaz%'
      )
  )

很可能有其他方法可以解决这个问题,例如在 Model.beforeFind 时间创建和包含额外的翻译关联 "manually"。看看 TranslateBehavior::setupFieldAssociations()TranslateBehavior::beforeFind() 的作用,你必须应用类似于 Activities table 的东西才能实现这一点。