Реалізація пошуку по сайту на Yii Framework 1.1
Пошукова видача може бути нерівномірною: категорії, товари і статті повинні мати різне представлення.
Доволі часто виникає необхідність реалізувати пошук по сайту, але при цьому структура даних нерівномірна. Наприклад, на корпоративному сайті клієнт хоче шукати по каталогу товарів (серед самих категорій та окремо по товарах), по статтях і по статистичних сторінках. Рішення повинно бути зручно розширюваним, а алгоритм зрозумілим навіть через півроку.
p>Перший, очевидний варіант — скласти великий SQL-запит, та оперувати різними UNION'ами. Але цей варіант гарний, якщо ми можемо обрати однакові поля з кожної із сутностей. У робочому проекті цього нам ніхто гарантувати не зможе. Отже, запит до бази будемо використовувати тільки для отримання даних, а усі маніпуляції з пошуком будемо виконувати за допомогою PHP.
Налаштування моделей ActiveRecord
Прагнучи зберегти логіку додатку, для вибірки моделі за умовою пошуку будемо використовувати окремий метод AR-моделі. Наприклад, назвемо его siteSearch. Він може виглядати наступним чином:
public function siteSearch($text) { $criteria=new CDbCriteria; $criteria->compare('field1',$text,true,'OR'); $criteria->compare('field2',$text,true,'OR'); $criteria->compare('field3',$text,true,'OR'); return new CActiveDataProvider($this, array( 'criteria'=>$criteria, 'pagination' => false, )); }
Для кожної ActiveRecord-моделі, яка буде включена в пошук, необхідно створити аналогічний метод. За допомогою нього ми зможемо отримати масив усіх даних, які задовольняють умовам пошуку.
Створення екшену для реалізації пошуку по багатьох моделях
Основним завданням екшену є отримання пошукового запиту, створення колекції моделей з даними, які задовольняють умовам пошуку, і структуризація цих даних для зручного відображення в представленні. Розглянемо варіант із двома моделями News і StaticPages.
public function actionSearch($text, $page = 1) { $output = 10; $pageSize = $output; $offset = ($page - 1)*$output; $news = News::model()->siteSearch($text); $staticPages = StaticPages::model()->siteSearch($text); $data = array($news, $staticPages); $count = 0; $j = 1; foreach ($data as $key => $material) { $count += $material->totalItemCount; $diff = $offset - $material->totalItemCount; if ($diff >= 0) { $data[$key] = ''; $offset = $diff; } else { $skip = $offset; $offset = 0; //Deleting $skip items from beginning $tmp_arr = array(); $tmp_arr = $material->getData(); $res_arr = array(); for ($i = $skip; $i<$material->totalItemCount; $i++) { $res_arr[] = $tmp_arr[$i]; } $data[$key]->setData($res_arr); $data[$key]->setTotalItemCount(count($res_arr)); $material->setData($res_arr); $d = $output - $material->totalItemCount; if ($d >= 0) { $output = $d; } else { //Delete all after $output items from $material $tmp_arr = $material->getData(); $res_arr = array(); for ($i = 0; $i<$output; $i++) { $res_arr[] = $tmp_arr[$i]; } $data[$key]->setData($res_arr); $output =0; } } $j++; } $news = $data[0]; $staticPages = $data[1]; $pages=new CPagination($count); // results per page $pages->pageSize=$pageSize; $pages->pageVar='page'; $pages->params = array('text'=>$text); $pages->route = '/site/search'; $this->render('search', array( 'text'=>$text, 'news'=>$news, 'staticPages'=>$staticPages, 'pages'=>$pages, )); }
Отже, ми передаємо в представлення текст запиту, усі моделі по черзі й об'єкт класу CPagination.
Оформлення представлення для результатів пошуку
У представленні search блок виведення результатів пошуку може виглядати так:
<p>Результаты поиска для «<?php echo CHtml::encode($text); ?>»:</p> <?php $arr = array('news', 'staticPages'); foreach ($arr as $key => $item) { if (empty($item)) unset($arr[$key]); } $arr = array_values($arr); foreach ($arr as $key => $controller) { $view_path = '/'.$controller.'/_search'; $this->widget('zii.widgets.CListView', array( 'dataProvider'=>$controller, 'itemView'=>$view_path, 'summaryText'=>false, 'emptyText'=>false, 'template'=>'{items}', )); } ?> <?php $this->widget('CLinkPager', array( 'pages' => $pages )) ?>
Для кожної моделі також необхідно створити унікальне представлення у пошуковому виданні, яке буде запитано з файлу _search. Це дозволяє оптимальним чином представити інформацію, в той же час розставивши акценти для більш важливих типів матеріалів і менш значних.
Представлення /news/_search для моделі News може виглядати приблизно так:
<div class="news_preview"> <?php echo CHtml::link($data->title, array('/news/view', 'id'=>$data->id)); ?> <p><?php echo strip_tags($data->annotation); ?></p> </div>
Підсумки
Що ж ми маємо врешті, використовуючи такий підхід? Із недоліків методу:
- необхідність вносити правки в екшен контролеру і представлення search при додаванні нових типів матеріалів
- при великих об'ємах даних запити до моделей будуть передавати усі дані, і виникне необхідність розрахунку обмежень
Із переваг підходу можна відмітити:
- універсальність у роботі з різними структурами даних
- окремі представлення для кожного типу матеріалу
- можливість сортування выбірки (може бути неефективно при великих об'ємах даних)
Практика демонструє, що підхід добре працює для більшості сайтів. Якщо пошук не є основним інструментом навігації і не буде створювати великого навантаження на сервер, використання описаного підходу може заощадити час як під час розробки, так і в процесі розвитку сайту, при додаванні нових типів матеріалів.