В PrestaShop уже имеется встроенный поисковик, который обладает неплохой функциональностью, но иногда его возможностей не хватает. К тому же он не очень быстрый и на крупных сайтах сам поиск и индексация могут работать очень медленно.
Если вас это не устраивает (как и меня), то можно использовать отличную замену — Sphinx.
Цитата из Википедии:
Sphinx (англ. SQL Phrase Index) — система полнотекстового поиска, разработанная Андреем Аксеновым и распространяемая по лицензии GNU GPL. Отличительной особенностью является высокая скорость индексации и поиска, а также интеграция с существующими СУБД (MySQL, PostgreSQL) и API для распространённых языков веб-программирования (официально поддерживаются PHP, Python, Java; существуют реализованные сообществом API для Perl, Ruby,.NET[1] и C++).
Официальный сайт Sphinx — http://sphinxsearch.com/
Установка Sphinx
Я использовал Debian 8.1, в других дистрибутивах (а тем более в Windows) процесс может немного отличаться.
Если Sphinx имеется в репозиториях, то устанавливаем его:
1 2 3 | su apt-get update apt-get install sphinxsearch |
Если в репозиториях его нет, то можно скачать с официального сайта установщик, попутно установив необходимые пакеты:
1 2 3 | apt-get install mysql-client unixodbc libpq5 wget http://sphinxsearch.com/files/sphinxsearch_2.2.9-release-1~wheezy_i386.deb dpkg -i sphinxsearch_2.2.9-release-1~wheezy_i386.deb |
Имя и адрес файла установщика нужно, конечно, заменить на актуальные.
Настройка Sphinx
Открываем конфигурационный файл Sphinx и изменяем его для своих нужд:
1 | nano /etc/sphinxsearch/sphinx.conf |
Редактируем имеющийся по умолчанию блок source src1 (или добавляем свой блок), должно получиться примерно следующее:
1 2 3 4 5 6 7 8 9 10 11 12 | source PrestaSite { type = mysql sql_host = localhost #имя пользователя базы данных PrestaShop sql_user = DBUSER #пароль пользователя базы данных PrestaShop sql_pass = DBPASSWORD #имя базы данных PrestaShop sql_db = DBNAME sql_port = 3306 # optional, default is 3306 sql_query_pre = SET NAMES utf8 |
#MySQL запрос со списком полей для индексации
sql_query = \
SELECT id_product, name, description, description_short \
FROM ps_product_lang
}
Сначала идут настройки, отвечающие за подключение к базе данных (не забываем поменять значения этих параметров на свои). В параметре sql_query указан MySQL запрос, в котором описано, какая информация будет проиндексирована.
Это все незакомментированные настройки из моего конфига. В принципе, конфигурационный файл хорошо документирован и все параметры там описаны, так что вы можете без проблем настроить свою конфигурацию.
Далее, в блоке index definition настраиваем индекс:
1 2 3 4 | index PrestaSite { # Источник данных для индексирования source = PrestaSite |
# Адрес, где будут хранится данные индекса
path = /var/lib/sphinxsearch/data/prestasite
# Индекс с учетом морфологии
morphology = stem_ru
# Минимальная длина слова для индексации
min_word_len = 1
}
Далее идут блоки indexer settings и searchd settings, их можно не трогать.
Таким образом, мы настроили источник информации (source PrestaSite) и индекс (index PrestaSite).
Индексация
Чтобы проиндексировать нашу базу, запускаем indexer:
1 | indexer --all |
Запуск Sphinx
Не забываем запустить/перезапустить searchd:
1 | searchd |
И добавляем indexer в crontab:
1 | 15 * * * * root indexer --all |
Индексация будет запускаться каждый час.
Настройка PrestaShop
Ниже приведен код для PrestaShop 1.6. Для других версий он может немного отличаться.
Создаем (или редактируем) класс Search:
/override/classes/Search.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | <?php class Search extends SearchCore { public static function find($id_lang, $expr, $page_number = 1, $page_size = 1, $order_by = 'position', $order_way = 'desc', $ajax = false, $use_cookie = true, Context $context = null) { if (!$context) { $context = Context::getContext(); } $db = Db::getInstance(_PS_USE_SQL_SLAVE_); // TODO : smart page management if ($page_number < 1) $page_number = 1; if ($page_size < 1) $page_size = 1; if (!Validate::isOrderBy($order_by) || !Validate::isOrderWay($order_way)) return false; if (!Validate::isOrderBy($order_by) || !Validate::isOrderWay($order_way)) { return false; } $start = ($page_number - 1) * $page_size; // Sphinx search, get ids of found products $sphinx_results = self::getSphinxResults($expr, $start, $page_size); $result = null; $total = 0; // get products by id if something found if (is_array($sphinx_results) AND sizeof($sphinx_results) AND isset($sphinx_results['total']) AND $sphinx_results['total'] > 0) { $sql = 'SELECT p.*, product_shop.*, stock.out_of_stock, IFNULL(stock.quantity, 0) as quantity, pl.`description_short`, pl.`available_now`, pl.`available_later`, pl.`link_rewrite`, pl.`name`, MAX(image_shop.`id_image`) id_image, il.`legend`, m.`name` manufacturer_name, MAX(product_attribute_shop.`id_product_attribute`) id_product_attribute, DATEDIFF( p.`date_add`, DATE_SUB( NOW(), INTERVAL '.(Validate::isUnsignedInt(Configuration::get('PS_NB_DAYS_NEW_PRODUCT')) ? Configuration::get('PS_NB_DAYS_NEW_PRODUCT') : 20).' DAY ) ) > 0 new FROM '._DB_PREFIX_.'product p '.Shop::addSqlAssociation('product', 'p').' INNER JOIN `'._DB_PREFIX_.'product_lang` pl ON ( p.`id_product` = pl.`id_product` AND pl.`id_lang` = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('pl').' ) LEFT JOIN `'._DB_PREFIX_.'product_attribute` pa ON (p.`id_product` = pa.`id_product`) '.Shop::addSqlAssociation('product_attribute', 'pa', false, 'product_attribute_shop.`default_on` = 1').' '.Product::sqlStock('p', 'product_attribute_shop', false, $context->shop).' LEFT JOIN `'._DB_PREFIX_.'manufacturer` m ON m.`id_manufacturer` = p.`id_manufacturer` LEFT JOIN `'._DB_PREFIX_.'image` i ON (i.`id_product` = p.`id_product`)'. Shop::addSqlAssociation('image', 'i', false, 'image_shop.cover=1').' LEFT JOIN `'._DB_PREFIX_.'image_lang` il ON (i.`id_image` = il.`id_image` AND il.`id_lang` = '.(int)$id_lang.') WHERE p.`id_product` IN('.implode(',', $sphinx_results['results']).') GROUP BY product_shop.id_product'; $result = $db->executeS($sql); // results count $total = $sphinx_results['total']; } if (!$result) { $result_properties = array(); } else { $result_properties = Product::getProductsProperties((int)$id_lang, $result); } return array('total' => $total, 'result' => $result_properties); } protected static function getSphinxResults($search_query, $page_number, $page_size) { $results = array(); $total = 0; if(!$search_query) return null; // connect to Sphinx database $link = mysqli_connect('127.0.0.1', '', '', '', '9306'); if($link) { $query = 'SELECT * FROM `PrestaSite` WHERE MATCH(\''.$search_query.'\') limit '.$page_number.', '.$page_size.';'; if ($result = $link->query($query)) { while($query_results = $result->fetch_array()) { $results = array_merge($results, $query_results); } /* clear result */ $result->close(); } // get count of results $query_total = 'SELECT count(*) FROM `PrestaSite` WHERE MATCH(\''.$search_query.'\');'; if ($result = $link->query($query_total)) { $total = (int)$result->fetch_array()[0]; if($total > 1000) $total = 1000; } mysqli_close($link); } return array('results' => $results, 'total' => $total); } } |
В принципе, код довольно простой. Мы переопределяем функцию find, в которой заменяем механизм поиска PrestaShop на поиск Sphinx. Sphinx возвращает id товаров, по этим id делаем запрос на получение полных данных товаров. Вторая функция (getSphinxResults) непосредственно осуществляет поиск.
При необходимости очищаем кэш и удаляем кэш классов (cache/class_index.php). Все готово.
Thank you, I got it. Had some problems with your Search override class.
1. Got error for mysqli_connect, I changed to PDO object.
2
3
4
try {
$link = new PDO($dns,$db_user ,$db_pass);
}
2. Problem with count(*) query. I have to run two queries change it:
2
3
4
5
6
7
8
9
10
$link->query($query_total);
$query_total = 'SHOW META;';
if ($result2 = $link->query($query_total))
{
$total = (int)$result2->fetchAll()[0][1];
if($total > 1000)
$total = 1000;
}
$link = null;
Thank you for your comment and thank you for the code!
New Question:
Current search will not take to consideration product ‘out of stock’. How can I Order By stock.quantity>0 DESC
if I don’t want to include quantity to indexation?
When I add Order By in Search Class it only works per page, I want to be able to sort products in sphinx before hand.
Any idea?
I think it’s not possible without including quantity to indexation since the sorting must be applied in the Sphinx query. How can you sort by quantity without using quantity?
Спасибо тебе за этот пост.
Он был очень полезным .
Вопрос :
Какой порт я должен выбрать в search.php и должен я создать таблицу в моей базе данных для`PrestaSite`
«‘SELECT * FROM `PrestaSite` WHERE…»?
`PrestaSite` это таблица Sphinx, она создается при его настройке.
`PrestaSite` is a Sphinx table, it is created when we configure Sphinx.