Going for Symfony | 第六天

这是我对任氏有无轩改造的(第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();
}

我只对三个字段进行了索引:titleauthorid。如果将来要增加索引字段(例如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>

其它链接的改写也类似,就不举例了。

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *