Введение в Elasticsearch в PHP

В этом уроке мы рассмотрим Elasticsearch и то, как мы можем использовать его в PHP. Elasticsearch — поисковый сервер с открытым исходным кодом, основанный на Apache Lucene. Мы можем использовать его для сверхбыстрого полнотекстового и другого сложного поиска. Он также включает REST API, который позволяет нам легко отправлять запросы на создание, удаление, обновление и получение данных.

Установка Elasticsearch

Чтобы установить 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

Во-первых, давайте начнем с 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, the 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 тогда каждый осколок в настоящее время исправен.
  • 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.

$params['index'] = 'pokemon';
$params['type']  = 'pokemon_trainer';

$params['body']['query']['bool']['must']['terms']['age'] = array(10, 15);

Этот метод принимает только одномерные массивы.

Фильтрованный поиск

Далее давайте выполним поиск с фильтром. Чтобы использовать поиск с фильтром, нам нужно указать filtered элемент и установить диапазон, который мы хотим вернуть для определенного поля. В приведенном ниже примере мы используем age поле as. Мы выбираем документы, возраст которых больше или равен (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);

OR and AND

В сфере СУРБД мы привыкли использовать ключевые слова AND и OR для указания двух или более условий. Мы также можем сделать это с помощью Elasticsearch, используя фильтрованный поиск. В приведенном ниже примере мы используем and фильтр для выбора документов с возрастом 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 метод. Этот метод принимает массив, содержащий 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. В частности, мы рассмотрели, как индексировать новые документы, искать документы, разбивать результаты на страницы и удалять документы.