返回目录
Zend_Search_Lucene 组件的目的是提供一个即可使用的全文搜索解决方案。它不要求任何 PHP 扩展 UTF-8mbstring 或者安装额外的软件,在 Zend_Framework 安装以后立刻能被使用。
Zend_Search_Lucene 是流行开源全文搜索引擎 Apache Lucene 的一个纯 PHP 端口。查看 http://lucene.apache.org 获得更多信息。
为了可以搜索,信息必须被建立索引。Zend_Search_Lucene 和 Java Lucene 使用一个文件概念,称之为 atomic indexing item。
每一个文件是一套字段:<name, value> 一对,name 和 value 是 UTF-8 字符串。文件字段的任何子集可以被标记为 indexed 来包括在建立文字索引的过程中的字段数据。
当建立索引的时候,字段值可以或不必被标记。如果一个字段不被标记,字段值以一个词组保存;否则,当前的分析器被用作标记。
在 Zend_Search_Lucene 包中提供了几个分析器。默认的分析器工作在 ASCII 文字(因为 UTF-8 分析器需要打开 mbstring 扩展)。它是大小写敏感,而且它会忽略数字。如果你需要改变这个行为,可以使用其它的分析器或者创建你自己的分析器。
注意:在索引和搜索的过程中使用分析器
重要提示!搜索语句同样使用当前的分析器标记,所以在索引和搜索过程中必须设置相同的默认分析器。这将保证源文件和搜索文字以同样的方式转化成词组。
字段值可选的被保存在一个索引内。这允许当搜索的时候,原始的字段数据可以从索引中被检索。这个是把搜索结果和原始数据关联起来的唯一方法(在索引优化或者自动优化以后,内部的文件 ID 可能被改变)。
要记住的是,Lucene 索引不是一个数据库。除了备份文件系统目录,它不提供索引备份机制。它不提供事务缓存(transactional)机制,虽然迸发索引更新和迸发更新和读取被支持。在数据检索速度上它比不上数据库。
所以好的主意:
在索引中的独立的文件,可能有完全不同的字段组合。不同文件中的同样的字段不必拥有相同的属性。例如,一个字段可以为一个文件被索引,同时被其它文件的索引忽略。存储,标识,或者把字段值当作一个二进制字符串处理,也是如此。
返回目录
为了全面的使用 Zend_Search_Lucene 的最大功能,你应该了解它的内置索引结构
一个索引以一套文件的形式被保存在一个单独的目录中。
一个索引由数量众多的独立片断组成,这些片断储存有信息,这些信息是关于被索引文件的一个子集。每一个片断有它自己的词组字典,词组字典索引,和文件存储(保存字段值)Zend_Search_Lucene。所有的片断数据保存在 _xxxxx.cfs 文件中,xxxxx 是片断的名字。
一旦一个索引片断文件被创建,它不能被更新。新的文件被加到新的片断中。删除的文件只被以 deleted 标记在一个可选择的 <segmentname>.del 文件中。
文件更新将会以删除和添加这两个独立的操作来完成,虽然它是通过调用 update() API Zend_Search_Lucene API 做到的。这简化了添加新文件,并允许和搜索操作同时更新。
在另外一方面,使用几个片断()增加了搜索时间:
索引自动优化被这三个选项控制:
MaxBufferedDocs(在缓冲内存中文件被写入到一个新的片断之前,要求的文件的最小数量)
MaxMergeDocs(被一个优化操作合并的文件的最多的数量)
MergeFactor(用来决定由自动优化操作合并的片断标记体的频繁程度)。
如果每执行一个脚本我们添加一个文件,那么 MaxBufferedDocs 事实上没有被使用(在脚本执行结束的时候只有一个新片断和一个文件被创建,这时自动优化进程开始)。
返回目录
全部的索引操作(例如,创建一个新的索引,给索引添加一个文件,删除一个文件,搜索整个索引)需要一个索引对象。可以使用以下两种方法的任何一个:
例一 Lucene 索引创建
$index = Zend_Search_Lucene::create($indexPath);
例二 Lucene 索引打开
$index = Zend_Search_Lucene::open($indexPath);
返回目录
索引由添加一个新文件到一个已经存在或者新的索引完成:
$index->addDocument($doc);
有两种办法来创建一个文件对象。第一个是手动的
例一 手动文件建造
$doc = new Zend_Search_Lucene_Document(); $doc->addField(Zend_Search_Lucene_Field::Text('url', $docUrl)); $doc->addField(Zend_Search_Lucene_Field::Text('title', $docTitle)); $doc->addField(Zend_Search_Lucene_Field::unStored('contents', $docBody)); $doc->addField(Zend_Search_Lucene_Field::binary('avatar', $avatarData));
第二种办法是从 HTML 或者微软 Office 2007 文件中载入:
例二 文件载入
$doc = Zend_Search_Lucene_Document_Html::loadHTML($htmlString); $doc = Zend_Search_Lucene_Document_Docx::loadDocxFile($path); $doc = Zend_Search_Lucene_Document_Pptx::loadPptFile($path); $doc = Zend_Search_Lucene_Document_Xlsx::loadXlsFile($path);
如果一个文件以一个被支持的格式载入,它仍然可以通过新的用户定义字段来手动扩展。
你应该在你的应用程序的结构设计中定义索引策略。
你可能需要一个随选定制的索引设置(和 OLTP 系统相似的东西)。在这样的系统,你通常为每一用户请求添加一个文件。这样,MaxBufferedDocs 选项将对系统没有影响。在另外一方面,MaxMergeDocs 由于它允许你限制脚本执行时间的最大值,就真的有用了。MergeFactor 应该被设置到一个值,这个值保持平均索引时间(它也受平均自动优化时间影响)和搜索效率(索引优化水平依赖于片断的数量)之间的平衡。
如果你主要是进行批量索引更新,你的设置应该使用一个 MaxBufferedDocs 选项,把它设置到被可用的内存支持的最大值。MaxMergeDocs 和 MergeFactor 不得不设置到尽可能的降低自动优化相关的值。全部的索引拿破仑应该在索引以后进行。
例三 索引优化
$index->optimize();
在一些配置中,更有效的是,通过组织更新请求到一个队列,和在一个单独的脚本执行中进行几个更新请求来序列化索引更新。这将降低索引过分打开,同时允许利用索引文件缓冲。
返回目录
搜索通过使用 find() 方法来运行。
例一 搜索全部索引
$hits = $index->find($query); foreach ($hits as $hit) { printf("%d %f %s/n", $hit->id, $hit->score, $hit->title); }
这个例子展示了两个特殊搜索 hit 属性 -- id 和 score 的用法。
id 是在一个 Lucene 索引内使用的一个内置的文件标识符。它可以被用来执行许多操作,包括从索引删除一个文件。
例二 删除一个已经被索引的文件
$index->delete($id);
或者从索引中检索文件:
$doc = $index->getDocument($id);
注意:内置文件标识符
重要提示!内置的文件标识符可以通过索引优化或者自动优化进程改变,但是它在一个单一的脚本执行过程中它不会改变,除非 addDocument()(可能包括一个自动拿破仑过程)或者 optimize() 方法被调用。
score 字段是一个命中得分。默认的,搜索结果将按照 score 排序(最好的结果首先返回)。
按照特定的字段值来给结果排序也是可以的。查看 Zend_Search_Lucene 文档来获得更多信息。
这个例子也展示了一个能力,访问储存字段的能力(例如,$hit->title)。在第一次访问非 id 或者 score 的 hit 属性的时候,文件存储字段被加载,然后返回相应的字段值。
这引起一个歧义,对于拥有自己 id 或者 score 字段的文件而言;因此,不建议在存储文件内使用这些字段名。尽管如此,仍然可以通过 getDocument() 方法来访问他们:
例四 访问文件内的原始 id 和 score 字段
$id = $hit->getDocument()->id; $score = $hit->getDocument()->score;
返回目录
Zend_Search_Lucene 和 Jave Lucene 支持一个强有力的查询语言。它允许搜索单独的词组,短语,词组范围;使用外卡和模糊搜索,使用布尔值操作符来合并查询,诸如此类。
更详细的查询语言资料可以在 Zend_Search_Lucene 组件文档中找到。
下面是一些普通查询类型和策略的例子。
例一 查询一个单独的字
hello
在全部文件字段中搜索 hello 这个单词
注意:默认搜索字段
重要记号!Jave Lucene 默认的只搜索全部的 contents 字段,但是 Zend_Search_Lucene 搜索全部的字段。这个行为可以使用 Zend_Search_Lucene::setDefaultSearchField($fieldName) 方法来改变。
例二 查询多个字
hello dolly
搜索两个单词,两个单词都是可选的,结果必须出现两个单词中的一个。
例三 在一个查询中要求单词
+hello dolly
搜索两个单词,hello 是必须的,dolly 是可选的
例四 在查询的文件中禁止单词
+hello -dolly
搜索两个单词,hello 是必须的,dolly 是禁止的。也就是说,如果文件匹配 hello,但是包含 dolly 这个单词,它不会返回在搜索结果中。
例五 查询短语
"hello dolly"
搜索 hello dolly 这个短语;只有这个短语的文件才会匹配。
例六 在特殊的字段中查询
title:"The Right Way" AND text:go
在标题(title)内搜索 The Right Way 这个短语,和在正文(text)内出现的 word 这个单词。
例七 在特殊字段内搜索同时整个文件
title:"The Right Way" AND go
在标题内搜索 The Right Way 这个短语,同时在文件内任何字段中出现的 word 这个单词。
例八 在特殊字段内和整个文件内搜索(可选择)
title:Do it right
在标题字段内搜索 Do,同时在全部字段内搜索 it 和 right;与之任何一个匹配的文件将返回结果。
例九 使用通配符 ? 查询
te?t
搜索匹配 te?t 模式的单词,? 可以是任何一个字符。
例十 使用通配符 * 查询
test*
搜索匹配 test* 的单词,* 是任何的0或多个字母序列。
例十一 查询一个短语包括的范围
mod_date:[20020101 TO 20030101]
搜索一个短语的范围(包括在内的)
例十二 搜索一个短语之外的范围
title:{Aido to Carmen}
搜索一个短语的范围(排除在外的)
例十三 模糊搜索
roam~
模糊搜索 roam 单词
例十四 布尔搜索
(framework OR library) AND php
布尔搜索
所有的搜索可以通过 Zend_Search_Lucene 的 query construction API 来构建。还有,查询解析和查询构建可以结合:
例十五 合并查询解析和构建
$userQuery = Zend_Search_Lucene_Search_QueryParser::parse($queryStr); $query = new Zend_Search_Lucene_Search_Query_Boolean(); $query->addSubquery($userQuery, true /* required */ $query->addSubquery($constructedQuery, true /* required */);
返回目录
在之前提到的,搜索结果命中对象使用懒惰装载来储存文件字段。当任何被储存的字段被访问,整个文件将会被装载。
如果你只想对某些文件进行操作,不要检索全部的文件。
$cacheId = md5($query); if (!$resultSet = $cache->load($cacheId)) { $hits = $index->find($query); $resultSet = array(); foreach ($hits as $hit) { $resultSetEntry = array(); $resultSetEntry['id'] = $hit->id; $resultSetEntry['score'] = $hit->score; $resultSet[] = $resultSetEntry; } $cache->save($resultSet, $cacheId); } $publishedResultSet = array(); for ($resultId = $startId; $resultId < $endId; $resultId++) { $publishedResultSet[$resultId] = array( 'id' => $resultSet[$resultId]['id'], 'score' => $resultSet[$resultId]['score'], 'doc' => $index->getDocument($resultSet[$resultId]['id']), ); }