Сквозной поиск для сайта на Yii

Документы и лупа

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

Поиск по нескольким таблицам

На сайте из нескольких разделов более вероятно, что материалы описываются разными сущностями и хранятся в разных таблицах. Исключение составляют проекты с CCK, например Drupal с модулем Views или 1C Bitrix с его инфоблоками. Чаще всего в них все придуманные разработчиком в панели управления сущности хранятся в одной общей таблице. Но на Yii такой подход не распространён, поэтому возникает задача склейки выборок из нескольких таблиц. Эту проблему мы и рассмотрим.

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

SELECT title, text FROM tbl_post WHERE title LIKE :query OR text LIKE :query

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

Предположим, что нам нужно производить поиск по блогу, новостям и страницам. Нам на помощь придёт SQL оператор UNION. Именно он служит для склейки результатов нескольких запросов в одну ленту. Самое банальное решение по выборке из трёх таблиц может выглядеть так:

SELECT title, text FROM tbl_new WHERE title LIKE :query OR text LIKE :query UNION 
SELECT title, text FROM tbl_post WHERE title LIKE :query OR text LIKE :query UNION 
SELECT title, text FROM tbl_page WHERE title LIKE :query OR text LIKE :query

Или если вынесем общие элементы за скобки:

SELECT t.* FROM (
    SELECT title, text FROM tbl_new UNION 
    SELECT title, text FROM tbl_post UNION 
    SELECT title, text FROM tbl_page
) AS t WHERE t.title LIKE :query OR t.text LIKE :query;

От трёх поисков и склейки мы перешли к одному поиску по одной склеенной временной выборке. На основе этого запроса мы и будем строить варианты решения. Первый вариант – это непосредственное использование этого запроса в DAO.

Если, например, для записей блога используется Markdown синтаксис или какие-либо другие фильтры с преобразованием HTML кода в поле text_purified при сохранении записи, то мы можем использовать псевдонимы для подмены поля text:

SELECT title, text FROM tbl_new UNION 
SELECT title, text_purified AS text FROM tbl_post UNION 
SELECT title, text FROM tbl_page

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

Поиск с использованием DAO

Если записей на сайте не так уж и много, то можно одним запросом без указания LIMIT и OFFSET выбрать все результаты в ассоциированный массив $items и передать его в CArrayDataProvider, который позаботится о разбивке на страницы:

class SearchController extends CController
{
    const ITEMS_PER_PAGE = 10;
 
    public function actionIndex($query)
    {                      
        $items = Yii::app()->db->createCommand("
            SELECT t.* FROM (
                SELECT title, text FROM {{new}} UNION 
                SELECT title, text FROM {{post}} UNION 
                SELECT title, text FROM {{page}}
            ) AS t WHERE t.title LIKE :query OR t.text LIKE :query
        ")->queryAll(true, array(
            ':query'=>'%' . $query . '%',
        ));       
 
        $dataProvider = new CArrayDataProvider($items, array(
            'pagination'=>array(
                'pageSize'=>self::ITEMS_PER_PAGE,
            )
        ));
 
        $this->render('index', array(
            'dataProvider'=>$sqlDataProvider,
            'query'=>$query,
        ));
    }
}

Это не очень оптимальный вариант, так как если найдётся тысяча записей, то в массиве окажутся они все. Для избежания такой растраты памяти можно перейти к использованию CSqlDataProvider, передав ему непосредственно сам SQL-запрос и число элементов:

class SearchController extends CController
{
    const ITEMS_PER_PAGE = 10;
 
    public function actionIndex($query)
    {
        $from = "(
            SELECT CONCAT('new_', id) AS id, title, text FROM {{new}} UNION
            SELECT CONCAT('post_', id) AS id, title, text FROM {{post}} UNION
            SELECT CONCAT('page_', id) AS id, title, text FROM {{page}}
        )";        
        $where = 'WHERE t.title LIKE :query OR t.text LIKE :query';        
        $params = array(
            ':query'=>'%' . $query . '%',
        );
 
        $countSql = 'SELECT COUNT(*) FROM ' . $from . ' AS t ' . $where;
        $dataSql = 'SELECT t.* FROM ' . $from . ' AS t ' . $where;
 
        $count = Yii::app()->db->createCommand($countSql)->queryScalar($params);
        $sqlDataProvider = new CSqlDataProvider($dataSql, array(
            'params'=>$params,
            'keyField'=>'id',
            'totalItemCount'=>$count,
            'pagination'=>array(
                'pageSize'=>self::ITEMS_PER_PAGE,
            ),
        ));
 
        $this->render('index', array(
            'dataProvider'=>$sqlDataProvider,
            'query'=>$query,
        ));
    }
}

Здесь мы для удобства разделили запросы на части $from и $where.

Класс CArrayDataProvider сам произведёт установку параметров LIMIT и OFFSET для разбивки на страницы. Этому компоненту нужно указать ключевое поле, но значения id из разных таблиц будут повторяться. Поэтому мы схитрили и собрали это поле динамически (добавив префиксы):

SELECT CONCAT('new_', id) AS id, ...
SELECT CONCAT('post_', id) AS id, ...
SELECT CONCAT('page_', id) AS id, ...

Теперь этот CSqlDataProvider можно использовать привычным способом как и CActiveDataProvider в представлении views/search/index.php:

<?php
<h1>Поиск по запросу <?php echo CHtml::encode($query); ?></h1>
 
<?php $this->widget('zii.widgets.CListView', array(
    'dataProvider'=>$dataProvider,
    'itemView'=>'_item',
)); ?>

Но выводить элементы в представлении views/search/_item.php нужно с той лишь разницей, что у нас каждая запись представляет из себя ассоциативный массив, а не объект:

<?php
<h2><?php echo CHtml::encode($data['title']; ?></h2>
<p><?php echo mb_substr(strip_tags($data['text']), 200, 'UTF-8'); ?></p>

Здесь мы ограничились выводом в представлении первых 200 символов текста. При желании можно придумать подсветку найденного слова. Например так:

class SearchHighlighter
{
    public static function getFragment($text, $word){
        if ($word)
        {
            $pos = max(mb_stripos($text, $word, null, 'UTF-8') - 100, 0);
            $fragment = mb_substr($text, $pos, 200, 'UTF-8');
            $highlighted = str_ireplace($word, '<mark>' . $word . '</mark>', $fragment);
        } else {
            $highlighted = mb_substr($text, 0, 200, 'UTF-8');
        }
        return $highlighted;
    }
}
<?php
<h1>Поиск по запросу <?php echo CHtml::encode($query); ?></h1>
 
<?php $this->widget( 'zii.widgets.CListView', array(
    'dataProvider'=>$dataProvider,
    'itemView'=>'_view',
    'viewData'=> array('query'=>$query),
)); ?>
<?php
<h2><?php echo CHtml::encode($data['title']; ?></h2>
<p><?php echo SearchHighlighter::getFragment(strip_tags($data['text']), $query); ?></p>

Наш поиск уже должен работать. Осталось рассмотреть ещё пару нюансов.

Добавление ссылки на материал

C выводом заголовка и фрагмента текста в ленте результатов поиска нужно выводить ссылку на источник.

В простейшем случае адреса можно генерировать используя конкатенацию прямо в запросе:

SELECT t.* FROM (
    SELECT title, text, CONCAT('/news/', id) AS url FROM {{new}} UNION 
    SELECT title, text, CONCAT('/blog/post/', id) AS url FROM {{post}} UNION 
    SELECT title, text, CONCAT('/page/', alias) AS url FROM {{page}}
) ...

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

SELECT t.* FROM (
    SELECT CONCAT('new_', id) AS id, title, text, CONCAT('/news/', id) AS url FROM {{new}} UNION 
    SELECT CONCAT('post_', p.id) AS id, p.title AS title, p.text_purified AS text, CONCAT('/blog/', c.alias, '/' , p.id) AS url FROM {{post}} AS p LEFT JOIN {{category}} AS c ON p.category_id = c.id UNION
    SELECT CONCAT('page_', id) AS id, title, text, CONCAT('/page/', alias) AS url FROM {{page}}
) ...

Теперь для вывода ссылки можно использовать значение псевдополя $data['url']:

<?php
<h2><?php echo CHtml::link(CHtml::encode($data['title']), $data['url']); ?></h2>

Этот подход подойдёт практически для всех случаев, за исключением многоуровневых категорий, вложенных страниц и прочих нестандартных реализаций ЧПУ. Этого мы коснёмся вдальнейшем. А сейчас попробуем упростить наши результирующие запросы.

Использование представлений в БД

При работе с СУБД на уроках информатики ученикам даётся задание создать несколько таблиц, а потом на основе содержащихся в них данных сконструировать какие-либо представления. Это на самом деле виртуальные таблицы, которые не содержат своей информации, а выводят записи из других таблиц. Фактически, это именованный и сохранённый в БД отдельный SQL запрос. Этим инструментом мы и можем воспользоваться.

Первым делом, создадим в базе данных наше представление:

CREATE OR REPLACE VIEW tbl_view_search AS
SELECT CONCAT('new_', id) AS id, title, text, CONCAT('/news/', id) AS url FROM tbl_new UNION 
SELECT CONCAT('post_', id) AS id, title, text, CONCAT('/blog/post/', id) AS url FROM tbl_post UNION 
SELECT CONCAT('page_', id) AS id, title, text, CONCAT('/page/', alias) AS url FROM tbl_page;

Теперь мы можем делать выборку из него как из обычной таблицы:

SELECT * FROM tbl_view_search WHERE title LIKE '%Yii%' OR text LIKE '%Yii%';

Представление можно создать данным запросом вручную, либо в миграции или (для экспериментов) непосредственно в контроллере перед запросом:

Yii::app()->db->createCommand("
    CREATE OR REPLACE VIEW {{view_search}} AS
    SELECT CONCAT('new_', id) AS id, title, text, CONCAT('/news/', id) AS url FROM {{new}} UNION 
    SELECT CONCAT('post_', id) AS id, title, text, CONCAT('/blog/post/', id) AS url  FROM {{post}} UNION 
    SELECT CONCAT('page_', id) AS id, title, text, CONCAT('/page/', alias) AS url  FROM {{page}}
")->execute();

Теперь будем использовать это представление как таблицу для наших выборок:

$where = 't.title LIKE :query OR t.text LIKE :query';  
$countSql = 'SELECT COUNT(*) FROM {{view_search}} WHERE ' . $where;
$dataSql = 'SELECT * FROM {{view_search}} WHERE ' . $where;

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

Если Вы не любите использовать DAO или если ссылки в вашем проекте генерируются не очень банально (например, если проект многоязычный и нужно использовать указание языка в адресе), то можно воспользоваться достоинствами ActiveRecord.

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

Замечательной особенностью представлений в БД является то, что они воспринимаются внешним миром как таблицы. Соответственно, для работы с этой виртуальной таблицей как с реальной мы можем использовать модель CActiveRecord.

Например, если у нас есть представление:

CREATE OR REPLACE VIEW tbl_view_search AS
SELECT title, text, CONCAT('/news/', id) AS url FROM {{new}} UNION 
SELECT title, text, CONCAT('/blog/post/', id) AS url  FROM {{post}} UNION 
SELECT title, text, CONCAT('/page/', alias) AS url  FROM {{page}}

мы можем создать для него модель:

/**
 * @property string $title
 * @property string $text
 * @property string $url
 */
class Search extends CActiveRecord
{
    public static function model($className=__CLASS__)
    {
        return parent::model($className);
    }
 
    public function tableName()
    {
        return '{{view_search}}';
    }
}

Теперь в коде контроллера мы можем работать с этой моделью как с любой другой:

class SearchController extends Controller
{
    public function actionIndex($query)
    {
        $criteria = new CDbCriteria();
        $criteria->addSearchCondition('title', $query);
        $criteria->addSearchCondition('text', $query, true, 'OR');
 
        $dataProvider = new CActiveDataProvider('Search', array(
            'criteria'=>$criteria,
        ));
 
        $this->render('search', array(
            'dataProvider'=>$dataProvider,
            'query'=>$query,
        ));
    }
}

и также привычно выводить результаты поиска:

<?php
<h2><?php echo CHtml::link(CHtml::encode($data->title), $data->url); ?></h2>

Здесь, как и прежде, адреса собираются конкатенацией в самом запросе.

Построение нестандартных ссылок

Иногда конкатенации и подключения частей через JOIN запрос может не хватать. Например, при генерации ссылок на вложенные страницы.

Хорошей практикой является добавление метода getUrl() в модель. Это позволяет просто использовать везде где нужно $model->url вместо громоздкой записи Yii::app()->createUrl(...) с различными для каждой сущности параметрами.

Добавим этот метод в наши модели:

class News extends CAtiveRecord
{
    ...
 
    private $_url;
 
    public function getUrl(){
        if ($this->_url === null)
            $this->_url = Yii::app()->createUrl('news/view', array('id'=>$this->id, 'alias'=>$this->alias));
        return $this->_url;
    }
}
 
class Post extends CAtiveRecord
{
    ...
 
    private $_url;
 
    public function getUrl(){
        if ($this->_url === null)
            $this->_url = Yii::app()->createUrl('blog/view', array('id'=>$this->id, 'alias'=>$this->alias));
        return $this->_url;
    }
}
 
class Page extends CAtiveRecord
{
    ...
 
    private $_url;
 
    public function getUrl()
    {
        if ($this->_url === null)
        {
            $this->_url = Yii::app()->createUrl('page/view', array('path'=>$this->getPath()));
        }
        return $this->_url;
    }
 
    public function getPath()
    {
        ...
    }    
}

Модель страницы отличается от моделей записи блога и новости тем, что содержит метод getPath, который склеивает вложенный псевдоним (например about/company/personal). А потом уже модель строит адрес на основе этого псевдонима.

Теперь для генерации ссылок для наших результатов поиска нужно использовать метод getUrl() соответствующей модели.

Изменим наше представление следующим образом:

CREATE OR REPLACE VIEW tbl_view_search AS
SELECT title, text, id AS material_id, 'News' AS material_class FROM tbl_new UNION 
SELECT title, text, id AS material_id, 'Post' AS material_class FROM tbl_post UNION 
SELECT title, text, id AS material_id, 'Page' AS material_class FROM tbl_page;

Представление будет возвращать идентификатор material_id и класс material_class, по которым можно будет найти оригинал модели. Если необходимо ипользовать Yii::import, то путь для него тоже можно возвращать из выборки:

CREATE OR REPLACE VIEW tbl_view_search AS
SELECT title, text, id AS material_id, 'news.models.News' AS material_import, 'News' AS material_class FROM tbl_new UNION 
SELECT title, text_purified AS text, id AS material_id, 'blog.models.Post' AS material_import, 'Post' AS material_class FROM tbl_post UNION 
SELECT title, text, id AS material_id, '' AS material_import, 'Page' AS material_class FROM tbl_page UNION
SELECT title, '' AS text, id AS material_id, 'photo.models.Photo' AS material_import, 'Photo' AS material_class FROM tbl_photo;

Здесь мы также добавили поиск по фотографиям. У них есть только заголовок и нет текста, поэтому в поле text мы возвращаем пустую строку. Аналогично можно легко добавить в поиск любую сущность.

Теперь необходимо доработать нашу модель поиска так, чтобы по свойству $model->material можно было бы получить доступ к оригинальной модели практически как через ленивую загрузку через отношение BELONGS_TO:

/**
 * @property string $title
 * @property string $text
 * @property string $material_import
 * @property string $material_class
 * @property integer $material_id
 */
class Search extends CActiveRecord
{
    public static function model($className=__CLASS__)
    {
        return parent::model($className);
    }
 
    public function tableName()
    {
        return '{{view_search}}';
    }
 
    private $_material;
 
    public function getMaterial()
    {
        if ($this->_material === null){
            if ($this->material_import){
                Yii::import($this->material_import);
            }   
            $this->_material = CActiveRecord::model($this->material_class)->findByPk($this->material_id);
        }
 
        return $this->_material;
    }
}

Контроллер мы оставим без изменений:

class SearchController extends Controller
{
    public function actionIndex($query)
    {
        $criteria = new CDbCriteria();
        $criteria->addSearchCondition('title', $query);
        $criteria->addSearchCondition('text', $query, true, 'OR');
 
        $dataProvider = new CActiveDataProvider('Search', array(
            'criteria'=>$criteria,
        ));
 
        $this->render('search', array(
            'dataProvider'=>$dataProvider,
            'query'=>$query,
        ));
    }
}

При выводе списка результатов мы теперь можем вызывать метод getUrl() соответствующей модели, к экземпляру которой мы можем обращаться через отношение getMaterial() нашей модели Search:

<?php
<h2><?php echo CHtml::link(CHtml::encode($data->title), $data->material->url); ?></h2>

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

Непосредственное получение экземпляров моделей

В последнем примере оригинальную запись можно получить через отношение $data->material. Но существует возможность возвращать оригинальные модели сразу в выборке, то есть оригинал будет содержаться вместо экземпляра класса Search прямо в переменной $data.

Этого можно добиться переопределив метод создания экземпляра модели CActiveRecord::instantiate:

/**
 * @property string $title
 * @property string $text
 * @property string $material_import
 * @property string $material_class
 * @property integer $material_id
 */
class Search extends CActiveRecord
{
    public static function model($className=__CLASS__)
    {
        return parent::model($className);
    }
 
    public function tableName()
    {
        return '{{view_search}}';
    }
 
    protected function instantiate($attributes)
    {
        if ($this->material_import){
            Yii::import($this->material_import);
        }
        return CActiveRecord::model($this->material_class)->findByPk($this->material_id);
    }
}

Данный метод будет вызываться при создании элементов в поисковых выборках find(), findByAttributes(), findAll() и findAllByAttributes(). Вместо экземпляра класса Search мы возвращаем экземпляр оригинального класса.

Определяя оригинальный класс элемента с помощью instanceof в _view.php мы можем подключать соответствующее типу материала представление:

<?php
<?php 
if ($data instanceof Page)) {
    $view = 'application.modules.page.views.default._view';
} elseif ($data instanceof News)) {
    $view = 'application.modules.new.views.default._view';
} elseif ($data instanceof BlogPost)) {
    $view = 'application.modules.blog.views.default._view';
} else {
    $view = '_default_view';
}
?>
 
<?php $this->renderPartial($view,  array(
    'data'=>$data, 
    'index'=>$index,
    'widget'=>$widget,
)); ?>

Теперь все найденные материалы будут выведены в одной ленте, причем каждый будет выведен в своём персональном оформлении. Для подключения новых сущностей достаточно лишь изменить SQL запрос в БД и привязать сооответствующие шаблоны.

Подобный трюк с использованием представления в базе данных и с переопределённым методом CActiveRecord::instantiate, привязанной к данному представлению модели, можно использовать не только для поиска, но и, например, для агрегации материалов различных типов в одну ленту RSS с общей сортировкой по дате.

Ну и как было обещано, пробежимся по некоторым альтэрнативам.

Другие решения по организации поиска на сайте

Здесь мы затронули простейший вариант поиска с использованием оператора LIKE. Кроме него в MySQL можно использовать и другие операторы, но это требует использования движка MyISAM и обязательное построение индексов.

Более сложные варианты предполагают использование сторонних систем. Их отличает то, что они обеспечивают полнотекстовый поиск, то есть способны не обращать внимания на порядок слов в запросе и словоформы. С примерами их использования в Yii можно ознакомиться, например, здесь, здесь и здесь. У каждого из этих решений свои особенности работы и свой порядок интеграции в проект.

Комментарии

 

Евгений Швейн

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

Ответить

 

standalone

Искать средствами одного мускуля с помощью LIKE не целесообразно.

Ответить

 

Александр

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

Ответить

 

Дмитрий Елисеев

Может вскоре напишу. Хотя их прямо почти не использую. Только косвенно.

Ответить

 

seydamet

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

Ответить

 

Сергей – janaka.ru

Было бы неплохо отметить немного в статье почему именно UNION, по опыту могу сказать что он помогать будет до тех пор пока записей будет "не много". Я решал задачу иначе, написав поведение для сохранения/удаление модели, которое сохраняет/удаляет запись в отдельной таблице хранения ключевых слов, где происходит связка по названию модели и её первичному ключу

Ответить

 

Дмитрий Елисеев

Для больших объёмов уже LIKE или HAVING итак не подойдут, так что UNION сделает всё как наиболее простой способ без лишних телодвижений. А ваш подход с отдельной таблицей для поиска уже сродни ведению индексов в упомянутых Zend Lucene и Sphinx, то есть более серьёзный.

Ответить

 

Sanchezzzhak – vk.com

Годно до 10к страниц вполне нормально.

Ответить

 

Ринат

Хороший сайт и хороший материал! Было бы не плохо если бы описали способ работы с zend_search_lucene или настройка работы с помощью sphinx

Ответить

 

Дмитрий Елисеев

Можно попробовать:

SELECT CONCAT(smalltext, ' ', fulltext) AS text ...
Ответить

 

Илья Левцов

Добрый день! Очень понравилась статья. Расскажите пожалуйста как можно сделать фильтрацию по gridview с csqldataprovider. Не могу решить проблему уже третий день. Заранее спасибо!

Ответить

 

Дмитрий Елисеев

В крайнем случае можно поступить аналогичным образом: сохранить этот запрос в представление базы данных и обернуть ActiveRecord моделью. Тогда можно будет использовать CActiveDataProvider как обычно.

Ответить

 

Akulenok

Не понимаю как составить запрос. Хочу искать в блоге и в комментах
в блоге title, link, short
в комментах text
делаю так:

$from = '(
            SELECT id, title, link, CONCAT(short, full) AS text FROM post UNION
            SELECT id, text FROM comments
        )';               
$where = 'WHERE t.title LIKE :query OR t.text LIKE :query';
$params = array(
   ':query'=>'%' . $query . '%',
);

$countSql = 'SELECT COUNT(*) FROM ' . $from . ' AS t ' . $where;
$dataSql = 'SELECT t.* FROM ' . $from . ' AS t ' . $where;

CDbCommand не удалось исполнить SQL-запрос: SQLSTATE[21000]: Cardinality violation: 1222 The used SELECT statements have a different number of columns. The SQL statement executed was: SELECT COUNT(*) FROM (

Ответить

 

Akulenok

Все разобрался, спасибо за статью!!!

Ответить

 

white123 – nicoblog.ru

Есть ли исходный код?
Я начинающий, мне трудно понять как использовать код, напр. class SearchHighlighter (где создать файл и как использовать?).

Ответить

 

Дмитрий Елисеев

Создаёте в папке components, например.

Ответить

 

Андрей Лукьянов

На yii1.1.16 метод instantiate у меня не сработал... $this - пустая.

получилось так

    public function instantiate($attributes)
    {
        return self::model($attributes['material_class'])->findByPk($attributes['material_id']);
    }
Ответить

 

Almas

Bolshooooooooi rahmet, bratan!

Ответить

 

Sergalas

Получается представление предается стирать и записывать заново при каждом добавлении или обновлении Постов страниц и новостей?

Ответить

 

Дмитрий Елисеев

Представление - это всего лишь SQL-запрос. Данные будет всегда возвращать свежие. Перезаписывать нужно будет только для изменения самого запроса.

Ответить

 

Sergalas

то есть его достаточно вставить в экшен search и каждый раз он будет обновляться ?

Ответить

 

Sergalas

К чему вопрос когда я второй раз использую поисковой запрос вот такого плана

{
        Yii::$app->db->createCommand("CREATE  SQL SECURITY INVOKER VIEW fl_search(id,name,slug,created_at,description,country_title,year,nesting) AS
                SELECT id, name_film,slug_film, created_at, description_film,country_title,year, nesting FROM `film`
                UNION SELECT id, name_serial,slug_serial, created_at, description_serial,country_title,year, nesting FROM `serial`
                UNION SELECT id, name_mfilm,slug_mfilm, created_at, description_mfilm,country_title,year, nesting FROM `mfilm`
                ORDER BY created_at DESC ")->execute();
        $arr=array(" ",'-',':') ;
        $querys=str_replace($arr,'%',$query);
        $expression = new Expression('"%'.$querys.'%"');
        $search = Search::find()->where(['or',['like', 'name', $expression],['like', 'description', $query, ]]);
        $searchDataprovider = new ActiveDataProvider([
            'query' => $search,
            'pagination' => [
                'pageSize' => 15,
            ],
        ]);
        return $this->render('search', [
            'searchDataprovider' => $searchDataprovider,
            'vardump'   => $querys
        ]);
    }

выдает такую ошибку

SQLSTATE[42S01]: Base table or view already exists: 1050 Table 'fl_search' already exists
The SQL being executed was: CREATE SQL SECURITY INVOKER VIEW search(id,name,slug,created_at,description,country_title,year,nesting) AS
SELECT id, name_film,slug_film, created_at, description_film,country_title,year, nesting FROM `film`
UNION SELECT id, name_serial,slug_serial, created_at, description_serial,country_title,year, nesting FROM `serial`
UNION SELECT id, name_mfilm,slug_mfilm, created_at, description_mfilm,country_title,year, nesting FROM `mfilm`
ORDER BY created_at DESC 
Ответить

 

Дмитрий Елисеев

Используйте CREATE OR REPLACE VIEW.

Ответить

 

Виталий

Ув. Дмитрий,

А как адаптировать вашу статью под Yii2? А именно класс Search - метод ActiveRecord::model() не существует.

Ответить

 

Дмитрий Елисеев

Убрать этот метод. Использовать ::find().

Ответить

 

Виталий

Дмитрий, но метод ::find() применителен к определённой модели ActiveRecord, а вы здесь используете ::model() для того чтобы разложить их по полочкам.Как будет выглядеть аналог такого подхода для yii2? Может это слишком глупый вопрос, но я новичок в этом деле, и не могу понять как это реализовать.

Ответить

 

Дмитрий Елисеев

Вместо:

return CActiveRecord::model($this->material_class)
    ->findByPk($this->material_id);

будет:

$class = $this->material_class;
return $class::findOne($this->material_id);
Ответить

 

Виталий

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

Ответить

 

Sheroz – t.me

Использую yii2, и при обращение к модели получаю такую ошибку Using $this when not in object context.
Вот код модели поиска

<?php

namespace app\models;

use Yii;
use \yii\db\ActiveRecord;
use app\models\Person;

class Search extends ActiveRecord
{
	public function rules()
    {
        return [
            [['tj', 'ru'], 'safe'],
        ];
    }

    /**
     * @inheritdoc
     */
    public function scenarios()
    {
        // bypass scenarios() implementation in the parent class
        return Model::scenarios();
    }

	public static function tableName()
	{
		return 'view_search';
	}

    private $_material;

    /*
    public function getMaterial()
    {
        $class = $this->material_class;
        return $class::findOne($this->material_id);
    }
    */

    public static function instantiate($attributes)
    {
        return var_dump($this->material_class);
        $class = $this->material_class;
        return $class::findOne($this->material_id);
    }

	public function search($params)
    {
        $query = $this->find()
        ->where(['like', 'title', $params]);
        //->andWhere(['like', 'title_id', $params])
        //->where(['like', 'content_id', $params])
        //->andWhere(['like', 'ru', $params]);
        return $query;
    }
}
Ответить

 

Дмитрий Елисеев
$class = $attributes['material_class'];
Ответить

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

Войти | Завести аккаунт | Войти через


(никто не увидит)





Можно использовать теги <p> <ul> <li> <b> <i> <a> <pre>