好了,咱们的学习要更进一步了,现在进入到的就是整个 XS 学习的核心部分,PHP SDK 相关操作的学习。其实说白了,对于 PHP 代码中使用 XS 的功能,无非就是我们之前已经演示的那些内容。最核心的就是三个对象:XS、XSIndex 和 XSSearch 对象。但是整个 XS 中又不完全是这三个对象的内容,还有一些其它的对象也有着非常重要的作用。因此,在这一篇和下一篇文章中,我们将以 XS 对象为切入点,同时学习一下一些其它方面的基础对象。
而 XSIndex 和 XSSearch ,将分别会在索引管理和搜索技巧中进行更深入的学习,最后,我们还有非常重要的分词模块 XSTokenizer 的学习。整个 XS 系列后面的学习体系大概就是这样了。
话不多说,直接开始今天的学习吧。
在 XS 提供的 PHP SDK 中,所有的代码均采用的是面向对象的方式,也就是说,我们只需要学习不同的对象相关的属性和方法就可以全面掌握 XS 的各种操作了。
在这些对象中,最核心的是主要是以下几个对象(类)。
XS 对象,用于搜索项目的总对象,所有操作都是从它开始的
XSException,XS 操作的异常都可以通过这个对象进行捕获,完全面向对象的,还有一个 XSErrorException 是它的子类
XSIndex,PHP动态代码中操作索引的对象,后面我们在索引管理中会详细学习
XSSearch,提供各种搜索功能的对象,也是我们后面要重点学习的内容
XSTokenizer,分词相关接口,之后有单独的章节学习
XSDocument,文档对象,具体的数据对象,不管是索引还是查询所有的增删改查操作全是在摆弄它
上面这几个对象,有几个是不是已经很熟悉了。其它不熟悉的,我们也都会讲解一点。另外还有 XSCommand 、XSFieldScheme、XSFieldMeta、XSServer 以及 XSComponent 这几个对象(类)。除了上面简介中提到的 XSIndex、XSSearch 和 XSTokenizer 是在后面我们单独需要学习的,剩下的都会在这两篇基础对象的学习中讲解到。
首先,我们先来讲一下这些所有对象的超级父类,或者说是整个 XS PHP SDK 的全局基类,那就是 XSComponent 类。
这个类是所有 XS 对象的基类,它最主要的作用是啥呢?其实就是封装了四个魔术方法:__set()
、__get()
、__isset()
、__unset()
,关于魔术方法的作用,我最早的文章中就有写过,不记得的小伙伴可以去看一下哦 https://www.zyblog.com.cn/t/PHP魔术 。
因此,就像我们之前已经见到过的。
$xs = new XS("./config/zyarticle.ini");
$index = $xs->index;
$search = $xs->search;
看似调的是属性,但都是通过 __get()
魔术方法去调用的具体对象内部的 getXXX() 方法。比如上面的 XS 对象中,就有 getIndex() 和 getSearch() 方法。
具体的魔术方法的代码,我们就看一下 __get()
的好了。源码在 /vendor/hightman/xunsearch/lib/XS.class.php
文件中。
// /vendor/hightman/xunsearch/lib/XS.class.php XSComponent 类
public function __get($name)
{
$getter = 'get' . $name;
if (method_exists($this, $getter)) {
return $this->$getter();
}
// throw exception
$msg = method_exists($this, 'set' . $name) ? 'Write-only' : 'Undefined';
$msg .= ' property: ' . get_class($this) . '::$' . $name;
throw new XSException($msg);
}
再给个简单的例子。
$a = $obj->text; // $a 值等于 $obj->getText() 的返回值
$obj->text = $a; // 等同于调用 $obj->setText($a)
后面有关于属性这块我就不多说了,如果你看了源码,找不到对应的属性,一定要记得,它们都继承自 XSComponent 对象,都实现了这些魔术方法,要去找类中对应的具体实现方法,而不是死盯着变量去找。
项目对象,就是我们一直都能见到的 new XS('xxx.ini')
这个实例化出来的对象。它是一个搜索项目的总对象,所有的别的操作都是从它开始的。
$xs = new XS("./config/5-zyarticle-test1.ini");
print_r($xs);
// object(XS)#3 (6) {
// ["_index":"XS":private]=>
// NULL
// ["_search":"XS":private]=>
// NULL
// ["_scws":"XS":private]=>
// NULL
// ["_scheme":"XS":private]=>
它的构造参数只有一个,就是索引配置文件的地址。关于配置文件加载的内容之前我们已经详细的说过了,这里就不再多做赘述了。XS 对象最主要的功能,就是读取索引配置文件,将配置信息保存,并初始化其它相关的操作对象。
同时,它又是继承自 XSComponent 的,因此,它的大部分功能都是可以通过魔术属性获得的。比如说它的主要属性内容包括:
$xs->index
,对应 $xs->getIndex()
方法,获取索引操作对象
$xs->search
,对应 $xs->getSearch()
方法,获取搜索操作对象
这两个对象是我们后面要学习的两大核心内容,它们都是 XS 对象给我们准备好的,不需要我们自己再去实例化。
其它还有类似的属性及对应的方法,我就简单列举一下,包括:
allFields,getAllFields(),获取一个 XSFieldMeta 对象数组,这个对象我们后面会说,就是全部的字段定义内容
config,getConfig(),获取配置原始数据数组,就是配置文件里的内容
defaultCharset,getDefaultCharset() 和 setDefaultCharset(),对应的就是配置文件中的 project.default_charset 参数配置,没错,它还能在 PHP 代码中动态修改,但是,现在建议就是通用 UTF-8 ,基本不用动这个了。配置文件中不设置的话默认也是 UTF-8 。
fieldBody,getFieldBody(),配置文件中 body 类型字段的 XSFieldMeta 类型对象
fieldId,getFieldId,配置文件中 id 类型字段的 XSFieldMeta 类型对象
fieldTitle,getFieldTitle,配置文件中 Title 类型字段的 XSFieldMeta 类型对象
name,getName() 和 setName() ,对应配置文件中 project.name 的内容,是的,它也可以动态配置
scheme,getSchema() 和 setSchema() ,返回一个 XSFieldScheme 对象,表示当前在用的字段方案,如果修改了,还可以通过 restoreScheme() 还原成配置文件中最原始的方案,这个 XSFieldScheme 对象我们马上会在后面细说
scwsServer,getScwsServer() 创建 SCWS 分词连接,返回的是一个 XSServer 对象,这个我们也会在后面详细学习
上面这些属性和方法,大家都可以自己一个一个 var_dump() 打印一下看看效果,也可以查看一下源码,源码位置还是在 /vendor/hightman/xunsearch/lib/XS.class.php
的 XS 类中。有一些是我们后面还要继续学习的,而另外一些大家看看就好。
除了上面的这些内容之外,XS 对象中还有三个别的功能方法,简单来看一下。
是的,XS 对象为我们封装了 mbstring 或 iconv 的字符转换方法,只要这两个扩展安装了其一,就可以使用下面的 convert() 方法。
$gbkStr = $xs->convert('我爱北京天安门!', 'GBK', 'UTF8');
echo $gbkStr, PHP_EOL;
echo $xs->convert($gbkStr, 'UTF8', 'GBK');
// �Ұ������찲�ţ�
// 我爱北京天安门!
XS 中没有地理位置索引,但是,它在 XS 对象中提供了一个根据经纬度计算距离的方法。要实现 Geo 查询的效果还有别的方式,我们后面再说。
echo $xs->geoDistance(112.983001,28.195852,112.934978,28.176032);
// 五一广场 中南大学 返回值单位是米,距离为 5193.8670834187 米
最后还有一个 getLastXS() 方法,用于返回最新的 XS 对象。这个是如果有链式调用的情况下,XS 内部的各种属性变量在不停变化,就可能通过这个方法获得最后最新的那个 XS 对象。
var_dump($xs->getLastXS());
链式调用指的就是在 XS 中,所有的操作都是可以一直 -> 下去的。就像 Laravel 中的数据库 Builder 一样,在之前的 Laravel 系列文章中有过说明哦。
XSFieldMeta 的意思就是数据字段结构元数据,其实就是保存着我们在索引项目文件中定义的每个字段的具体信息的对象。每一个 XS 项目对象都会有多个数据字段,其中有些特别的,有些普通的。比如说在上篇文章中我们学习到 XS 项目对象中的三个属性。
var_dump($xs->fieldId);
// object(XSFieldMeta)#4 (7) {
// ["name"]=>
// string(2) "id"
// ["cutlen"]=>
// int(0)
// ["weight"]=>
// int(1)
// ["type"]=>
// int(10)
// ["vno"]=>
// int(0)
// ["tokenizer":"XSFieldMeta":private]=>
// string(4) "full"
// ["flag":"XSFieldMeta":private]=>
// int(1)
// }
var_dump($xs->fieldTitle);
// ……………………
var_dump($xs->fieldBody);
// ……………………
XS 对象中的这三个属性分别返回的就是我们在索引配置文件中定义的 type 类型为 id、title 和 body 三种类型的字段。从打印的结果来看,一些在 ini 文件中设置的值会直接保存到对象的属性中,比如 cutlen、weight、type、tokenizer 这些。另外还有一些我们不太清楚的,咱们就来简单看看。
vno,字段序号取值为 0~255, 同一字段方案内不能重复, 由 XSFieldScheme::addField 进行确定,XSFieldScheme 我们下一节再说,源码中大概的意思是这个值会和 type 类型有关,如果是 body 类型的字段,这个值直接变成 255 ,否则,按字段数量递增
flag,其实就是配置文件的中 index ,记录索引方式的
从这些属性中,也可以看到 id、title、body 这些特殊字段的默认值是不是和我们前面文章中说的一样,比如 body 的 cutlen ,title 的 weight 等。
接下来,我们继续测试这个对象的一些方法。
$id = $xs->fieldId;
var_dump($id->hasCustomTokenizer()); // bool(true) . 是否自定义分词器
var_dump($id->getCustomTokenizer()); // object(XSTokenizerFull)#10 (0) {} 分词器对象
var_dump($id->hasIndex()); // bool(true) 是否索引
var_dump($id->hasIndexMixed()); // bool(false) 是否混合区索引
var_dump($id->hasIndexSelf()); // bool(true) 是否字段索引
var_dump($id->isBoolIndex()); // bool(true) 是否布尔索引
var_dump($id->isNumeric()); // bool(false) 是否是数字类型
var_dump($id->isSpeical()); // bool(true) 是否是特殊字段:id、title、body
var_dump($id->withPos()); // bool(false) 是否支持短语搜索
var_dump($id->toConfig()); // 显示字段的配置信息
// string(15) "[id]
// type = id
// "
我们以 id 字段为基础进行测试,代码中的注释已经写得很清楚啦。其实呀,大部分的方法都是在判断我们在配置文件中进行的一些配置参数的具体情况。注意最后一个 toConfig() 方法,它会返回字段在配置文件中的具体定义信息。这个方法还有一个对应的 fromConfig() ,可以用于改变字段的配置信息。这个咱们就不演示了,接下来直接演示的,是动态创建一个新的字段,fromConfig() 的功能也会体现在这里。
$c = [
'type'=>'date',
'index'=>'both',
];
$dateTest = new XSFieldMeta('date_test', $c);
var_dump($dateTest->toConfig());
// string(37) "[date_test]
// type = date
// index = both
// "
var_dump($dateTest->hasIndexMixed()); // bool(true)
var_dump($dateTest->hasIndexSelf()); // bool(true)
var_dump($dateTest->isSpeical()); // bool(false)
var_dump($dateTest->val('2022-11-23')); // string(8) "20221123" 把给定的值转换为符合这个字段的数据格式
看到效果了吧,我们自己动态地定义了一个 date 类型的字段。在 XSFieldMeta 的构造函数中,第二个参数就是需要一个 config 配置类型的内容,我们通过数组直接定义了字段需要的一些属性信息。
自己定义一个字段有什么用呢?别急,下面要学习的内容就会用到它。
在上一篇索引配置的学习中,我们就提到过,虽然不推荐,但是 XS 也是可以动态定义索引字段的。要实现这个功能,就需要用到 XSFieldScheme 对象。XS 数据字段方案对象,就是在索引文件中,我们定义的所有字段信息,每个方案包含若干个字段结构对象 XSFieldMeta,每个方案必须并且只能包含一个类型为 ID 的字段, 支持 foreach 遍历所有字段。方案对象没有什么公开的属性,它只有一堆操作方法,我们接下来就好好学习一下。
上个小节,我们直接通过 XS 对象的 fieldId、fieldTitle、fieldBody 来获取那三个特殊字段。那么其它字段要怎么获取呢?比如说 tags 和 category_name 以及 pub_time 这些。
$fields = $scheme->getAllFields();
print_r($fields['tags']);
// XSFieldMeta Object
// (
// [name] => tags
// [cutlen] => 0
// [weight] => 1
// [type] => 0
// [vno] => 4
// [tokenizer:XSFieldMeta:private] => split(,)
// [flag:XSFieldMeta:private] => 3
// )
print_r($scheme->getField('category_name'));
// XSFieldMeta Object
// (
// [name] => category_name
// [cutlen] => 0
// [weight] => 1
// [type] => 0
// [vno] => 3
// [tokenizer:XSFieldMeta:private] => full
// [flag:XSFieldMeta:private] => 1
// )
不用多解释了吧,要获取全部的字段只需要 getAllFields() 方法即可,然后可以遍历返回的数组。如果知道字段名,就可以使用 getField() 方法。它们返回的都是上一个小节里我们学习过的 XSFieldMeta 对象。
另外,还有在 XSFieldMeta 中我们见过的一个属性 vno ,在 XSFieldScheme 里也可以看到当前方案中所有的 vno 信息。
print_r($scheme->getVnoMap());
// Array
// (
// [0] => id
// [1] => title
// [255] => content
// [3] => category_name
// [4] => tags
// [5] => pub_time
// )
查看相关的方案字段就是这些了。接下来就是重头戏了,咱们要动态地修改索引字段配置信息。
其实呀,直接给方案中添加一个新的字段非常简单。
$c = [
'type'=>'date',
'index'=>'both',
];
$dateTest = new XSFieldMeta('date_test', $c);
$scheme->addField($dateTest);
echo $scheme;
// [id]
// type = id
// [title]
// type = title
// [content]
// type = body
// [category_name]
// index = self
// tokenizer = full
// [tags]
// index = both
// tokenizer = split(,)
// [pub_time]
// type = date
// [date_test]
// type = date
// index = both
看到了吧,就是通过 addField() 方法,直接添加一个 XSFieldMeta 对象进去就可以了,使用的就是上一节我们新建的那个日期字段。
此外,XSFieldScheme 对象还重写了 __toString()
方法,所以直接打印这个对象,就会输出完整的,和配置文件中一模一样的字段配置格式。聪明的你一定猜到了,在动态修改字段后,我们就可以将这个信息再保存到 .ini 配置文件或者是数据库中。动态新添加的字段保存后,下次再次实例化时,就可以直接通过配置文件或者数据库中的信息加载新的配置信息了。
之前我们已经学习过,XS 对象的构造参数,不仅可以通过文件加载配置信息,也可以通过字符串加载配置信息。这样一来,其实整个 XS 的配置就非常灵活了,完全可以达到与 ES 和 MongoDB 之类类似的不用预先定义结构字段的效果。
但是,还是那句话,不推荐!!原因之前已经说过,这里不再赘述。
好了,动态添加字段有了,那么有效果吗?咱们添加一条数据试下呗。
$xs->index->add(new XSDocument([
'id'=>200001,
'title'=>'测试动态添加新字段',
'date_test'=>time()
]));
添加完数据之后等一会。
print_r($xs->search->search('测试动态添加新字段'));
// Array
// (
// [0] => XSDocument Object
// (
// [_data:XSDocument:private] => Array
// (
// [id] => 200001
// [title] => 测试动态添加新字段
// [date_test] => 20221123
// [content] =>
// ……………………
看来确实是没有问题了。
最后,我们再来看一下方案的有效性。
在 XS 中,对于索引配置中的字段方案,只有一个要求,那就是必须要有一个 id 类型的字段。同时,XSFieldScheme 也提供了一个 checkValid() 来进行检查。那么咱们就来测试一下这个方法。
$s1 = new XSFieldScheme;
$s1->addField('date_test');
// $s1->addField('pid', ['type'=>'id']); // 打开注释,结果就是 true 了
var_dump($s1->checkValid()); // bool(false)
checkValid() 没啥多说的,但是这段代码是比较有意思的。
第一,XSFieldScheme 对象可以单独实例化,没有构造参数。同时,上面说过的 XS 对象中的 scheme 属性是有对应的 setScheme() 方法的,也就是说,XS 对象的 scheme 确实是可以完全通过自定义动态 XSFieldScheme 对象来配置的。
第二,XSFieldScheme 对象的 addField() 方法可以不用传 XSFieldMeta 对象,而是像 XSFieldMeta 对象的构造函数一样传字段名字和字段的 config 配置。其实呀,大家看源码就能知道,addField() 方法会先判断第一个参数是不是 XSFieldMeta 对象,如果不是,就会使用传递进来的参数 new 一个 XSFieldMeta 对象出来。
怎么样?现在对于 XS 对象是啥、有什么作用;为什么 XS 中所有的对象都能通过属性获得对象或者各种值;索引字段是怎么在代码中配置和保存的等等,相信大家对这三个问题都有了一个比较清晰的认识了吧。如果你还很模糊,那么首先还是要恶补一下基础知识了。从大佬的源码中,我们可以看到各种魔术方法的应用,也能看到各种组合、继承模式的应用。
基础对象的下一篇,我们将学习到的是与服务端的对接,并且会粗浅地了解一下 XS 是如何通过 PHP 代码与 Xapian 进行数据交互的。也有基础知识的展现,但是同样我也不会非常深入,只是会提供出源码的位置供各位大神们进行更加深入的研究学习。
测试代码:
https://github.com/zhangyue0503/dev-blog/blob/master/xunsearch/source/6.php
参考文档:
http://www.xunsearch.com/doc/php/guide/class.overview
http://www.xunsearch.com/doc/php/guide/class.xs
http://www.xunsearch.com/doc/php/api/XSComponent
http://www.xunsearch.com/doc/php/api/XS
http://www.xunsearch.com/doc/php/api/XSFieldMetal
http://www.xunsearch.com/doc/php/api/XSFieldScheme#checkValid-detail