RSS-подписка

RSS-лента

Новые статьи

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


Получать обновления на эл. почту

Ваш e-mail:

Рассылка новостей от Loco

Yii: Делаем поиск по сайту, используя Zend Lucene

Yii: Делаем поиск по сайту, используя Zend Lucene Внедряем в свой сайт на Yii framework возможность поиска по записям из базы данных, используя библиотеку Zend_Search_Lucene из стороннего PHP фреймворка Zend. Кто боится сочетания Yii  и Zend, напрасно - хорошие продукты не имеют границ.

Ожнажды мы делали поиск по сайту, используя только средства самого Yii (делая выборку данных из базы данных запросом со сравнением like). Этот способ прекрасно работает на сайте loco.ru, можно тут и опробовать его. Почитайте нашу статью о поиске по сайту на Yii, чтобы сейчас было проще. Идея та же, только теперь мы задействуем мощности библиотека Zend_Search_Lucene из фреймворка Zend.

(Для версии Yii2 смотрите Поиск по сайту с Zend Lucene для Yii2)

1. Скачиваем на официальном сайте архив фреймворка Zend.

2. Берём оттуда library/Zend/Search и копируем в vendors/Zend, также копируем library/Zend/Exception.php туда же. Если у вас ещё нет папки vendors, создайте. Мы завели её, чтобы отличать сторонние решения от самого Yii. Zend - сторонний "поставщик" библиотеки Zend_Search_Lucene в нашем случае. Посмотрите что получилось:

поиск на сайте с Yii, используя Zend Lucene
Структура папок в Yii с Zend Lucene.

3. Создайте в папке protected/runtime директорию search с правами на запись. Zend будет туда сохранять индекс страниц для результатов поиска. Ему можно.

4. В components создаём SearchBlock.php, который дальше поместим где захотим.

<?php

Yii::import('zii.widgets.CPortlet');

class SearchBlock extends CPortlet
{
public $title='Поиск';

protected function renderContent()
  {
echo CHtml::beginForm(array('search/search'), 'get', array('style'=> 'inline')) .
      CHtml::textField('q', '', array('placeholder'=> 'search...','style'=>'width:140px;')) .
      CHtml::submitButton('Go!',array('style'=>'width:30px;')) .
      CHtml::endForm('');
  }
}

5. Вызовем этот виджет в шаблоне (например в правой колонке на главной в самом верху),  вставляя код:

<?php
      $this->widget('SearchBlock', array(
      )); 
?>

Вот что получилось на вид:

поиск с помощью Zend Lucene

6. Теперь самое главное, мозг "поиска...", создаём контроллер protected/controllers/SearchController.php:

<?php

class SearchController extends Controller
{
    /**
     * @var string index dir as alias path from <b>application.</b>  , default to <b>runtime.search</b>
     */
    private $_indexFiles = 'runtime.search';
    /**
     * (non-PHPdoc)
     * @see CController::init()
     */
    public function init(){
        Yii::import('application.vendors.*');
        require_once('Zend/Search/Lucene.php');
        parent::init(); 
    }

    /**
     * Search index creation
     */
    public function actionCreate()
    {
        setlocale(LC_CTYPE, 'ru_RU.UTF-8');
        Zend_Search_Lucene_Analysis_Analyzer::setDefault(
     new Zend_Search_Lucene_Analysis_Analyzer_Common_Utf8());       

        $index = new Zend_Search_Lucene(Yii::getPathOfAlias('application.' . $this->_indexFiles), true);

        $posts = Place::model()->findAll();
        foreach($posts as $post){
            $doc = new Zend_Search_Lucene_Document();
            $doc->addField(Zend_Search_Lucene_Field::Text('title',
                                          CHtml::encode($post->name), 'UTF-8')
            );

            $doc->addField(Zend_Search_Lucene_Field::Text('link',
                                            "/place/".$post->id."-".CHtml::encode($post->alias)
                                                , 'UTF-8')
            );   

  $content = $this->clean_content($post->description);
            $doc->addField(Zend_Search_Lucene_Field::Text('content',
                                          $content
                                         , 'UTF-8')
            );

            $index->addDocument($doc);
        }
        $index->optimize();
        $index->commit();
        echo 'Lucene index создан успешно';
    }

    public function actionSearch()
    {
         setlocale(LC_CTYPE, 'ru_RU.UTF-8');
         Zend_Search_Lucene_Analysis_Analyzer::setDefault(
          new Zend_Search_Lucene_Analysis_Analyzer_Common_Utf8());

        $this->layout='column2';
         if (($term = Yii::app()->getRequest()->getParam('q', null)) !== null) {
            $index = new Zend_Search_Lucene(Yii::getPathOfAlias('application.' . $this->_indexFiles));
            $results = $index->find($term);
            $query = Zend_Search_Lucene_Search_QueryParser::parse($term);      


            $this->render('search', compact('results', 'term', 'query'));
        }
    }
    
    // Function for returning a preview of the content:
// The preview is the first XXX characters.
private function preview_content($data, $limit = 400) {
   return substr($data, 0, $limit) . '...';
} // End of preview_content() function.
// Function for stripping junk out of content:
private function clean_content($data) {
   return strip_tags($data);
}
}

 

7. Создаём представление views/search/search.php


<?php
	$this->pageTitle='Результаты поиска -' .Yii::app()->name;
	$this->breadcrumbs=array(
	    'Результаты поиска по запросу: '. CHtml::encode($term),
	);
	?>
	 
	<h3>Результаты поиска по запросу: "<?php echo CHtml::encode($term); ?>"</h3>
	<?php if (!empty($results)): ?>
	                <?php foreach($results as $result): 
	?>                  
	                    <h2><?php echo CHtml::link($result->title, CHtml::encode($result->link)); ?></h2>
	                    <p><?php echo $query->highlightMatches($result->content); ?></p>
	                    <hr/>
	                <?php endforeach; ?>
	 
	            <?php else: ?>
	                <p class="error">Поиск не дал результатов.</p>
	            <?php endif; ?>

 

8. Дальше по идее всё. Надо сначала набрать в строке браузера: http://домен/search/create, чтобы Lucene создал индекс для поиска в папку runtime. Дальше это можно иногда снова выполнять, когда новые материалы добаляются, то есть обновляйте индекс время от времени, либо в КРОН поместите по расписанию. Может возникать ошибка с iconv() из за русской кодировки, но когда мы добавили в контроллерах строки

setlocale(LC_CTYPE, 'ru_RU.UTF-8');
         Zend_Search_Lucene_Analysis_Analyzer::setDefault(
          new Zend_Search_Lucene_Analysis_Analyzer_Common_Utf8());

проблема ушла. Почитайте о проблеме с русским языком и iconv в  Zend Lucene.

В работе поиск можно посмотреть на whytravel.ru:

поиск по сайту с Zend Lucene

 

Также полезно почитать:

 

А для версии Yii2 смотрите Поиск по сайту с Zend Lucene для Yii2

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

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

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



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

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

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

#484
BS говорит:
June 4, 2012 at 07:26 pm
Спасибо, будет полезно :), найду время опробовать. не хватает ещё сфинкса :)
#486
almix говорит:
June 5, 2012 at 04:58 pm
Sphinx не поставить на виртуальном сервере. Там нужен выделенный, это как ПО устанавливать на сервере. Вроде так.
#487
BS говорит:
June 5, 2012 at 06:01 pm
Я знаю.
#493
Кирилл говорит:
June 8, 2012 at 01:45 pm

У меня вот такая ошибка вылетает при http://домен/search/create! Help!

Warning: include(Place.php) [function.include]: failed to open stream: No such file or directory in W:\home\domen\www\yii-v1.1.9\framework\YiiBase.php on line 418

Warning: include() [function.include]: Failed opening 'Place.php' for inclusion (include_path='.;W:\home\domen\www\protected\vendors;W:\home\domen\www\protected\components;W:\home\domen\www\protected\models;/usr/local/php5/PEAR') in W:\home\domen\www\yii-v1.1.9\framework\YiiBase.phpon line 418

Fatal error: Class 'Place' not found in W:\home\domen\www\protected\controllers\SearchController.php on line 30

З.Ы. На денвере (на локальном сервере) лежи сайтик.

#520
fantomas говорит:
June 24, 2012 at 06:45 am
А что в модели "
Place
" должно быть?
#521
almix говорит:
June 25, 2012 at 10:51 am
Place - это модель с данными о местах (связывается с tbl_place), устроена аналогично Post. Вы можете использовать Post, тогда и поиск будет по записям.
#583
norgen говорит:
July 25, 2012 at 12:37 pm

Аффтар, ты текст писал по памяти чтоли?Громко смеяться

Написано:

 $index = new Zend_Search_Lucene(Yii::getPathOfAlias('application.' . $this->_indexFiles));
$results = $index->find($term);
$query = Zend_Search_Lucene_Search_QueryParser::parse($term);

Должно быть:

 $index = new Zend_Search_Lucene(Yii::getPathOfAlias('application.' . $this->_indexFiles));
$query = Zend_Search_Lucene_Search_QueryParser::parse($term);
$results = $index->find($query);


#602
almix говорит:
August 2, 2012 at 01:43 pm
norgen, нет текст из файла копирую. У меня работает именно так как написал. А в чём разница видна?
#606
George говорит:
August 10, 2012 at 05:15 pm

Здравствуйте. очень надеюсь на вашу помощь ибо сделал все что мог. проблема при создании индекса. Дело в том что локально у меня все работало, а как залил на сервер появилась такая пролема. При том что файлы индекса прекрасно создаются (права на папку 777). но во время их создания не рендерится надпись что индекс создан успешно, вместо этого 504 ошибка через некоторое время. Вот что мне выдается если после строки $index->addDocument($doc); вставить print_r($doc); exit; 

Zend_Search_Lucene_Document Object ( [_fields:protected] => Array ( [short] => Zend_Search_Lucene_Field Object ( [name] => short [value] => Требуемый контент тут [isStored] => 1 [isIndexed] => 1 [isTokenized] => 1 [isBinary] => [storeTermVector] => [boost] => 1 [encoding] => UTF-8 ) [full] => Zend_Search_Lucene_Field Object ( [name] => full [value] => totally [isStored] => 1 [isIndexed] => 1 [isTokenized] => 1 [isBinary] => [storeTermVector] => [boost] => 1 [encoding] => UTF-8 ) ) [boost] => 1 ) 
Fatal error: Exception thrown without a stack frame in Unknown on line 0


заранее благодарю за помощь. 

#617
almix говорит:
August 21, 2012 at 10:07 am
Попробуйте удалить индекс из папки runtime, если получится. Заново создать.
#700
TPOJIb говорит:
November 15, 2012 at 01:08 am

Прошел по ссылке на сайт http://whytravel.ru/ дабы проверить поиск. и на все мои запросы мне выдается ответ что:
Поиск не дал результатов.

Вводил слова: Германия, Дубай, ОАЭ и ....

Так этот поиск работает или нет?

#702
almix говорит:
November 15, 2012 at 04:03 pm
TPOJIb, на whytravel поиск ищет только по местам (он на контроллер мест "повешен") и не ищет по странам и городам.
#731
Lev говорит:
December 21, 2012 at 08:47 pm

Беда!

Делаю все так же на yupe yii.
Выдает ошибку:

Index doesn't exists in the specified directory.

www\protected\vendors\Zend\Search\Lucene.php(538)

В папке runtime/search создается 1 пустой файл read.lock.file

Что делать?

#762
Евгений говорит:
January 14, 2013 at 10:19 pm

скачал с сайта Zend

но в указанном месте нет папки Search

2. Берём оттуда library/Zend/Search

её убрали из фреймворка?

#774
almix говорит:
February 6, 2013 at 11:00 pm
Евгений, Lucene теперь в ZendSearch лежит – https://github.com/zendframework/ZendSearch
#804
kropaman говорит:
March 18, 2013 at 10:05 pm

Тут вопрос насчет пагинаци, можно решить с помоще CArrayDataProvider:

/* actionSearch*/

.........................................

if (($term = Yii::app()->getRequest()->getQuery('q')) !== null) {
            $index = new Zend_Search_Lucene(Yii::getPathOfAlias('application.' . $this->_indexFiles));
            $results = new CArrayDataProvider($index->find($term), array(
                        'id' => 'search',
                        'pagination' => array(
                            'pageVar' => 'page',
                       //     'pageSize'=>1,
                        ),
                        'sort' => false,
                    ));
            $query = Zend_Search_Lucene_Search_QueryParser::parse($term);
            $this->render('search', compact('results', 'term', 'query'));

и

/*views/search/search*/

......................................

    <?php if ($results->itemCount): ?>
        <?php foreach ($results->data as $result):
            ?>                  
            <h2><?php echo CHtml::link(CHtml::encode($result->title), CHtml::encode($result->link)); ?></h2>
            <p><?php echo $query->highlightMatches($result->content); ?></p>
            <hr/>
        <?php endforeach; ?>
    <?php else: ?>
        <div class="alert alert-error">Пошук не дав результатів.</div>
    <?php endif; ?>
        <div class="pagination pagination-centered">
            <?php $this->widget('CLinkPager', array('pages' => $results->pagination)); ?>
        </div>

#850
Павел говорит:
April 12, 2013 at 05:05 pm
Поиск хороший, ток подсветку лучше самому написать заодно фильтрацию проработать на спец. символы и правила вывода
#1065
sancho говорит:
January 10, 2014 at 02:19 pm

Делаю все как написано. Уперся в проблему "include(Post.php): failed to open stream: No such file or directory" 

Что я делаю не так?

#1079
Serus говорит:
February 2, 2014 at 10:20 pm
a как сделать так, чтоб результат выводился не на пустой странице, а в странице пределах контента сайта, с оформлением в общем так же как и на whytravel??
#1085
almix говорит:
February 7, 2014 at 01:21 pm
Serius, это значит у вас шаблон не подключился, в контроллере написано render('search'...), он должен быть вложен в layout какой-то.