Получение списка категорий бренда - с виду довольно тривиальная задача, но при её решении всплывают подводные камни. Давайте разберёмся: как правило, в интернет-магазинах товары классифицируются как минимум по двум словарям - каталог и бренд. Например, товар под названием "мобильный телефон" относится к термину "мобильные телефоны" из словаря "каталог" и к термину "Завод имени Ленина" из словаря "производители". Довольно часто заказчик ставит перед разработчиком задачу отобразить на странице производителя все категории, в которых у производителя есть товары. Т.е. на странице "Завод имени Ленина" необходимо отобразить ссылки на разделы "мобильные телефоны", "холодильники", "ядерные боеголовки". Любой, кто знаком с друпалом, но не сталкивался с решением этой задачи, с ходу скажет, что это решается за 1 минуту через Views. Однако, на самом деле всё не так просто. Сразу напрашивается алгоритм - выводим материалы, с контекстным фильтром по бренду, отображаем только поле "Каталог" и дело якобы в шляпе. Но ведь товаров могут быть тысячи, поэтому категории многократно продублируются, а поскольку вьюс сделан по нодам, то ни уникальность запроса, ни агрегация нам не помогут. Не поможет также и группировка полей, т.к. помимо поля группировки, надо вывести ещё что-нибудь. Можно конечно же раздраконить шаблон представления, убрать оттуда вывод $rows, оставить только заголовки, но это костыль, ведь вьюс всё равно будет собирать тысячи товаров, которые не будет выводить.
Именно поэтому я решил, что оптимально будет написать свой запрос в базу, чтобы получить айдишники нужных терминов, а далее уже как-нибудь их обработать - либо вывести кодом, либо передать айдишники во views, и там уже настроить вывод, как душе угодно. Сам запрос делается в таблицу taxonomy_index, а для того, чтобы получить термины другого словаря, мы просто приджойним эту таблицу к самой себе. Собственно, код:
<?php
// Select terms
$query = db_select('taxonomy_index', 'ti');
$query->innerJoin('taxonomy_index', 'ti2', 'ti.nid=ti2.nid');
// Если вам не нужно название термина, то эту строку можно пропустить
$query->innerJoin('taxonomy_term_data', 'td', 'td.tid=ti2.tid');
$query->fields('ti2', array('tid'));
// и эту тоже
$query->fields('td', array('name'));
$query->condition('ti.tid', $args[0]);
$query->condition('ti2.tid', $args[0], '<>');
$query->distinct();
$terms = $query->execute()->fetchAll();
?>
Приведённый код я использовал в своём Custom Ctools content type, поэтому аргумент с айдишником бренда указан как $args[0], естественно, вы можете использовать это и в другом контексте, например, в кастомном модуле, блоке, шаблоне и т.д. Из полученных айдишников я сгенерировал ссылки вида catalog/%category/%brand, которые ведут на страницы с вьюсами с двумя контекстными фильтрами, чтобы показать товары, относящиеся одновременно и к указанной категории, и к бренду.
В общем, берите и пользуйтесь, а если у вас есть другие способы решения этой задачи, то готов услышать вас в комментариях.
Ссылка на первоисточник в моём блоге
Комментарии
В решении для двух словарей taxonomy_index связывается сама с собой один раз, для трех словарей - уже два раза, для N словарей - N-1 раз. Может быть весьма накладно по ресурсоемкости и производительности. Кроме того, taxonomy_index содержит данные по всем словарям, может имеет смысл ограничить выборку конкретными словарями?
Я в таких случаях выбирал из taxonomy_index список nid бренда и подставлял в виде аргумента в основной вьюв, где происходила группировка по категориям.
Если данных немного, можно еще сделать "решение в лоб". Создать вьюв по терминам категории, где в каждой строке вызывать вложенный вьюв с аргументами текущей категории и бренда. Зато можно одной мышкой сделать.
Совсем не понятен кейс с тремя и более словарями. Ограничить выборку можно, приджойнив taxonomy_term_data. С гуппировкой всё равно остаётся проблема множественных выводов одинаковых значений. Может я не прав, но дайте тогда скрины настроек рабочего варианта.
А вложенные вьюсы сами по себе крайне накладны, т.к. нужно N раз вызвать вьюс, его настройки, построить его объект, применить контекстные фильтры и только потом сделать запрос.
А почему не использовать для этих целей фасеты?
Для предачи id бренда в урле, а не get-параметре - Facet API Pretty Paths.
Даже в случае Search API DB это будет работать быстрее, т.к. в базе будут лежать денормализованные индексы, запрос будет без джойнов. Ну а в случае Солра-Сфинкса - ещё лучше.
А потому что фасеты выводят варианты, присутствующие в текущей выборке. А выборки нет - нужно вывести категории, а не товары.
Не совсем понял задачу.
Вот точка входа в каталог по id категории:
https://www.densurka.ru/products/2 (2 в данном случае - это корневой термин, т.е. показывается весь каталог, можно использовать All вместо этого)
вот мы выбрали определённый бренд: https://www.densurka.ru/products/2/brand/2 (та самая ссылка catalog/%category/%brand).
В каком случае не будет выборки?
Вот именно, что не поняли. Задача вывести на странице бренда список категорий, не выводя при этом товары.
Список категорий, в которых есть товары определённого бренда?
Например, https://www.densurka.ru/brands/4 - ещё одна точка входа в каталог, теперь по брендам.
Верхний левый блок "Каталог" - это фасет, собранный в дерево с прикрученным к нему jQuery menu.
Не выводить товары - решение конечно странное, чего бы их сразу не вывести, зачем заставлять пользователя делать лишние клики.
Ну да ладно. Надо не выводить - можно и не выводить.
Всё равно, дёрнуть индекс и вывести его фасеты - дешевле по расходам, чем лезть в базу.
Абсолютно всё, что вы пишете - мимо кассы.
Повторюсь - задача вывести список категорий бренда.
Почему не выводить товары? А просто - берём Samsung - он выпускает мобильные телефоны, стиральные машины, холодильники, телевизоры и т.д. Все эти группы товаров очень отличаются одна от другой и нет абсолютно никакого смысла в том, чтобы выводить в одном списке телефон и стиральную машину.
Далее вы говорите о фасетах. Но ведь индекс строится по нодам или по продуктам, а вывести надо таксономию, соответственно и фасеты к решению данной задачи не имеют абсолютно никакого отношения.
Товар - нода (ну или продукт, привязанный к ноде-дисплею), каталог и производитель - поля сущности.
Сущность добавляется в индекс вместе со значениями полей.
Соответственно - из индекса можно получить с помощью фасетов список значений каждого поля.
Одно из полей - фиксируется по значению, по второму - строится список значений, соответствующих значению первого поля (производитель -> каталог, или наоборот каталог -> производитель, я привёл оба примера).
В таком решении - может быть сколько угодно полей-фасетов-критериев: бренд/тип/каталог, бренд/каталог/цвет/размер и т.п.
Но если хочется лезть руками в базу - тогда да, решение действительно мимо кассы.
Лезть руками в базу не хочется. Но я очень хорошо изучил вопрос, прежде чем это делать. Если по-вашему всё так легко решается вьюсами и фасетами, то не могли бы вы поделиться хотя бы скринами настроек такого вьюса?
Настройки вьюса не представляют из себя ничего сложного: с помощью search_api_views из search_api - создаем вьюс по индексу нужной сущности.
Аргументом будет являться id бренда. Так мы сразу фиксируем значение одного из полей. Собственно - всё.
На странице, где выводится вьюс - добавляем блок с фасетом по категориям каталога. В этом фасете получаем список категорий, в которых представлены сущности, имеющие значением поля бренд - передаваемый во вьюс аргумент.
Если сам вьюс не требуется - делаем вывод одного поля, например id сущности и LIMIT 0,1 - чтобы не расходовать на него ресурсы. скрываем вывод в темплейте.
Слегка костыльно, но минимум телодвижений. Можно обойтись и без вьюса, используя search_api_pages или обращаясь к индексу своим кодом, используя search_api. Просто мне результат этого вьюса был нужен.
Главное, чтобы на странице где выводится блок фасетов - присутствовал результат запроса к индексу, тогда фасет заполнится значениями.
Замечательно, а теперь перечитайте первый пост и убедитесь, что это решение изначально было отвергнуто из-за своей костыльности.
Костыльность - из-за сокрытия вьюса?
Это один из вариантов решения, я же написал про запрос к индексу своим кодом. Всё лучше, чем запрос к базе.
Вьюс в данном случае - это способ обратиться к индексу без кода.
А скрывать что-то в шаблоне - это не код? А потом если мне понадобятся картинки к категориям, придётся переопределять темизацию фасетных ссылок, причем придётся немного извратиться, чтобы на одних страницах фасетный блок был переопределён, а на других оставался в нормальном виде.
Скрывать в шаблоне - конечно же код. HTML-код, вот такой:
в views-wiew-...tpl.php - лишь бы вернулось не пустое значение.Темизация - да, придется повозиться. Но тут можно столько вводных добавить, что и Друпал не спасёт.
В вашем сайте словарей же больше чем два, вот когда-то заказчику может понадобиться выборка с тремя и более. Скажем, Страна - Бренд - Категория. Или ваше решение именно для двух словарей?
Я и пишу, почему этого не сделано в вашем решении.
$query->condition('ti2.tid', $args[0], '<>');
- здесь идет перебор по связкам nid-tid по всем словарям, нужным и ненужным. Таблица taxonomy_index вытянута в длину и может иметь огромное количество записей, может быть долго.Рабочий пример как-то не могу найти. Но, смотрите, если бы был только один словарь (Категории) - проблемы сгруппировать во вьюве бы не было? Я вместо двух словарей делаю один, узнав перечень nid этого бренда (1,5,10,11,12...). Сужаю данные таблицы node вот таким списком, как будто других брендов нет.
Накладны, безусловно. На больших данных будет тормозить. Но если N=3 и нод пол-сотни, почему нет. Стоимость разработки как сократится ). Это крайний случай, конечно, просто показал, что и так можно решить. Модуль https://www.drupal.org/project/views_field_view довольно востребован, народ пользуется.
И вы тоже ничего не поняли. Не стоит задача выводить товары. Выводить нужно только ссылки на категории. В таком случае на странице страны выводим бренды этой страны, а на страницу бренда список категорий бренда, т.к. две маленькие задачи остаются двумя маленькими задачами, не превращаясь в одну большую.
Нет, я имел в виду на одной странице выводить ссылки на категории (не товары), но в разрезе не только брендов, но и стран. Все в рамках одной задачи.
Практического смысла мало. Как правило, бренд относят к одной стране. А если надо делать выборки не по стране бренда, а по стране фактического производства, то лучше уже делать это просто ещё одним фасетным фильтром в списке именно товаров, а не категорий.
Что если ВТОРУЮ ТАКСОНОМИЮ сделать типом контент == каждый термин будет теперь соответствовать ноде, и выводить их как обычно ноды выводятся по клику на таксономию.
И связать этот тип контента через поле Entity Reference с товарами?
В таком случае задача ничуть не упростится. Повторюсь, постановка такая - вывести на странице термина термины другого словаря, которые есть в нодах из текущего термина. В вашем случае путь не сокращается, зато второго словаря просто не будет в taxonomy_index
так и не будет никакого второго словаря, вместо него - тип контента, т.е. задача будет звучать как:
"вывести на странице текущего термина таксономии ноды <первого типа>, которые через поле entity referense связаны с нодами <второго типа>, и только те, у которых это ER поле не пустое".
А вывод нод <второго типа>, ради которых вся канитель затевалась, становится отдельной задачей по настройке вывода в поле ER этих нод.
Только непонятно, как это условие "только те, у которых это ER поле не пустое" выполнить.
Вроде и с таксономией должно сработать. Делаем вьюху по терминам, добавляем связь на ноды, добавляем ещё одну связь на термины другого словаря и ставим контекстный фильтр по второй связи. Надо будет попробовать.
Странно, что сразу не догадался. И не менее странно, что не догадался никто другой)) хотя по сути получится точно такой же запрос в бд.