Yii: Добавляем "Поиск по сайту", используя только Yii

Yii: Добавляем "Поиск по сайту", используя только Yii
Добавляем на наш сайт возможность поиска по материалам

Ранее мы добавили на наш сайт календарь на месяц по статьям. Теперь организуем один из самых важных компонентов любого сайта - механизм поиска статей (постов, материалов). Сейчас мы будем делать поиск по сайту только средствами Yii, также можно делать поиск с помощью библиотеки Zend_Search_Lucene -  об этом читайте в статье Yii: Делаем поиск по сайту, используя Zend Lucene.

Поехали. Традиционно начинаем с конца. Добавляем в шаблон вызов несуществующего ещё виджета (компонента) SiteSearch:

<?php $this->widget('SiteSearch'); ?>

Дальше создаём этот компонент protected/components/SiteSearch.php:

<?php

class SiteSearch extends CWidget 
{
	public function run()
	{
    	$form = new SiteSearchForm();
    	$this->render('siteSearch', array('form'=>$form));
  	}
}

Здесь есть обращение к форме и виду. Создаём вид /protected/components/views/siteSearch.php:

<div id="search">
    <div id="search_div">
    	<h3>Поиск по сайту</h3>
    	<?php $url = $this->getController()->createUrl('material/search'); ?>
		<?php echo CHtml::beginForm($url); ?>
		<div class="row">
		<?php echo CHtml::activeTextField($form,'string') ?>
		<?php echo CHtml::error($form,'string'); ?>
		<?php echo CHtml::SubmitButton('Поиск'); ?>
		</div>
		<?php echo CHtml::endForm(); ?>
    </div>
    <div id="SearchFooter"></div>
</div>

 

Теперь создадим модель формы поиска (в ней только одно поле) /protected/models/SiteSearchForm.php:

<?php

class SiteSearchForm extends CFormModel
{
  public $string;

  public function rules() {
    return array(array('string', 'required'));
  }

  public function safeAttributes() {
    return array('string',);
  }

}

 

 В контроллер с постами (статьями, материалами) добавляем ещё один метод:

public function actionSearch()
        {
                $search = new SiteSearchForm;
                
                if(isset($_POST['SiteSearchForm'])) {
                        $search->attributes = $_POST['SiteSearchForm'];
                        $_GET['searchString'] = $search->string;
                } else {
                        $search->string = $_GET['searchString'];
                }
                
                $criteria = new CDbCriteria(array(
                        'condition' => 'status='.Material::STATUS_PUBLISHED.' AND content LIKE :keyword',
                        'order' => 'create_time DESC',
                        'params' => array(
                                ':keyword' => '%'.$search->string.'%',
                        ),
                ));
                
                $materialCount = Material::model()->count($criteria);
                $pages = new CPagination($materialCount);
                $pages->pageSize = Yii::app()->params['materialsPerPage'];
                $pages->applyLimit($criteria);
                
                $materials = Material::model()->findAll($criteria);
                                                    
                $this->render('found',array(
                        'materials' => $materials,
                        'pages' => $pages,
                        'search' => $search,
                ));

        }

А также в нём разрешаем этот метод всем посетителям сайта:

public function accessRules()
	{
		return array(
			array('allow',  // allow all users to access 'index' and 'view' actions.
				'actions'=>array('index','view','postedinmonth','postedondate','search'),
				'users'=>array('*'),
			),
...

 

И вид /protected/views/found.php

<?php if(!empty($_GET['tag'])): ?>
<h1>Materials Tagged with <i><?php echo CHtml::encode($_GET['tag']); ?></i></h1>
<?php endif; ?>
<?php if(!empty($search->string)): ?>
<h1>Materials Searched by <i><?php echo CHtml::encode($search->string); ?></i></h1>
<?php endif; ?>

<?php foreach($materials as $material): ?>
<?php
        $pizza = explode('>', $material->content);
        $s = '';
        for ($i = 0; $i < count($pizza); $i++) {
                $piece = explode('<', $pizza[$i]);
                $replace = preg_replace('/('.$search->string.')/i', '<b><span style="background:yellow;">${1}</span></b>', $piece[0]);
                if (count($piece) == 2) {
                        $s .= $replace.'<'.$piece[1].'>';
                } else if (count($piece) == 1) {
                        $s .= $replace;
                }
        }
        $material->content = $s;

        $this->renderPartial('_view',array(
                'data'=>$material,
        ));
?>
<?php endforeach; ?>

<br/>
<?php $this->widget('CLinkPager',array('pages'=>$pages)); ?>

После введения запроса и нажатия кнопки Поиск, посетитель переводится на url - /material/search

 

Источник: loco.ru

almix
Разработчик Loco, автор статей по веб-разработке на Yii, CodeIgniter, MODx и прочих инструментах. Создатель Team Sense.

Вы можете почитать все статьи от almix'а.



Другие статьи по этой теме:

Комментарии (24)     Подпишитесь на RSS комментариев к этой статье.

24 комментариев

#141
Dims говорит:
November 28, 2011 at 10:28 am
Спасибо. Отличная статья. Как раз сейчас начал писать поиск и очень помогло.
#400
Ибрагим говорит:
March 27, 2012 at 01:58 am

В самом начале написано "Добавляем в шаблон вызов несуществующего ещё виджета (компонента) SiteSearch: <?php $this->widget('SiteSearch'); ?> " В какой именно файл мне нужно вставить эту строчку кода?

almix: в шаблон, в layouts какой-нибудь, у меня это column3.php

#446
Артем говорит:
May 3, 2012 at 09:14 pm
сделал по примеру, все понятно, но возникает проблема - не работает пагинатор! То есть страницы выводятся, и их нужное количество, но выводятся все материалы. При переходе на страницы выводятся все материалы, они не пагинируются :( В чем может быть проблема?
#454
almix говорит:
May 10, 2012 at 01:43 pm

Артём, вы точно задали $pages->pageSize = Yii::app()->params['materialsPerPage']; ?

materialsPerPage устанавливаю в config/params

#465
sega говорит:
May 21, 2012 at 08:46 am
Спасибо, хорошая статья, замиксовал под свои нужды + добавил на длину строки запроса и уведомление, если ничего не найдено
#466
almix говорит:
May 21, 2012 at 11:26 am
Отлично sega, поделитесь своей доработкой. Будем признательны!
#468
sega говорит:
May 22, 2012 at 12:40 pm

В экшне Search добавил ограничение на длину строки запроса. Значение указал в params.

if (strlen($search->string) < Yii::app()->params->minSearchStringLength) {

                    $this->render('invalidRequest');
                    return;

}

Ну и виде при нулевом результате:

<?php if (!$materials) {
    echo "Ничего не найдено";
} ?>

#473
almix говорит:
May 24, 2012 at 09:58 am

Спасибо! Сделал без создания доп. вида invalidRequest и использовал mb_strlen вместо strlen, а то русские символы не правильно по длине ограничиваются:

<?php if (mb_strlen($search->string, 'UTF-8') < Yii::app()->params->minSearchStringLength) {
                    echo "Слишком короткий запрос. Надо не меньше ".Yii::app()->params->minSearchStringLength." символов.";
                    return;
}?>
<?php if (!$materials) {
    echo "Ничего не найдено.";
} ?>
#477
sega говорит:
May 31, 2012 at 11:49 am
Спасибо. Не знал про mb_strlen. У себя тоже поправил
#524
Лёня говорит:
June 26, 2012 at 01:48 pm
Поиск отлично работает, ищет по содержанию. Подскажите как сделать поиск по заголовку?
#609
Сергей говорит:
August 17, 2012 at 12:52 am
Спасибо за статью. Беда с ie8 - любой запрос в поиск и ie выдает "веб страница устарела". в основных браузерах всё ок. Сталкивались?
#611
farw говорит:
August 18, 2012 at 01:19 am
Зачем явно проверять вводимую строку на длину, если можно это сделать через метод rules в моделе SiteSearchForm?
#619
almix говорит:
August 21, 2012 at 10:27 am

Лёня -> контроллер actionSearch, допишите в запросе

'condition' => 'status='.Material::STATUS_PUBLISHED.' AND content LIKE :keyword', что-то типа 'OR title LIKE :keyword'

#703
Олег говорит:
November 16, 2012 at 04:05 pm
спасибо за статью! А как получив результат, перейти на то что  выдал поиск? В базе хранить link  страницы и вытягивать вместе с результатом? 
#704
Олег говорит:
November 16, 2012 at 04:15 pm
Пока предыдущий коммент не опубликован рискну написать. Необходимо возможно использовать id статьи и передавать  ее GET параметром
#709
almix говорит:
November 20, 2012 at 02:07 pm
Олег, поиск выдаёт с заголовком-ссылкой статью. Да, для линка, всё то же самое, как и для вывода списка статей на главной или в разделе "Статьи". Это url самой статьи.
#766
Satana говорит:
January 25, 2013 at 04:51 pm

Сделал все по статье.

При вставке в лейаут который рендерю в main перестает работать(запрос отправляется по другому адресу). 

В чем может быть проблема?

#773
almix говорит:
February 6, 2013 at 10:48 pm
в адресе видимо. Куда запрос уходит? Может метод не так называли или его нет? search
#791
melomaniac говорит:
March 5, 2013 at 01:32 am
Добрый вечер, подскажите пожалуйста, выдает ошибку - Контроллер TestController не может найти представление "found".
#800
almix говорит:
March 10, 2013 at 09:51 pm
melomaniac, вы должно быть не создали представление found.php, вот контроллер его и не находит? Рассмотрю в курсе по Yii подробнее на примере скоро.
#868
Roman говорит:
May 10, 2013 at 04:26 pm

Работает всё хорошо, но а если понадобится сделать поиск не по одной модели, а по всему сайту, а там же ведь есть и не связанные таблицы. К примеру как найти в этих таблицах:

$materials = User::model()->findAll($criteria);
$materials = School::model()->findAll($criteria);
$materials = Tutor::model()->findAll($criteria);

#874
MoKo говорит:
May 17, 2013 at 04:50 pm

PHP warning

include(Material.php): failed to open stream: No such file or directory


privet, atkuda nayti etot fayl..? 

#886
almix говорит:
May 30, 2013 at 02:13 am
MoKo, это в моделях models. Типа как Post.php, только у меня Material.php
#913
ElisDN говорит:
July 10, 2013 at 11:04 pm

Вставлять $search->string прямо в preg_replace - не очень-то хорошая идея.

P.S. Это будет поиск по одному разделу (только по Material), но почти таким же образом можно сделать поиск по всему сайту.