Difference between #14 and #13 of Adding search to Yii blog example (using Zend Lucene)

unchanged
Title
Adding search to Yii blog example (using Zend Lucene)
unchanged
Category
Tutorials
unchanged
Tags
blog, demo, search, zend, zf, lucene, tutorial
changed
Content
Introduction
------------
ForIn this tutorial I will add a search to the blog demo.
The search would be based on Zend Lucene.

Requirements and Preparation
---------------------------
* Yii framework -  II will work with the current version
[yii-1.1.8.r3324](yii.googlecode.com/files/yii-1.1.8.r3324.tar.gz
"1.1.8")
extract the archive inon your server...
* Now go to your **yii-1.1.8.r3324\demos\blog\protected** directory
and create there a **vendors** directory
In the vendors directory you should put the Zend Framework
library,
For thislibrary which you will need tocan
[download
it](http://www.zendframework.com/download/latesthere](http://www.zendframework.com/download/latest
"download it")here").
After downloading copy the Zend directory that insidewith
the library (with Zend directory inside) to vendors
Shouldinto vendors.
It should look like this now (I copied only the Search
directory...):
<center>
![](http://i31.fastpic.ru/big/2011/1007/35/815528faa31a322de17366764b489335.png
"")
</center>

* add under runtime directory,directory a new one
and call it search, it will be used for the index files that Zend Lucene
will create.
Make sure it is writable!

Step 1 - adding the search form
-------------------------------
For this we will create a widget or if to be more specific a CPortlet!
The most simple way is to go to your components
directory,directory and create a
SearchBlock.php filefile.
And copy inside code from tag cloud.
change class name to SearchBlock.
Now lets insert it !
Go to views/layouts/column2.php
add this, abouveabove the TagCloud widget

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

after saving you will see 2 tag couldsclouds in your main
blog page...
lets now edit it and change it to some search form

Now lets change the SearchBlog widget code...


~~~
[php]
<?php

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

class SearchBlock extends CPortlet
{
	publicpublic $title='Search';

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

It will look like this:
<center>
![](http://i30.fastpic.ru/big/2011/1007/d8/e6aba953b2b9056dcb502d25e2d91dd8.png
"")
</center>

Creating SearchController
------------------------
Now lets add a 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
*/
private $_indexFiles = 'runtime.search';
	/**
	 *
/**
* (non-PHPdoc)
	 *
* @see CController::init()
	 */
	public
*/
public function init(){
		Yii::import('application.vendors.*');
		require_once('Zend/Search/Lucene.php');
		parent::init();	
	}
	
    public
Yii::import('application.vendors.*');
require_once('Zend/Search/Lucene.php');
parent::init();
}

public function actionCreate()
    {
    }
    
    public
{
}

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](http://framework.zend.com/manual/en/zend.search.lucene.overview.html
"")
To do more advanced stuff, you will need to learn it

Also the [api
](http://framework.zend.com/apidoc/core/db_Search_Lucene.html#%5CZend_Search_Lucene
"") for the Lucene class 

Creating Search Index
---------------------
This is an example of blog array after AR findAll

~~~
[php]
'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:
~~~
[php]
/**
	 ** Search index creation
	 */
    public
*/
public function actionCreate()
    {
    	$index
{
$index = new Zend_Search_Lucene(Yii::getPathOfAlias('application.' .
$this->_indexFiles), true);
    	
    	$posts

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

$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)
												,
);

$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)
                                          ,
);

$doc->addField(Zend_Search_Lucene_Field::Text('content',
CHtml::encode($post->content)
, 'utf-8')
            );
           
			
			$index->addDocument($doc);
    	}
	    $index->commit();
	   	echo
);


$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"

Doing the search
----------------


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


	       
$this->render('search',$this->render('search',
compact('results', 'term', 'query'));
	    }
    }
}
}
~~~

Creating the view
-----------------
Create views/search/search.php
add there


~~~
[php]
<?php
$this->pageTitle=Yii::app()->name . ' - Search results';
$this->breadcrumbs=array(
	'Search'Search Results',
);
?>

<h3>Search Results for: "<?php echo CHtml::encode($term);
?>"</h3>
<?php if (!empty($results)): ?>
			    <?php<?php foreach($results as $result):

?>					
					<p>Title:
?>
<p>Title: <?php echo
$query->highlightMatches(CHtml::encode($result->title));
?></p>
			        <p>Link:
<p>Link: <?php echo
CHtml::link($query->highlightMatches(CHtml::encode($result->link)),
CHtml::encode($result->link)); ?></p>
			        <p>Content:
<p>Content: <?php echo
$query->highlightMatches(CHtml::encode($result->content));
?></p>
			        <hr/>
			    <?php
<hr/>
<?php endforeach; ?>
			    
			<?php

<?php else: ?>
			    <p
<p class="error">No results matched your search
terms.</p>
			<?php
<?php endif; ?>
~~~

As you can see because I passed to the view the $query, I can use Zend's
highlightMatches ...<br> 
(if
(if your search results not in english,English, you
might have some encoding issues with highlightMatches, so you might consider
using your own highlighter for greater flexibility and encoding issues
free)<br> 
Also I created a real link from the linked I passed and created via
**$doc->addField(Zend_Search_Lucene_Field::Text('link' ...**

And thatsthat's it
try to search the word "it"
And you will get:

![](http://i31.fastpic.ru/big/2011/1007/e2/15c1a1e713fde3ba060ad3f845b034e2.png
"")

Conclusion
----------
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!

Additional Links
----------------
1. [larry ulman's artice about Zend
Lucene](http://www.larryullman.com/2009/12/05/integrating-zend_lucene-with-yii/
"")
2. [Zend Lucene
documentation](http://framework.zend.com/manual/en/zend.search.lucene.overview.html
"")
3. [Good article
1](http://www.talkincode.com/getting-started-with-zend_lucene-885.html
"")
3. [Good article 2](http://devzone.zend.com/article/91 "")
5. [Good articles
3](http://ganeshhs.com/zend-framework/zend-lucene-search-part5-search-engine-results-page-formatting
"")

Bonus: Pagination with CPagination
-----------------------------------
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](http://www.yiiframework.com/doc/api/1.1/CPagination ""),
after you figure out how to do the pagination of the result yourself:
<center>
(The page limit is 3 per page)
![](http://i28.fastpic.ru/big/2011/1007/63/fdd639bd9fdb28a8e50fa49903d6e663.png
"")
</center>

So dontdon't continue reading, until you spend some time
thinking about it and trying your self!


Add to search
~~~
[php]
$pages = new CPagination(count($results));
$currentPage = Yii::app()->getRequest()->getQuery('page', 1);
$pages->pageSize = 3;
~~~

In the view change the foreach view with for


~~~
[php]
 <?php<?php for($i = $currentPage *
$pages->pageSize - $pages->pageSize, $end = $currentPage *
$pages->pageSize; $i<$end;$i++):
?>					
					<p>Title:
?>
<p>Title: <?php echo
$query->highlightMatches(CHtml::encode($results[$i]->title));
?></p>
			        <p>Link:
<p>Link: <?php echo
CHtml::link($query->highlightMatches(CHtml::encode($results[$i]->link)),
CHtml::encode($results[$i]->link)); ?></p>
			        <p>Content:
<p>Content: <?php echo
$query->highlightMatches(CHtml::encode($results[$i]->content));
?></p>
			        <hr/>
			    <?php
<hr/>
<?php endfor; ?>
~~~

and add after this 



~~~
[php]
<?php $this->widget('CLinkPager', array(
    'pages''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 ofssetundefined offset errors...

Consider this as homework ;-)

p.s. don't forget to add some blog posts or you will have nothing to paginate
;-)

Another pagination solution
---------------------------

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 liklylikely
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!