Как отфильтровать по переводам принадлежащих ToMany ассоциаций?
Я хотел бы запросить переводы в связанной с ним принадлежности. В соответствии с документами и этим вопросом должна быть возможность запрашивать в переводах ассоциации. Я попробовал следующий (упрощенный) код:
$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']]);
ActivityTag:
$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 Неизвестный столбец "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 для генерации недостающего соединения? Мое намерение состоит в том, чтобы отфильтровать действия по переведенным тегам. Это работает для непереводов.
1 ответ
Как упомянуто в связанном вопросе, так же, как для содержания hasMany
ассоциации, belongsToMany
ассоциации извлекаются в отдельном запросе, и именно здесь поведение "Translate" включается и содержит ассоциации перевода (каждое поле представлено отдельным hasOne
ассоциация), так что таблица перевода будет объединена.
То же самое верно для *joinWith()
а также *matching()
в то время как он применяет соединения и условия к основному запросу, фактическое содержание ассоциации и связанные с ней переводы снова извлекаются в отдельном запросе, т. е. поведение Translate не будет задействовано в основном запросе, следовательно, таблица перевода не присоединяться. Можно назвать это недостатком ORM, может быть, какой-то хук для объединения / сопоставления был бы полезен в тех случаях, когда поведение может соответствующим образом изменять запросы, но пока такой вещи нет.
Таким образом, не слишком задумываясь, вы можете, например, использовать коррелированный подзапрос (да, я знаю, он может работать не слишком хорошо) в качестве условия фильтрации, то есть запрашивать требуемые теги через Tags
таблица, в которую будут включены переводы, и использовать, например, EXISTS
состояние на Activities
что-то вроде этого:
$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
стол для достижения этого.