Подключение полнотекстового поиска к проекту - vorst.ru

Интерфейс


ElasticSearch для полнотекстового поиска

Что делать, когда вашему проекту нужен быстрый полнотекстовый поиск. Как быстро подключить ElasticSearch.

Интерфейс

ElasticSearch для полнотекстового поиска

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

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


Model

frontend/models/SearchForm.php

namespace frontend\models;
use yii\base\Model;
class SearchForm extends Model
{
  public $text;
  public function rules()
  {
    return [
      ['text', 'required', 
        'message' => \Yii::t('app', 'Enter words separated by spaces.')],
      ['text', 'string', 'max' => 255],
    ];
  }
  public function attributeLabels()
  {
    return [
      'text' => \Yii::t('app', 'Search'),
    ];
  }
}

При редактировании Post, нужно сохранять изменения в индексе.

common/models/Post.php

class Post extends ActiveRecord
{
  ...
  public function afterSave($insert, $changedAttributes){
    parent::afterSave($insert, $changedAttributes);
    if($insert) {
      $elastic = new ElasticPost();
      $elastic->fill($this);
      $elastic->save(false);
    } else {
      $elastic = ElasticPost::get($this->id);
      $elastic->fill($this, false);
      $elastic->update(false, ['title', 'content']);
    }
  }

Controller

В коде контроллера frontend/controllers/PostController.php оставлены только строки, существенные для данной темы. ElasticSearch индекс нужен для поиска подходящих статей. Найдя их, сохраняем ID в массиве и передаем объекту Query. Таким образом действие контроллера потребует минимум изменений.

<?php
namespace frontend\controllers;
use yii\web\Controller;
use yii\db\Query;
use yii\data\ActiveDataProvider;
use common\models\Post;
use common\models\elastic\Post as ElasticPost;
use frontend\models\SearchForm;
use frontend\components\PostQuery;
class PostController extends Controller
{
  public $layout = 'column2';
  public function actionIndex()
  {
    $query = Post::find()
      ->where(['status' => Post::STATUS_PUBLISHED])
      ->orderBy('created_at DESC');
    $search = new SearchForm();
    if ($search->load(
      Yii::$app->request->post()) && 
      $search->validate() && $search->text
    ) {
      $query->andWhere([
        'in', 
        'id', 
        $this->getPostIdByElasticIndex($search->text)]);
    } 
    $dataProvider = new ActiveDataProvider([
      'query' => $query,
    ]);
    // save text
    $searchText = $search->text;
    // new search
    $search = new SearchForm();
    return $this->render('index', [
      'dataProvider' => $dataProvider,
      'post' => $this->getPostBySlug('post-index'),
      'search' => $search,
      'searchText' => $searchText, 
    ]);
  }
  private function getPostIdByElasticIndex($text)
  {
    $query = ElasticPost::find()->query([
      'bool' => [
        'should' => [
          PostQuery::match('title', $text),
          PostQuery::match('excerpt', $text),
          PostQuery::match('content', $text),
        ],
      ],
    ]);
    $ids = [];
    foreach($query->all() as $post) {
      $ids[] = $post->primaryKey;
    }
    return $ids;
  }
}

frontend/componets/PostQuery.php

<?php
namespace frontend\components;
use yii\db\ActiveQuery;
class PostQuery extends ActiveQuery
{
  public static function match($attribute, $text)
  {
    return [
      'match' => [
        $attribute => [
          'query' => $text,
          'operator' => 'and',
        ],        
      ],
    ];
  }
}

View

frontend/views/post/index.php

<?php
/* @var $dataProvider ActiveDataProvider */
/* @var $search string */
use yii\helpers\Html;
use yii\helpers\Url;
use yii\widgets\ListView;
use yii\data\ActiveDataProvider;
// keep for using in layout
$this->params['model'] = $search;
?>
<?php if($searchText): ?>
  <blockquote><?= \Yii::t('app', 'Posts with word(s)'); ?> <b><?= Html::encode($searchText); ?></b></blockquote>
<?php endif; ?>
<div id="post-list">
  <?php echo ListView::widget([
    'dataProvider' => $dataProvider,
    'itemView' => '_view',
    'layout' => "{items}\n{pager}",
  ]); ?>
</div>

frontend/views/layout/column2.php

<?php $this->beginContent('@app/views/layouts/main.php'); ?>
<div class="row">
  <div class="col-sm-9">
    <?= $content ?>
  </div>
  <div class="col-sm-3">
    <?= $this->render('@frontend/widgets/views/search', [
      'model' => $this->params['model'],
    ]) ?>
  </div>
</div>
<?php $this->endContent(); ?>

frontend/widgets/views/search.php

<?php
use yii\helpers\Html;
use yii\bootstrap\ActiveForm;
?>
<?php $form = ActiveForm::begin([
    'id' => 'search-form',
    'layout' => 'horizontal',
]); ?>
  <?= $form->field($model, 'text')
    ->textInput(['maxlength' => true,
      'placeholder' => \Yii::t('app', Full-text searching')
  ])->label(false) ?>
  <?= Html::submitButton(\Yii::t('app', 'Search'), [
    'class' => 'btn btn-default',
  ]) ?>
<?php ActiveForm::end(); ?>

Заключение

Как работает полнотекстовый поиск можно проверить на странице post/index. Но это только поиск, причем вспомогательный. Нет, круто конечно - быстро, просто. Но очевидно, что ElasticSearch может использоваться абсолютно самостоятельно.

Оставьте комментарий

Только авторизованные посетители могут оставлять комментарии. Пожалуйста, войдите или пройдите регистрацию.