vorst.ru - Создание индекса ElasticSearch
Статьи из рубрики root

Пример работы с сессией

Если читатель хочет вспомнить, что просмотрено и прочитано

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


Как добавить атрибут к загруженному файлу

Добавление описания к файлу

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

Кроме описания может быть добавлено и любое другое свойство. В предыдущем примере использовалось свойство when типа radio с двумя значениями - "до" и "после". Как добавить новое свойство?


Как сделать галерею фотографий "До и После"

Показ фотографий до и после момента времени

Представьте, что вы продаете курс похудания. Вам нужно наглядно представить результат вашего курса. Фото "До" и "После", что может быть лучшей иллюстрацией?

Как и в предыдущем примере примем, что участников ограниченное количество. Например 4. Забудем про похудающих - слишком скучно. Будем "работать" с кинозвездами. Для каждого можно загрузить только 4 фотографии. Таким образом всего 16 фото. Если загружены все 16 и кому-то захочется добавить "свои" фото, то придется что-то удалить. Демо.


Делаем демо для загрузки фото

Загрузка жестко определенного количества файлов

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

Например, можно устроить, что-то вроде надписи в столовой - "Уберите, пожалуйста, за собой посуду" (демо).


Редактирование и валидация модальной формы

Использование pop-up формы

Бывает, что количество полей в таблице не слишком велико и редактировать в модальном окне проще и нагляднее. Что необходимо, чтобы организовать стандартный набор операций - CReate, Update, Delete?


Доступ к таблицам Google

Рредактирование таблиц Google из приложения

В предыдущей статье уже была ссылка на работающий пример.

Следующим шагом стало выделение процесса аутентификации на сервисе Google в отдельный класс. А это, в свою очередь, позволило сделать расширение, немного упрощающее решение задачи.


OAuth2 аутентификация на примере Google

Как работает аутентификация OAuth2

Если у вас есть аккаунт в Google, то вы наверняка пользовались документами. Например, электронными таблицами.

Доступ к таблицам разрешен только вам и тем, кому вы предоставите такое право. Причем доступ возможен не только из Google.

Можно просматривать список таблиц и редактировать выбранную таблицу оставаясь в рамках своего приложения. Примерно так - http://sample.vorst.ru/googl


Как разместить контент на двух языках

Перевод контента сайта

Для перевода интерфейса в Yii применяется таблица из двух колонок. В первой колонке записываются слова или фразы на английском, во второй - перевод на русском. Строки отсортированы по первой колонке. В тексте программы вместо нужного слова или фразы вызывается функция t('Word or Phrase'). Если текущий язык сайта английский, функция возвращает параметр, иначе соответствующий перевод.

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


Виджет для вывода списка рубрик Nested Set

Выбор всех статей по рубрике

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

Первая из них - необходимо добавить поле rubric в таблицу post. С этой операцией связано и изменения в модели common/models/Post, которые очевидно касаются методов rules() и attributeLabels().

Кроме этого нужно добавить обработку выбранной пользователем рубрики в actionIndex() контроллера frontend/controllers/PostController.


Простые действия над деревом Nested Set

Что нужно учесть для CRUD

Все необходимые методы, для управления деревом Nested Set уже определены в расширении.

Поэтому изменения в стандартном контроллере совсем небольшие. Нужно, например, вместо стандартного метода save() модели использовать метод prependTo() расширения, а вместо delete(), deleteWithChildren().


Представления для операций с Nested Set

Что нужно учесть в представлениях при редактировании Nested Set

Мы, в общих чертах, разобрались с деревьями Nested Set и создали модель для хранения дерева. В модели определили пару простых методов.

Теперь нужно разобраться с тем, как добавлять новые рубрики. Начнем с представлений.


Подключение расширения Nested Set

Определение данных и необходимых методов в модели

Мы получили общее представление о древовидной структуре данных называемой Nested Set. Теперь пришло время подключить к блогу соответствующее расширение.

Поведение выполняет работу по добавлению новых элементов в дерево, поиск всех подчиненных элементов для данного узла, удаление узла. И многое другое, что может быть использовано, например, при работе с каталогами товаров.


Как устроена структура данных Nested Set

Как сделать рубрикатор

Рубрикатор, как средство поиска интересных статей, имеет свои недостатки - трудно придумать иерархию, названия рубрик, трудно потом решить к какой конкретно рубрике относится статья. "Но, так принято" - скажите вы и будете правы.

Поэтому, давайте делать рубрикатор.


Какую статью выбрать следующей или предыдущей

Как выбрать все статьи в цепочке

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

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


Цепочки статей в блоге

Можно ли сделать блог удобнее?

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

Для группировки статей в WordPress существует возможность помечать статьи метками или прикреплять к наперед заданной рубрике. Кроме того, в конце статьи есть навигация - предыдущая, следующая статья, ну и меню конечно. Вроде все удобно.



Поиск



Как создать индекс ElasticSearch

Полнотекстовый поиск

  • 1. Как создать индекс ElasticSearch
  • 2. Интерфейс для полнотекстового поиска

Один парень написал: "Нужно сделать поиск слов в таблице "articles" по столбцу "content". В результатах поиска сначала выводить те, которые содержат наибольшее количество слов в статье и далее по мере уменьшения."

Мысль летит) Но ясно, что нужен полнотекстовый поиск.


    Поделиться

Что мы имеем? Есть таблица с заголовками, статьями и прочими полями. Нужен индекс, который позволит проводить поиск по содержимому. Искать надо слово или набор слов, причем в любом количестве и быстро. Для подобной задачи подходит ElasticSearch и начать нужно с установки Java, ElasticSearch (который требует версии PHP >= 7.0) и расширения yii2-elasticsearch.

Model

Индекс нужно создавать для модели, содержащей статьи. Допустим это модель Post. В общем каталоге для моделей, то есть в папке common/models можно создать подкаталог elastic и определить в нем класс модели для индекса (yii2-elasticsearch doc, https://habr.com/post/280488/).

<?php
namespace common\models\elastic;
use yii\elasticsearch\ActiveRecord;
class Post extends ActiveRecord
{
  // DB name
  public static function index() 
  {  
    return 'blog'; 
  } 
  // Table name
  public static function type() 
  {
    return 'post';  
  }
  public function attributes()
  {
    return ['id', 'title', 'content'];
  }
  public function rules() 
  {   
    return [
      [$this->attributes(), 'safe']  
    ];
  }
  // Table definition
  public static function mapping()
  {
    return [
      static::type() => [
        'properties' => [
          'id' => ['type' => 'long'],
          'title' => ['type' => 'text'],
          'content' => ['type' => 'text'],
        ]
      ],
    ];
  }
  public static function updateMapping()
  {
    $db = static::getDb();
    $command = $db->createCommand();
    $command->setMapping(static::index(), static::type(), static::mapping());
  }
  // Fill in by common/models/Post model
  public function fill($model, $setPrimaryKey = true) {
    if($setPrimaryKey)
      $this->primaryKey = $model->id;
    $this->attributes = [
      'title' => $model->title,
      'content'  => $model->content,
    ];
  }
  public static function createIndex()
  {
    $db = static::getDb();
    $command = $db->createCommand();
    if($command->indexExists(static::index()))
      $command->deleteIndex(static::index());
    $command->createIndex(static::index(), [
      'settings' => [
        'analysis' => [
          'filter' => [
            'ru_stop' => [
              'type' => 'stop',
              'stopwords' => '_russian_',
            ],
            'ru_stemmer' => [
              'type' => 'stemmer',
              'language' => 'russian',
            ],
          ],
          'analyzer' => [
            'default' => [
              'char_filter' => [
                'html_strip',
              ],
              'tokenizer' => 'standard',
              'filter' => [
                'lowercase',
                'ru_stop',
                'ru_stemmer',
              ],
            ],
          ],
        ],
      ],
      'mappings' => static::mapping(),
    ]);
  }
}

Полей в индексе может быть больше. Собственно, можно указать все поля, где целесообразно вести поиск. В "боевой" версии я добавил еще excerpt.

При создании индекса используется анализатор. Я просто скопировал его готовым из примера. Очевидно, что он требует к себе большего внимания, но результата можно достичь и копипастом. А результат классный - поиск ведется не только по полному совпадению слова, но и по корню.

Controller

Для выполнения поиска нужно предварительно сформировать индекс. Делается это просто. Перебираются все записи основной модели и сохраняются в индексе.

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

Кроме того, при изменении записей в основной модели, индекс требует периодического полного обновления. Эту процедуру тоже удобно проводить из консоли, через cron.

<?php
namespace console\controllers;
use yii\console\Controller;
use common\models\Post;
use common\models\elastic\Post as ElasticPost;
class ElasticController extends Controller {
  public function actionCreateIndex() {
    ElasticPost::createIndex();
    foreach(Post::find()->where([
      'status' => Post::STATUS_PUBLISHED
    ])->all() as $post) {
      $elastic = new ElasticPost();
      $elastic->fill($post);
      $elastic->save();
    }
    \Yii::info('The ElasticSearch index was created ('. 
      ElasticPost::index() .'/'. ElasticPost::type() .').', __METHOD__);
  }
}

Log message

Метод Yii::info() записывает сообщение в лог. Лог может быть не только файлом, но и email ящиком. В этом случае, после формирования индекса, можно получать извещение на нужный ящик.

Параметр categories в console/config.php определяет, что на ящик нужно отправлять только сообщения начинающиеся на указанную строку. В свою очередь строка задается в __METHOD__. Параметр logVars нужно обязательно обнулить, чтобы не получать кучу не нужной информации.

<?php
$params = array_merge(
    require(__DIR__ . '/../../common/config/params.php'),
    require(__DIR__ . '/../../common/config/params-local.php'),
    require(__DIR__ . '/params.php'),
    require(__DIR__ . '/params-local.php')
);
$mailer = require(__DIR__ . '/../../common/config/mailer.php');
return [
  'id' => 'app-console',
  'basePath' => dirname(__DIR__),
  'bootstrap' => ['log'],
  'controllerNamespace' => 'console\controllers',
  'components' => [
    'urlManager' => [
      'class' => 'yii\web\UrlManager',
        'enablePrettyUrl' => true,
        'showScriptName' => false,
        'baseUrl' => 'http://localhost/console',
    ],
    'mailer' => $mailer,
    'log' => [
      'targets' => [
        [
          'class' => 'yii\log\FileTarget',
          'levels' => ['error', 'warning'],
        ],
        [
          'class' => 'yii\log\EmailTarget',
          'levels' => ['info'],
          'categories' => ['console\controllers\ElasticController*'],
          'logVars' => [],
          'message' => [
            'from' => ['admin@sample.ru'],
            'to' => ['admin@sample.ru'],
            'subject' => 'ElasticSearch',
          ],
        ],
      ],
    ],
  ],
  'params' => $params,
];

Создание индекса ElasticSearch

Переходим в корень приложения, запускаем и ждем письма. Не забудьте определить mailer.

>php yii elastic/create-index

Проверим, как работает индекс.

curl -XGET "localhost:9200/blog/post/_search?pretty" -d'
{
  "_source": ["title"],
  "query": {
    "match": {
      "content": "history"
    }
  }
}'

Заключение

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

Leave a comment

Only authorized users can leave comments. Please log in or pass a registration.