这是我对任氏有无轩改造的(第N个)第六天……
我要完成的是至关重要的功能:加入全文搜索。今天的收获还算可以,还需要我进行更多实际数据的测试进行验证。
程序开发的一个重要原则是:DRY(Don\’t Repeat Yourself),或者说不要去发明轮子。大意是说,要尽量多的复用现有的东西。比如在搜索功能上,我们完全可以使用现成的Lucene模块。在Symfony中,更是将DRY原则进一步发挥,我们不会从最基本的Lucene库开始开发,而是直接使用Zend Framework中包含的Lucene模块。
首先要安装并配置ZF。从Zend的网站下载Zend Framework,然后解压到lib/vendor/Zend目录下。由于我们不会用到那么多Zend的模块,所以可以删除一些目录,在lib/vendor/Zend目录下只保留这样一些内容:
- Exception.php
- Loader/
- Loader.php
- Search/
在ProjectConfiguration.class.php中注册Zend的自动加载器:
// config/ProjectConfiguration.class.php
class ProjectConfiguration extends sfProjectConfiguration
{
static protected $zendLoaded = false;
static public function registerZend()
{
if (self::$zendLoaded)
{
return;
}
set_include_path(sfConfig::get('sf_lib_dir').'/vendor'.PATH_SEPARATOR.get_include_path());
require_once sfConfig::get('sf_lib_dir').'/vendor/Zend/Loader.php';
Zend_Loader::registerAutoload();
self::$zendLoaded = true;
}
// ...
}
好了,Zend已经注册,我们已经可以进入编程了。 我们需要在BookBookPeer类中创建一个函数,用来打开或者创建索引文件:
// lib/model/BookBookPeer.php
static public function getLuceneIndex()
{
ProjectConfiguration::registerZend();
if (file_exists($index = self::getLuceneIndexFile()))
{
return Zend_Search_Lucene::open($index);
}
else
{
return Zend_Search_Lucene::create($index);
}
}
static public function getLuceneIndexFile()
{
return sfConfig::get('sf_data_dir').'/job.'.sfConfig::get('sf_environment').'.index';
}
索引的保存应该和记录的保存同时进行,所以我们需要改写BookBook类的save函数:
public function save(PropelPDO $con=null)
{
if(is_null($con))
$con=Propel::getConnection(BookBookPeer::DATABASE_NAME, Propel::CONNECTION_WRITE);
$con->beginTransaction();
try
{
$ret=parent::save($con);
$this->updateLuceneIndex();
$con->commit();
return $ret;
}
catch (Exception $e)
{
$con->rollBack();
throw $e;
}
}
这里用到了事务,因为我们希望保存记录本身和保存索引这两个动作要么都成功,要么都不成功。否则会出现索引和记录不匹配的问题。updateLuceneIndex()的方法是这样的:
public function updateLuceneIndex()
{
$index=BookBookPeer::getLuceneIndex();
//remove an existing entry
if($hit=$index->find('pk:'.$this->getId()))
$index->delete($hit->id);
$doc=new Zend_Search_Lucene_Document();
//Store book pk URI to identify it in the search results
$doc->addField(Zend_Search_Lucene_Field::UnIndexed('pk', $this->getId()));
//Index book fields
$doc->addField(Zend_Search_Lucene_Field::UnStored('title', $this->getTitle(), 'utf-8'));
$doc->addField(Zend_Search_Lucene_Field::UnStored('author', $this->getAuthor(), 'utf-8'));
$doc->addField(Zend_Search_Lucene_Field::UnStored('id', $this->getId(), 'utf-8'));
//I will leave tags search later
//add job to the index
$index->addDocument($doc);
$index->commit();
}
我只对三个字段进行了索引:title,author,id。如果将来要增加索引字段(例如tag),只要相应增加$doc->addField即可。
完成这个任务之后,我们基本已经大功告成,下面要做的就是编写对应的动作,并构造正确的搜索条件。由于搜索和列表实际上是功能相同的工作(后者可以看做没有搜索关键字的搜索),所以我将这两个功能的路径设置为一样的。另外,在Symfony的官方教程中,搜索并没有加入分页的功能,我也会一并加入。因此我改写了executeSearch函数:
public function executeSearch(sfWebRequest $request)
{
$key=$request->getParameter('key', 'null');
$c=new Criteria();
if($key!='null')
{
$c=BookBookPeer::getLuceneCriteria($key);
}
$this->pager=new sfPropelPager('BookBook', sfConfig::get('app_records_on_book_search'));
$this->pager->setCriteria($c);
$this->pager->setPage($request->getParameter('p',1));
$this->pager->init();
$this->key=$key;
}
我首先判断是否有key(搜索关键词),如果有的话,需要重新定义$c这个搜索条件(由getLuceneCriteria完成)。然后创建一个pager,并传递$c搜索条件。 getLuceneCriteria()函数的定义如下:
static public function getLuceneCriteria($key)
{
$hits=self::getLuceneIndex()->find($key);
$pks=array();
foreach($hits as $hit)
$pks[]=$hit->pk;
$c=new Criteria() ;
$c->add(self::ID, $pks, Criteria::IN);
return $c;
}
从代码中可以看到,这实际上是一个IN的条件搜索。
做完这些,我们还要稍微改写一下搜索(列表)页面的导航条显示,我们必须加入key参数,否则在导航后,就会失去搜索条件:
<a echo="" href="<?php" url_for="">?p=<?php echo $pager-?>getPreviousPage() ?>&key=<?php echo $key ??>>
<img alt="Previous" page="" src="/images/previous.png"></img>
</a>
其它链接的改写也类似,就不举例了。
Leave a Reply