Интересный способ получить список категорий бренда в Drupal

Главные вкладки

gun_dose 29 июля 2017 в 20:34
6

whyyyy???

Получение списка категорий бренда - с виду довольно тривиальная задача, но при её решении всплывают подводные камни. Давайте разберёмся: как правило, в интернет-магазинах товары классифицируются как минимум по двум словарям - каталог и бренд. Например, товар под названием "мобильный телефон" относится к термину "мобильные телефоны" из словаря "каталог" и к термину "Завод имени Ленина" из словаря "производители". Довольно часто заказчик ставит перед разработчиком задачу отобразить на странице производителя все категории, в которых у производителя есть товары. Т.е. на странице "Завод имени Ленина" необходимо отобразить ссылки на разделы "мобильные телефоны", "холодильники", "ядерные боеголовки". Любой, кто знаком с друпалом, но не сталкивался с решением этой задачи, с ходу скажет, что это решается за 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, которые ведут на страницы с вьюсами с двумя контекстными фильтрами, чтобы показать товары, относящиеся одновременно и к указанной категории, и к бренду.

В общем, берите и пользуйтесь, а если у вас есть другие способы решения этой задачи, то готов услышать вас в комментариях.

Ссылка на первоисточник в моём блоге

Автор

Комментарии

Аватар пользователя goodboy goodboy 30 июля 2017 в 1:12

В решении для двух словарей taxonomy_index связывается сама с собой один раз, для трех словарей - уже два раза, для N словарей - N-1 раз. Может быть весьма накладно по ресурсоемкости и производительности. Кроме того, taxonomy_index содержит данные по всем словарям, может имеет смысл ограничить выборку конкретными словарями?

Я в таких случаях выбирал из taxonomy_index список nid бренда и подставлял в виде аргумента в основной вьюв, где происходила группировка по категориям.

Если данных немного, можно еще сделать "решение в лоб". Создать вьюв по терминам категории, где в каждой строке вызывать вложенный вьюв с аргументами текущей категории и бренда. Зато можно одной мышкой сделать.

Аватар пользователя gun_dose gun_dose 30 июля 2017 в 1:33

Совсем не понятен кейс с тремя и более словарями. Ограничить выборку можно, приджойнив taxonomy_term_data. С гуппировкой всё равно остаётся проблема множественных выводов одинаковых значений. Может я не прав, но дайте тогда скрины настроек рабочего варианта.
А вложенные вьюсы сами по себе крайне накладны, т.к. нужно N раз вызвать вьюс, его настройки, построить его объект, применить контекстные фильтры и только потом сделать запрос.

Аватар пользователя Andruxa Andruxa 30 июля 2017 в 16:43
1

А почему не использовать для этих целей фасеты?
Для предачи id бренда в урле, а не get-параметре - Facet API Pretty Paths.

Даже в случае Search API DB это будет работать быстрее, т.к. в базе будут лежать денормализованные индексы, запрос будет без джойнов. Ну а в случае Солра-Сфинкса - ещё лучше.

Аватар пользователя gun_dose gun_dose 30 июля 2017 в 16:56

А потому что фасеты выводят варианты, присутствующие в текущей выборке. А выборки нет - нужно вывести категории, а не товары.

Аватар пользователя Andruxa Andruxa 30 июля 2017 в 17:11

Не совсем понял задачу.

Вот точка входа в каталог по id категории:
https://www.densurka.ru/products/2 (2 в данном случае - это корневой термин, т.е. показывается весь каталог, можно использовать All вместо этого)
вот мы выбрали определённый бренд: https://www.densurka.ru/products/2/brand/2 (та самая ссылка catalog/%category/%brand).
В каком случае не будет выборки?

Аватар пользователя gun_dose gun_dose 30 июля 2017 в 19:44

Вот именно, что не поняли. Задача вывести на странице бренда список категорий, не выводя при этом товары.

Аватар пользователя Andruxa Andruxa 30 июля 2017 в 20:26

Список категорий, в которых есть товары определённого бренда?
Например, https://www.densurka.ru/brands/4 - ещё одна точка входа в каталог, теперь по брендам.
Верхний левый блок "Каталог" - это фасет, собранный в дерево с прикрученным к нему jQuery menu.
Не выводить товары - решение конечно странное, чего бы их сразу не вывести, зачем заставлять пользователя делать лишние клики.
Ну да ладно. Надо не выводить - можно и не выводить.

Всё равно, дёрнуть индекс и вывести его фасеты - дешевле по расходам, чем лезть в базу.

Аватар пользователя gun_dose gun_dose 30 июля 2017 в 21:01

Абсолютно всё, что вы пишете - мимо кассы.

Повторюсь - задача вывести список категорий бренда.

Почему не выводить товары? А просто - берём Samsung - он выпускает мобильные телефоны, стиральные машины, холодильники, телевизоры и т.д. Все эти группы товаров очень отличаются одна от другой и нет абсолютно никакого смысла в том, чтобы выводить в одном списке телефон и стиральную машину.

Далее вы говорите о фасетах. Но ведь индекс строится по нодам или по продуктам, а вывести надо таксономию, соответственно и фасеты к решению данной задачи не имеют абсолютно никакого отношения.

Аватар пользователя Andruxa Andruxa 30 июля 2017 в 21:23

gun_dose wrote:

Например, товар под названием "мобильный телефон" относится к термину "мобильные телефоны" из словаря "каталог" и к термину "Завод имени Ленина" из словаря "производители".

Товар - нода (ну или продукт, привязанный к ноде-дисплею), каталог и производитель - поля сущности.
Сущность добавляется в индекс вместе со значениями полей.
Соответственно - из индекса можно получить с помощью фасетов список значений каждого поля.
Одно из полей - фиксируется по значению, по второму - строится список значений, соответствующих значению первого поля (производитель -> каталог, или наоборот каталог -> производитель, я привёл оба примера).

В таком решении - может быть сколько угодно полей-фасетов-критериев: бренд/тип/каталог, бренд/каталог/цвет/размер и т.п.
Но если хочется лезть руками в базу - тогда да, решение действительно мимо кассы.

Аватар пользователя gun_dose gun_dose 30 июля 2017 в 21:31

Лезть руками в базу не хочется. Но я очень хорошо изучил вопрос, прежде чем это делать. Если по-вашему всё так легко решается вьюсами и фасетами, то не могли бы вы поделиться хотя бы скринами настроек такого вьюса?

Аватар пользователя Andruxa Andruxa 30 июля 2017 в 21:58

Настройки вьюса не представляют из себя ничего сложного: с помощью search_api_views из search_api - создаем вьюс по индексу нужной сущности.
Аргументом будет являться id бренда. Так мы сразу фиксируем значение одного из полей. Собственно - всё.
На странице, где выводится вьюс - добавляем блок с фасетом по категориям каталога. В этом фасете получаем список категорий, в которых представлены сущности, имеющие значением поля бренд - передаваемый во вьюс аргумент.

Если сам вьюс не требуется - делаем вывод одного поля, например id сущности и LIMIT 0,1 - чтобы не расходовать на него ресурсы. скрываем вывод в темплейте.
Слегка костыльно, но минимум телодвижений. Можно обойтись и без вьюса, используя search_api_pages или обращаясь к индексу своим кодом, используя search_api. Просто мне результат этого вьюса был нужен.

Главное, чтобы на странице где выводится блок фасетов - присутствовал результат запроса к индексу, тогда фасет заполнится значениями.

Аватар пользователя gun_dose gun_dose 30 июля 2017 в 22:37

Замечательно, а теперь перечитайте первый пост и убедитесь, что это решение изначально было отвергнуто из-за своей костыльности.

Аватар пользователя Andruxa Andruxa 30 июля 2017 в 22:42

Костыльность - из-за сокрытия вьюса?
Это один из вариантов решения, я же написал про запрос к индексу своим кодом. Всё лучше, чем запрос к базе.
Вьюс в данном случае - это способ обратиться к индексу без кода.

Аватар пользователя gun_dose gun_dose 30 июля 2017 в 23:27

А скрывать что-то в шаблоне - это не код? А потом если мне понадобятся картинки к категориям, придётся переопределять темизацию фасетных ссылок, причем придётся немного извратиться, чтобы на одних страницах фасетный блок был переопределён, а на других оставался в нормальном виде.

Аватар пользователя Andruxa Andruxa 30 июля 2017 в 23:43

Скрывать в шаблоне - конечно же код. HTML-код, вот такой: &nbsp; в views-wiew-...tpl.php - лишь бы вернулось не пустое значение.
Темизация - да, придется повозиться. Но тут можно столько вводных добавить, что и Друпал не спасёт.

Аватар пользователя goodboy goodboy 30 июля 2017 в 18:02
1

gun_dose wrote:

Совсем не понятен кейс с тремя и более словарями.

В вашем сайте словарей же больше чем два, вот когда-то заказчику может понадобиться выборка с тремя и более. Скажем, Страна - Бренд - Категория. Или ваше решение именно для двух словарей?

gun_dose wrote:

Ограничить выборку можно, приджойнив taxonomy_term_data.

Я и пишу, почему этого не сделано в вашем решении.
$query->condition('ti2.tid', $args[0], '<>'); - здесь идет перебор по связкам nid-tid по всем словарям, нужным и ненужным. Таблица taxonomy_index вытянута в длину и может иметь огромное количество записей, может быть долго.

gun_dose wrote:

С гуппировкой всё равно остаётся проблема множественных выводов одинаковых значений. Может я не прав, но дайте тогда скрины настроек рабочего варианта.

Рабочий пример как-то не могу найти. Но, смотрите, если бы был только один словарь (Категории) - проблемы сгруппировать во вьюве бы не было? Я вместо двух словарей делаю один, узнав перечень nid этого бренда (1,5,10,11,12...). Сужаю данные таблицы node вот таким списком, как будто других брендов нет.

gun_dose wrote:

А вложенные вьюсы сами по себе крайне накладны, т.к. нужно N раз вызвать вьюс, его настройки, построить его объект, применить контекстные фильтры и только потом сделать запрос.

Накладны, безусловно. На больших данных будет тормозить. Но если N=3 и нод пол-сотни, почему нет. Стоимость разработки как сократится ). Это крайний случай, конечно, просто показал, что и так можно решить. Модуль https://www.drupal.org/project/views_field_view довольно востребован, народ пользуется.

Аватар пользователя gun_dose gun_dose 30 июля 2017 в 19:51

goodboy wrote:

Страна - Бренд - Категория

И вы тоже ничего не поняли. Не стоит задача выводить товары. Выводить нужно только ссылки на категории. В таком случае на странице страны выводим бренды этой страны, а на страницу бренда список категорий бренда, т.к. две маленькие задачи остаются двумя маленькими задачами, не превращаясь в одну большую.

Аватар пользователя goodboy goodboy 31 июля 2017 в 15:39

Нет, я имел в виду на одной странице выводить ссылки на категории (не товары), но в разрезе не только брендов, но и стран. Все в рамках одной задачи.

Аватар пользователя gun_dose gun_dose 31 июля 2017 в 16:37

Практического смысла мало. Как правило, бренд относят к одной стране. А если надо делать выборки не по стране бренда, а по стране фактического производства, то лучше уже делать это просто ещё одним фасетным фильтром в списке именно товаров, а не категорий.

Аватар пользователя superintendent superintendent 2 апреля 2018 в 9:56

Что если ВТОРУЮ ТАКСОНОМИЮ сделать типом контент == каждый термин будет теперь соответствовать ноде, и выводить их как обычно ноды выводятся по клику на таксономию.

И связать этот тип контента через поле Entity Reference с товарами?

Аватар пользователя gun_dose gun_dose 2 апреля 2018 в 10:15
1

В таком случае задача ничуть не упростится. Повторюсь, постановка такая - вывести на странице термина термины другого словаря, которые есть в нодах из текущего термина. В вашем случае путь не сокращается, зато второго словаря просто не будет в taxonomy_index

Аватар пользователя superintendent superintendent 25 июля 2018 в 12:49

так и не будет никакого второго словаря, вместо него - тип контента, т.е. задача будет звучать как:

"вывести на странице текущего термина таксономии ноды <первого типа>, которые через поле entity referense связаны с нодами <второго типа>, и только те, у которых это ER поле не пустое".

А вывод нод <второго типа>, ради которых вся канитель затевалась, становится отдельной задачей по настройке вывода в поле ER этих нод.

Только непонятно, как это условие "только те, у которых это ER поле не пустое" выполнить.

Аватар пользователя gun_dose gun_dose 29 июля 2018 в 18:15

Вроде и с таксономией должно сработать. Делаем вьюху по терминам, добавляем связь на ноды, добавляем ещё одну связь на термины другого словаря и ставим контекстный фильтр по второй связи. Надо будет попробовать.

Странно, что сразу не догадался. И не менее странно, что не догадался никто другой)) хотя по сути получится точно такой же запрос в бд.