Как подружить Yii (ActiveDataProvider) и Text Search в PostgreSQL

в 15:06, , рубрики: postgresql, yii, yii framework, Песочница, метки: ,

Использование PostgreSQL tsearch2 в проекте на Yii

Любой сайт — это прежде всего тексты. Для того, чтобы тексты было удобно редактировать их часто хранят в БД. При этом появляются дополнительные возможности, такие как удобный поиск по содержимому текстового поля. Старый добрый LIKE хорош, но не всегда. Есть более продвинутые вещи, такие как tsearch2 в PostgreSQL. Как им воспользоваться в Yii Framework я расскажу под катом.

Преамбула

Однажды мне пришлось реализовывать полнотекстовый поиск на одном из сайтов, созданных мной для меня же. Для работы с tsearch2 не нужно долго гуглить и думать как оно работает, потому как на сайте есть исчерпывающее руководство. В чем прелесть использования tsearch2 по сравнению с LIKE думаю тоже объяснять не нужно. На этом сайте информации полно. Так вот, если в приложении писать SQL запросы самому в явном виде то проблем никаких, но я пользуюсь Yii и мне хотелось чтобы все было сделано путем, рекомендуемым разработчиками. Однако это не единственная причина. Для использования виджета CListView нам нужно подготовить экземпляр класса CActiveDataProvider. Вот тут то и началось самое интересное.

Решение

Для использования CActiveDataProvider нам нужно инициализировать объект класса CDbCriteria, однако в нем нет возможности изменить поле FROM для добавления туда вызова функции, формирующего запрос к движку tsearch2, вида:
SELECT ... FROM ..., to_tsquery($sh_string) AS q ...
Остается только использование модели, заполняемой вызовом методом findAllBySql, однако встроенный в CActiveDataProvider механизм поддерживает только findAll. Так как же нам сконвертировать модель, полученную из чистого SQL запроса, в CActiveDataProvider? Решение было найдено на Stackoverflow однако на этом неприятности не закончились. У меня, в таблице где хранились тексты, ключевое поле называлось не id, а txt_id, в связи с этим пришлось вносить в текст из ответа небольшую, но очень важную поправку. Вот что получилось у меня.
В контроллере:

$_shString = '';
if (isset($_GET['sh']))
{
$_shString = implode('&', explode(' ', $_GET['sh']));
$_qry = "
SELECT
txt_id,
ts_headline(txt, q, 'StartSel=, StopSel=, MaxWords=35, MinWords=15') AS txt,
ts_rank(fti_txt, q) AS rank
FROM
texts, to_tsquery(:sh) AS q
WHERE
user_id=:uid AND fti_txt @@ q
ORDER BY rank DESC
";

$_model = Texts::model()->findAllBySql($_qry, array(':uid' => Yii::app()->user->id, ':sh' => $_shString));
}

В представлении:

$this->widget('zii.widgets.CListView', array(
'id' => 'search-results',
'dataProvider' => new CArrayDataProvider($model, array('keyField' => 'txt_id')),
'itemView' => '_text',
));

Та самая правка относится к array('keyField' => 'txt_id')

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

Автор: ischerbin

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js