【问题标题】:How to filter by translations of a belongsToMany association?如何通过 belongsToMany 关联的翻译进行过滤?
【发布时间】:2018-09-25 12:35:26
【问题描述】:

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

    $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]:未找到列:1054 'where 子句'中的未知列 'Tags_name_translation.content'

缺少以下连接:

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`)
) 

现在我的问题/编辑:

为了生成缺少的连接,我在配置 CakePHP 时缺少什么?我的意图是通过翻译的标签过滤活动。它适用于非翻译。

【问题讨论】:

  • 您是否尝试通过关联的Tags 翻译过滤 Activities
  • 这正是我想要做的

标签: cakephp internationalization translation query-builder cakephp-3.5


【解决方案1】:

正如链接问题中提到的,就像包含 hasMany 关联一样,belongsToMany 关联正在单独的查询中检索,这就是翻译行为将跳转并包含翻译关联的地方(每个字段都是由一个单独的hasOne 关联表示),以便加入转换表。

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

因此,无需过多考虑,您可以例如使用相关子查询(是的,我知道,它可能性能不太好)作为过滤条件,即通过Tags查询所需的标签表,其中将包含翻译,并在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);
    });

可以看出,这将需要手动加入连接表 (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 时间“手动”创建和包含其他翻译关联。看看TranslateBehavior::setupFieldAssociations()TranslateBehavior::beforeFind() 做了什么,你必须应用类似于Activities 表的东西才能实现这一点。

【讨论】:

    猜你喜欢
    • 2012-03-13
    • 2017-04-28
    • 1970-01-01
    • 1970-01-01
    • 2020-10-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多