В этом уроке мы рассмотрим Elasticsearch и то, как мы можем использовать его в PHP. Elasticsearch — это поисковый сервер с открытым исходным кодом, основанный на Apache Lucene. Мы можем использовать его для выполнения сверхбыстрых полнотекстовых и других сложных поисков. Он также включает в себя REST API, который позволяет нам легко отправлять запросы на создание, удаление, обновление и получение данных.
Установка Elasticsearch
В этом руководстве предполагается, что вы используете среду на основе Debian, подобную этой, в инструкциях по установке ниже.
Для установки Elasticsearch нам сначала нужно установить Java. По умолчанию он недоступен в репозиториях, которые использует Ubuntu, поэтому нам нужно добавить его.
sudo add-apt-repository ppa:webupd8team/java sudo apt-get update
Как только это будет сделано, мы можем установить Java.
sudo apt-get install oracle-java8-installer
Далее, давайте загрузим Elasticsearch с помощью wget
.
wget https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-1.5.2.tar.gz
В настоящее время самая последняя стабильная версия — 1.5.2, так что мы использовали выше. Если вы хотите убедиться, что вы получили самую последнюю версию, взгляните на страницу загрузок Elasticsearch .
Затем мы извлекаем и устанавливаем.
mkdir es tar -xf elasticsearch-1.5.2.tar.gz -C es cd es ./bin/elasticsearch
Когда мы получаем доступ к http://localhost:9200
в браузере, мы получаем что-то похожее на следующее:
{ "status" : 200, "name" : "Rumiko Fujikawa", "cluster_name" : "elasticsearch", "version" : { "number" : "1.5.2", "build_hash" : "62ff9868b4c8a0c45860bebb259e21980778ab1c", "build_timestamp" : "2015-04-27T09:21:06Z", "build_snapshot" : false, "lucene_version" : "4.10.4" }, "tagline" : "You Know, for Search" }
Использование Elasticsearch
Теперь мы можем начать играть с Elasticsearch. Сначала давайте установим официальный клиент Elasticsearch для PHP.
composer require elasticsearch/elasticsearch
Далее, давайте создадим новый php-файл, который мы будем использовать для тестирования и со следующим кодом, чтобы мы могли использовать клиент Elasticsearch.
<?php require 'vendor/autoload.php'; $client = new Elasticsearch\Client();
Индексирование документов
Индексирование новых документов можно выполнить, вызвав метод index
на клиенте. Этот метод принимает массив в качестве аргумента. Массив должен содержать body
, index
и type
качестве его ключей. body
— это массив, содержащий данные, которые вы хотите проиндексировать. index
— это место, где вы хотите проиндексировать конкретный документ (соответствует базе данных в традиционных СУБД). Наконец, type
— это тип, который вы хотите присвоить документу, как вы хотите классифицировать документ. Это как таблица в земле СУРБД. Вот пример:
$params = array(); $params['body'] = array( 'name' => 'Ash Ketchum', 'age' => 10, 'badges' => 8 ); $params['index'] = 'pokemon'; $params['type'] = 'pokemon_trainer'; $result = $client->index($params);
Если вы распечатаете $result
вы получите нечто похожее на следующее:
Array ( [_index] => pokemon [_type] => pokemon_trainer [_id] => AU1Bn51W5l_vSaLQKPOy [_version] => 1 [created] => 1 )
В приведенном выше примере мы не указали идентификатор для документа. Elasticsearch автоматически назначает уникальный идентификатор, если ничего не указано. Давайте попробуем присвоить идентификатор другому документу:
$params = array(); $params['body'] = array( 'name' => 'Brock', 'age' => 15, 'badges' => 0 ); $params['index'] = 'pokemon'; $params['type'] = 'pokemon_trainer'; $params['id'] = '1A-000'; $result = $client->index($params);
Когда мы печатаем $result
:
Array ( [_index] => pokemon [_type] => pokemon_trainer [_id] => 1A-001 [_version] => 1 [created] => 1 )
При индексации документов мы не ограничиваемся одномерным массивом. Мы также можем индексировать многомерные:
$params = array(); $params['body'] = array( 'name' => 'Misty', 'age' => 13, 'badges' => 0, 'pokemon' => array( 'psyduck' => array( 'type' => 'water', 'moves' => array( 'Water Gun' => array( 'pp' => 25, 'power' => 40 ) ) ) ) ); $params['index'] = 'pokemon'; $params['type'] = 'pokemon_trainer'; $params['id'] = '1A-002'; $result = $client->index($params);
Мы можем идти так глубоко, как хотим, но нам все еще нужно наблюдать за надлежащим хранением данных (не углубляясь, не структурируя и не логично и т.д.), когда мы индексируем их с помощью Elasticsearch, как мы это делаем в настройках СУБД.
Поиск документов
Мы можем искать существующие документы в определенном индексе, используя метод get
или search
. Основное различие между ними заключается в том, что метод get
обычно используется, когда вы уже знаете идентификатор документа. Его также используют для получения только одного документа. С другой стороны, метод search()
используется для поиска по нескольким документам, и вы можете использовать любое поле в документе для вашего запроса.
Получить
Сначала давайте начнем с метода get
. Как и метод index
, этот метод принимает массив в качестве аргумента. Массив должен содержать index
, type
и id
документа, который вы хотите найти.
$params = array(); $params['index'] = 'pokemon'; $params['type'] = 'pokemon_trainer'; $params['id'] = '1A-001'; $result = $client->get($params);
Код выше вернет следующее:
Array ( [_index] => pokemon [_type] => pokemon_trainer [_id] => 1A-001 [_version] => 1 [found] => 1 [_source] => Array ( [name] => Brock [age] => 15 [badges] => 0 ) )
Поиск с конкретными полями
Аргумент массива для метода search
должен иметь index
, type
и ключи body
. body
— это то, где мы указываем запрос. Для начала, вот пример того, как мы используем его для возврата всех документов, имеющих возраст 15 лет.
$params['index'] = 'pokemon'; $params['type'] = 'pokemon_trainer'; $params['body']['query']['match']['age'] = 15; $result = $client->search($params);
Это возвращает следующее:
Array ( [took] => 177 [timed_out] => [_shards] => Array ( [total] => 5 [successful] => 5 [failed] => 0 ) [hits] => Array ( [total] => 1 [max_score] => 1 [hits] => Array ( [0] => Array ( [_index] => pokemon [_type] => pokemon_trainer [_id] => 1A-001 [_score] => 1 [_source] => Array ( [name] => Brock [age] => 15 [badges] => 0 ) ) ) ) )
Давайте разберем результаты:
-
took
— количество миллисекунд, которое потребовалось для завершения запроса. -
timed_out
— возвращаетtrue
если время ожидания истекло. -
_shards
— по умолчанию Elasticsearch распределяет данные на 5 сегментов. Если вы получаете 5 в качестве значения дляtotal
иsuccessful
то каждый шард в настоящее время здоров. Вы можете найти более подробное объяснение в этой теме Stackoverflow . -
hits
содержат результаты.
Однако метод, который мы использовали выше, позволяет искать только с глубиной первого уровня. Если мы хотим пойти дальше, мы должны использовать bool
запросы. Для этого мы указываем bool
как элемент для query
. Затем мы можем перейти к нужному полю, используя .
начиная с поля первого уровня до поля, которое мы хотим использовать в качестве запроса.
$params['index'] = 'pokemon'; $params['type'] = 'pokemon_trainer'; $params['body']['query']['bool']['must'][]['match']['pokemon.psyduck.type'] = 'water'; $result = $client->search($params);
Поиск по массивам
Мы можем выполнять поиск, используя массивы в качестве запроса (для соответствия нескольким значениям), указав элемент bool
, затем must
, terms
а затем поле, которое мы хотим использовать для запроса. Мы указываем массив, содержащий значения, которые мы хотим сопоставить. В приведенном ниже примере мы выбираем документы, age
которых равен 10 и 15 age
.
$params['index'] = 'pokemon'; $params['type'] = 'pokemon_trainer'; $params['body']['query']['bool']['must']['terms']['age'] = array(10, 15);
Этот метод принимает только одномерные массивы.
Фильтрованный поиск
Далее, давайте сделаем фильтрованный поиск. Чтобы использовать отфильтрованный поиск, мы должны указать filtered
элемент и установить диапазон, который мы хотим вернуть для определенного поля. В приведенном ниже примере мы используем age
в качестве поля. Мы выбираем документы, возраст которых больше или равен (gte) 11, но меньше или равен (lte) 20.
$params['index'] = 'pokemon'; $params['type'] = 'pokemon_trainer'; $params['body']['query']['filtered']['filter']['range']['age']['gte'] = 11; $params['body']['query']['filtered']['filter']['range']['age']['lte'] = 20; $result = $client->search($params);
ИЛИ И И
В земле СУБД мы привыкли использовать ключевые слова AND и OR для указания двух или более условий. Мы также можем сделать это с Elasticsearch, используя фильтрованный поиск. В приведенном ниже примере мы используем фильтр и, чтобы выбрать документы, которые имеют возраст 10 и количество значков 8. Возвращаются только те документы, которые соответствуют этим критериям.
$params['index'] = 'pokemon'; $params['type'] = 'pokemon_trainer'; $params['body']['query']['filtered']['filter']['and'][]['term']['age'] = 10; $params['body']['query']['filtered']['filter']['and'][]['term']['badges'] = 8; $result = $client->search($params);
Если вы хотите выбрать любой из них, вы можете использовать or
вместо.
$params['body']['query']['filtered']['filter']['or'][]['term']['age'] = 10; $params['body']['query']['filtered']['filter']['or'][]['term']['badges'] = 8;
Ограничение результатов
Результаты могут быть ограничены определенным числом, указав поле size
. Вот пример:
$params['body']['query']['filtered']['filter']['and'][]['term']['age'] = 10; $params['body']['query']['filtered']['filter']['and'][]['term']['badges'] = 8; $params['size'] = 1;
Это возвращает первый результат, так как мы ограничили результаты только одним документом.
пагинация
В земле СУРБД у нас есть предел и смещение. В Elasticsearch у нас есть size
и from
. from
позволяет нам указать индекс первого результата в наборе результатов. Документы индексируются нулем. Таким образом, для 10 результатов на странице, если у нас размер 10, мы добавляем 10 к значению from
каждый раз, когда пользователь переходит на следующую страницу.
$params['index'] = 'pokemon'; $params['type'] = 'pokemon_trainer'; $params['size'] = 10; $params['from'] = 10; // <-- will return second page
Обновление документа
Чтобы обновить документ, нам сначала нужно получить старые данные документа. Для этого мы указываем index
, type
и id
как мы делали ранее, а затем вызываем метод get
. Текущие данные можно найти в _source
. Все, что нам нужно сделать, это обновить текущие поля новыми значениями или добавить новые поля к этому элементу. Наконец, мы вызываем метод update
с теми же параметрами, которые использовались для метода get.
$params = array(); $params['index'] = 'pokemon'; $params['type'] = 'pokemon_trainer'; $params['id'] = '1A-001'; $result = $client->get($params); $result['_source']['age'] = 21; //update existing field with new value //add new field $result['_source']['pokemon'] = array( 'Onix' => array( 'type' => 'rock', 'moves' => array( 'Rock Slide' => array( 'power' => 100, 'pp' => 40 ), 'Earthquake' => array( 'power' => 200, 'pp' => 100 ) ) ) ); $params['body']['doc'] = $result['_source']; $result = $client->update($params);
Это возвращает что-то похожее на следующее:
Array ( [_index] => pokemon [_type] => pokemon_trainer [_id] => 1A-001 [_version] => 2 )
Обратите внимание, что _version
увеличивается каждый раз, когда вы вызываете метод update
, независимо от того, были ли обновления на самом деле.
Вам может быть интересно, почему у нас есть версия в документе, или даже возникнет соблазн думать, что в Elasticsearch есть функциональность, которая позволяет нам получить предыдущую версию документа. К сожалению, это не так. Версия просто служит счетчиком того, сколько раз документ обновлялся.
Удаление документа
delete
документ можно, вызвав метод delete
. Этот метод принимает массив, содержащий index
, type
и id
качестве аргумента.
$params = array(); $params['index'] = 'pokemon'; $params['type'] = 'pokemon_trainer'; $params['id'] = '1A-001'; $result = $client->delete($params);
Это возвращает следующее:
Array ( [found] => 1 [_index] => pokemon [_type] => pokemon_trainer [_id] => 1A-001 [_version] => 7 )
Обратите внимание, что вы получите сообщение об ошибке, если попытаетесь извлечь удаленный документ, используя метод get
.
Вывод
В этой статье мы рассмотрели, как мы можем работать с Elasticsearch в PHP, используя официальный клиент Elasticsearch. В частности, мы рассмотрели, как индексировать новые документы, искать документы, разбивать на страницы результаты и удалять документы.
В целом, Elasticsearch — это хороший способ добавить функциональность поиска в ваши PHP-приложения. Если вы хотите узнать больше о том, как интегрировать Elasticsearch в ваши PHP-приложения, вы можете ознакомиться с серией статей Дэниела Сипоса о том, как интегрировать Elasticsearch с Drupal и Silex .
Однако, если вы предпочитаете более автоматические решения добавлению в свои приложения функции углубленного поиска, см. Эту серию .