vorst.ru - Статьи о задачах возникающих при создании сайта и их решении
Статьи помеченные model

Интерфейс для полнотекстового поиска

Использование ElasticSearch во frontend

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

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


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

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

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

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


Как определить и вызвать событие

Пример события

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

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


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

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

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


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

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

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

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


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

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

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

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


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

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

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


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

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

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

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


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

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

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

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


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

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

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

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



Поиск



Интерфейс для полнотекстового поиска

Использование ElasticSearch во frontend

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

На создание 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#searching. Но это только поиск, причем вспомогательный. Нет, круто конечно - быстро, просто. Но очевидно, что ElasticSearch может использоваться абсолютно самостоятельно.

Leave a comment

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