For this tutorial I will add a search to the blog demo. The search would be based on Zend Lucene.
Now go to your yii-1.1.8.r3324\demos\blog\protected directory and create there a vendors directory In vendors directory you should put Zend Framework library, For this you will need to download it After downloading copy the Zend directory that inside library (with Zend directory inside) to vendors Should look like this now (I copied only Search directory...):

add under runtime directory, a new one call it search, it will be used for the index files that Zend Lucene will create. Make sure it is writable!
For this we will create a widget or if to be more specific a CPortlet! The most simple way is go to your components directory, create SearchBlock.php file And copy inside code from tag cloud. change class name to SearchBlock. Now lets insert it ! Go to views/layouts/column2.php add this, abouve the TagCloud widget
$this->widget('SearchBlock', array( ));
after saving you will see 2 tag coulds in your main blog page... lets now edit it and change it to some search form
Now lets change the SearchBlog widget code...
Yii::import('zii.widgets.CPortlet'); class SearchBlock extends CPortlet { public $title='Search'; 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(''); } }
It will look like this:

Now lets add a SearchController
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(); } public function actionCreate() { } public function actionSearch() { } }
As you can see in the init method we import Zend from vendors and we require the Search Lucene
It has two actions Search and Create For this tutorial I don't need more... but you can add update for example to update the search index ( of course you should implement it) etc.
It is a time to mention the documentation for Zend Lucene Here To do more advanced stuff, you will need to learn it
Also the api for the Lucene class
This is an example of blog array after AR findAll
'id' => '2' 'title' => 'A Test Post' 'content' => 'a lot of text' 'tags' => 'test' 'status' => '2' 'create_time' => '1230952187' 'update_time' => '1230952187' 'author_id' => '1'
This is how we create the index:
/** * Search index creation */ public function actionCreate() { $index = new Zend_Search_Lucene(Yii::getPathOfAlias('application.' . $this->_indexFiles), true); $posts = Post::model()->findAll(); foreach($posts as $post){ $doc = new Zend_Search_Lucene_Document(); $doc->addField(Zend_Search_Lucene_Field::Text('title', CHtml::encode($post->title), 'utf-8') ); $doc->addField(Zend_Search_Lucene_Field::Text('link', CHtml::encode($post->url) , 'utf-8') ); $doc->addField(Zend_Search_Lucene_Field::Text('content', CHtml::encode($post->content) , 'utf-8') ); $index->addDocument($doc); } $index->commit(); echo 'Lucene index created'; }
First I found all the posts with Post::model()->findAll(); and than I added post to search index one by one, his content title and link fields
Now you should navigate to http://localhost/yii-1.1.8.r3324/demos/blog/index.php/search/create And you will see "Lucene index created"
public function actionSearch() { $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')); } }
Create views/search/search.php add there
$this->pageTitle=Yii::app()->name . ' - Search results'; $this->breadcrumbs=array( 'Search Results', ); <h3>Search Results for: "<?php echo CHtml::encode($term); ?>"</h3> <?php if (!empty($results)): <?php foreach($results as $result): <p>Title: <?php echo $query->highlightMatches(CHtml::encode($result->title)); </p> <p>Link: <?php echo CHtml::link($query->highlightMatches(CHtml::encode($result->link)), CHtml::encode($result->link)); </p> <p>Content: <?php echo $query->highlightMatches(CHtml::encode($result->content)); </p> <hr/> <?php endforeach; <?php else: <p class="error">No results matched your search terms.</p> <?php endif;
As you can see because I passed to the view the $query, I can use Zend's highlightMatches ...
(if your search results not in english, you might have some encoding issues with highlightMatches, so you might consider using your own highlighter for greater flexibility and encoding issues free)
Also I created a real link from the linked I passed and created via
$doc->addField(Zend_Search_Lucene_Field::Text('link' ...
And thats it try to search the word "it" And you will get:

Now we are done, we have our search working... As you noticed I don't explained a lot about Zend Lucene itself... So you should look at the links I gave, and try to learn about it more!
As some one here added in the comment, pagination can be a little tricky... You need to do some calculations by yourself...
Actually it is always a dilemma if to give you find the trick yourself, or give it to you directly...
There is a very big danger if you always get the answers for your problem from others... because in real time job, no one will do the job for you...
So this is how it would look with CPagination, after you figure out how to do the pagination of the result yourself:

So dont continue reading, until you spend some time thinking about it and trying your self!
Add to search
$pages = new CPagination(count($results)); $currentPage = Yii::app()->getRequest()->getQuery('page', 1); $pages->pageSize = 3;
In the view change the foreach view with for
for($i = $currentPage * $pages->pageSize - $pages->pageSize, $end = $currentPage * $pages->pageSize; $i<$end;$i++): <p>Title: <?php echo $query->highlightMatches(CHtml::encode($results[$i]->title)); </p> <p>Link: <?php echo CHtml::link($query->highlightMatches(CHtml::encode($results[$i]->link)), CHtml::encode($results[$i]->link)); </p> <p>Content: <?php echo $query->highlightMatches(CHtml::encode($results[$i]->content)); </p> <hr/> <?php endfor;
and add after this
$this->widget('CLinkPager', array( 'pages' => $pages, ))
It is not perfect, but it is the start. The problems you should think about are: what to do if some one tries to access page number 40 ? now it will throw error 500
What will happen if you have 2 pages, but only 4 records? You will get undefind ofsset errors...
Consider this as homework ;-)
p.s. don't forget to add some blog posts or you will have nothing to paginate ;-)
Actually if you want it pass between pages via ajax, and don't handle the offset issues etc. ( it is easy but... we still most likly want ajax)
So you can consider doing the pagination via CArrayDataProvider You have there example http://www.yiiframework.com/doc/api/1.1/CArrayDataProvider Just pass the result array than you can create CListView with some basic view for search result element, and you are done... CArrayDataProvider will do all the dirty work for you!
Total 15 comments
First: thanks for your tutorial. Without it, I would have needed much longer to establish a working Lucene search. However, I have some additions:
Do not CHtml::encode the fields you pass in. You will break searching for umlauts and Lucene can cope perfectly with umlauts.
For further avoiding of umlaut problems, one should add
to the beginning of actionSearch() and
to the beginning of actionCreate();
You should not put links into Lucene. Why should you? Nobody will every search for parts of the url, it will just distort search results. Better save the post-id (don't call this field id, as this may cause problems, call it 'post_id' or such) as unindexed field and retrieve the link from the DB when showing the results.
I don't like how you skip some parts, assuming everyone would know what to do next. The phrasing is sometimes very patronizing (I hope I got the right word), which makes some users feel bad.
But, nevertheless, thanks again for writing the one and only tutorial on Lucene in Yii.
Hi, i did everything excatly like u said and Zend's throwing an exception:
how should I solve it?
I had lot of problems with highlither and display, can't finaly get it to work, i remove the text highligthing using a jquery script to do it.
Does someone managed to do it with accents ?
when i try this link: http://www.zendframework.com/download/latest
get error 400
for true link replace download with downloads:
Download Latest Releases zend
But I did not find the folder search and got the following link : http://framework.zend.com/svn/framework/standard/trunk/
Great tutorial... just one quick question where does Lucene create the index files. How can I check the contents of this index after the create index function
is called.
Vaibhav
I had used this search features in my demo project previously. Recently I need to used in real project and I found this when i searched through the net. I tried it, it works fine for some search parameter also throws exception errors too. Further more I can't use
CleanHtml function to clear out html tags in the description section. Thats why I moved to another search approach with CDbCriteria and that work fine for with simple like condition.
So my question is why to use zend search rather than using simple cdb criteria.
Thanks for your great tutorial.
Hey am trying to do pagination the way you did it but my i contently stays at -1,-2,-3 starting from the bottom, any reason for that?
hi bro.. thanks for this useful tutorial..
i have a problem how to displaying the content in a couple line only... i have a content that contain a lot of text, it's so annoying if i displaying all the text contained..
so do you have a solution how to minimalize the words, maybe only a couple of line...
thanks anyway
Thanks a lot for this wiki.
Could you please expand this wiki with explanations/examples how to implement the following features:
1) Make search for all objects in DB. You described the search only for Post object. But many apps require a global search for all objects. For example DB schema: Accounts->Companies->Projects->ToDos->Tasks->Comments. Projects, ToDos and Tasks have 'title' and 'description' fields. How to organize the search for all these fields + content of comments and get it like one result set.
2) Restrict the scope of searching by user permissions: objects of projects to which user have not access (table Projects<->Users) must not be included in search results.
Thank you in advance.
really nice, useful, thanks!
This is awesome, thank you
I have been wondering how to implement search.
raa - it is not an issue, it is a little bit tricky but it is easy...
I added an example of how to do it, but you should be able to think about that yourself - so before looking at the solution - you better think yourself.
nice solution. I've used this search in all my projects. have you solved issue with CPagination ?
Leave a comment
Please login to leave your comment.