XS.class.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715
  1. <?php
  2. /**
  3. * XS 主类定义文件
  4. *
  5. * @author hightman
  6. * @link http://www.xunsearch.com/
  7. * @copyright Copyright &copy; 2011 HangZhou YunSheng Network Technology Co., Ltd.
  8. * @license http://www.xunsearch.com/license/
  9. * @version $Id$
  10. *
  11. * <ul>
  12. * <li>XS 是 XunSearch 的统一缩写, XS 是解决方案而不仅仅针对搜索, 还包括索引管理等</li>
  13. * <li>XS 运行环境要求 PHP 5.2.0 及以上版本, 带有 SPL 扩展</li>
  14. * <li>如果您的数据包含 utf-8 以外的编码(如: gbk), 则要求安装 mbstring 或 iconv 以便转换编码</li>
  15. * <li>对于 bool 类型函数/方法若无特别说明, 均表示成功返回 true, 失败返回 false</li>
  16. * <li>对于致命的异常情况均抛出类型为 XSException 的异常, 应将 xs 所有操作放入 try/catch 区块</li>
  17. * <li>这只是 XunSearch 项目客户端的 PHP 实现, 需要配合 xunsearch 服务端协同工作</li>
  18. * </ul>
  19. *
  20. * 用法简例:
  21. * <pre>
  22. * try {
  23. * // 创建 xs 实例 (包含3个字段 id, title, content)
  24. * $xs = new XS('etc/sample.ini');
  25. *
  26. * // 索引管理
  27. * $doc = new XSDocument('gbk');
  28. *
  29. * // 新增/根据主键更新数据
  30. * $doc->id = 123;
  31. * $doc->title = '您好, 世界!';
  32. * $doc->setFields(array('content' => '英文说法是: Hello, the world!'));
  33. * $xs->index->add($doc);
  34. *
  35. * $doc->title = '世界, 你好!';
  36. * $xs->index->update($doc);
  37. *
  38. * $xs->index->del(124); // 删除单条主键为 124 的数据
  39. * $xs->index->del(array(125, 126, 129)); // 批量删除 3条数据
  40. *
  41. * // 正常检索
  42. * // 快速检索取得结果
  43. * // 快速检索匹配数量(估算)
  44. *
  45. * } catch (XSException $e) {
  46. * echo $e . "<br />\n";
  47. * }
  48. * </pre>
  49. */
  50. define('XS_LIB_ROOT', dirname(__FILE__));
  51. include_once XS_LIB_ROOT . '/xs_cmd.inc.php';
  52. /**
  53. * XS 异常类定义, XS 所有操作过程发生异常均抛出该实例
  54. *
  55. * @author hightman <hightman@twomice.net>
  56. * @version 1.0.0
  57. * @package XS
  58. */
  59. class XSException extends Exception
  60. {
  61. /**
  62. * 将类对象转换成字符串
  63. * @return string 异常的简要描述信息
  64. */
  65. public function __toString()
  66. {
  67. $string = '[' . __CLASS__ . '] ' . $this->getRelPath($this->getFile()) . '(' . $this->getLine() . '): ';
  68. $string .= $this->getMessage() . ($this->getCode() > 0 ? '(S#' . $this->getCode() . ')' : '');
  69. return $string;
  70. }
  71. /**
  72. * 取得相对当前的文件路径
  73. * @param string $file 需要转换的绝对路径
  74. * @return string 转换后的相对路径
  75. */
  76. public static function getRelPath($file)
  77. {
  78. $from = getcwd();
  79. $file = realpath($file);
  80. if (is_dir($file)) {
  81. $pos = false;
  82. $to = $file;
  83. } else {
  84. $pos = strrpos($file, '/');
  85. $to = substr($file, 0, $pos);
  86. }
  87. for ($rel = '';; $rel .= '../') {
  88. if ($from === $to) {
  89. break;
  90. }
  91. if ($from === dirname($from)) {
  92. $rel .= substr($to, 1);
  93. break;
  94. }
  95. if (!strncmp($from . '/', $to, strlen($from) + 1)) {
  96. $rel .= substr($to, strlen($from) + 1);
  97. break;
  98. }
  99. $from = dirname($from);
  100. }
  101. if (substr($rel, -1, 1) === '/') {
  102. $rel = substr($rel, 0, -1);
  103. }
  104. if ($pos !== false) {
  105. $rel .= substr($file, $pos);
  106. }
  107. return $rel;
  108. }
  109. }
  110. /**
  111. * XS 错误异常类定义, XS 所有操作过程发生错误均抛出该实例
  112. *
  113. * @author hightman <hightman@twomice.net>
  114. * @version 1.0.0
  115. * @package XS
  116. */
  117. class XSErrorException extends XSException
  118. {
  119. private $_file, $_line;
  120. /**
  121. * 构造函数
  122. * 将 $file, $line 记录到私有属性在 __toString 中使用
  123. * @param int $code 出错代码
  124. * @param string $message 出错信息
  125. * @param string $file 出错所在文件
  126. * @param int $line 出错所在的行数
  127. * @param Exception $previous
  128. */
  129. public function __construct($code, $message, $file, $line, $previous = null)
  130. {
  131. $this->_file = $file;
  132. $this->_line = $line;
  133. if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
  134. parent::__construct($message, $code, $previous);
  135. } else {
  136. parent::__construct($message, $code);
  137. }
  138. }
  139. /**
  140. * 将类对象转换成字符串
  141. * @return string 异常的简要描述信息
  142. */
  143. public function __toString()
  144. {
  145. $string = '[' . __CLASS__ . '] ' . $this->getRelPath($this->_file) . '(' . $this->_line . '): ';
  146. $string .= $this->getMessage() . '(' . $this->getCode() . ')';
  147. return $string;
  148. }
  149. }
  150. /**
  151. * XS 组件基类
  152. * 封装一些魔术方法, 以实现支持模拟属性
  153. *
  154. * 模拟属性通过定义读取函数, 写入函数来实现, 允许两者缺少其中一个
  155. * 这类属性可以跟正常定义的属性一样存取, 但是这类属性名称不区分大小写. 例:
  156. * <pre>
  157. * $a = $obj->text; // $a 值等于 $obj->getText() 的返回值
  158. * $obj->text = $a; // 等同事调用 $obj->setText($a)
  159. * </pre>
  160. *
  161. * @author hightman <hightman@twomice.net>
  162. * @version 1.0.0
  163. * @package XS
  164. */
  165. class XSComponent
  166. {
  167. /**
  168. * 魔术方法 __get
  169. * 取得模拟属性的值, 内部实际调用 getXxx 方法的返回值
  170. * @param string $name 属性名称
  171. * @return mixed 属性值
  172. * @throw XSException 属性不存在或不可读时抛出异常
  173. */
  174. public function __get($name)
  175. {
  176. $getter = 'get' . $name;
  177. if (method_exists($this, $getter)) {
  178. return $this->$getter();
  179. }
  180. // throw exception
  181. $msg = method_exists($this, 'set' . $name) ? 'Write-only' : 'Undefined';
  182. $msg .= ' property: ' . get_class($this) . '::$' . $name;
  183. throw new XSException($msg);
  184. }
  185. /**
  186. * 魔术方法 __set
  187. * 设置模拟属性的值, 内部实际是调用 setXxx 方法
  188. * @param string $name 属性名称
  189. * @param mixed $value 属性值
  190. * @throw XSException 属性不存在或不可写入时抛出异常
  191. */
  192. public function __set($name, $value)
  193. {
  194. $setter = 'set' . $name;
  195. if (method_exists($this, $setter)) {
  196. return $this->$setter($value);
  197. }
  198. // throw exception
  199. $msg = method_exists($this, 'get' . $name) ? 'Read-only' : 'Undefined';
  200. $msg .= ' property: ' . get_class($this) . '::$' . $name;
  201. throw new XSException($msg);
  202. }
  203. /**
  204. * 魔术方法 __isset
  205. * 判断模拟属性是否存在并可读取
  206. * @param string $name 属性名称
  207. * @return bool 若存在为 true, 反之为 false
  208. */
  209. public function __isset($name)
  210. {
  211. return method_exists($this, 'get' . $name);
  212. }
  213. /**
  214. * 魔术方法 __unset
  215. * 删除、取消模拟属性, 相当于设置属性值为 null
  216. * @param string $name 属性名称
  217. */
  218. public function __unset($name)
  219. {
  220. $this->__set($name, null);
  221. }
  222. }
  223. /**
  224. * XS 搜索项目主类
  225. *
  226. * @property XSFieldScheme $scheme 当前在用的字段方案
  227. * @property string $defaultCharset 默认字符集编码
  228. * @property-read string $name 项目名称
  229. * @property-read XSIndex $index 索引操作对象
  230. * @property-read XSSearch $search 搜索操作对象
  231. * @property-read XSFieldMeta $idField 主键字段
  232. * @author hightman <hightman@twomice.net>
  233. * @version 1.0.0
  234. * @package XS
  235. */
  236. class XS extends XSComponent
  237. {
  238. /**
  239. * @var XSIndex 索引操作对象
  240. */
  241. private $_index;
  242. /**
  243. * @var XSSearch 搜索操作对象
  244. */
  245. private $_search;
  246. /**
  247. * @var XSServer scws 分词服务器
  248. */
  249. private $_scws;
  250. /**
  251. * @var XSFieldScheme 当前字段方案
  252. */
  253. private $_scheme, $_bindScheme;
  254. private $_config;
  255. /**
  256. * @var XS 最近创建的 XS 对象
  257. */
  258. private static $_lastXS;
  259. /**
  260. * 构造函数
  261. * 特别说明一个小技巧, 参数 $file 可以直接是配置文件的内容, 还可以是仅仅是文件名,
  262. * 如果只是文件名会自动查找 XS_LIB_ROOT/../app/$file.ini
  263. * @param string $file 要加载的项目配置文件
  264. */
  265. public function __construct($file)
  266. {
  267. if (strlen($file) < 255 && !is_file($file)) {
  268. $appRoot = getenv('XS_APP_ROOT');
  269. if ($appRoot === false) {
  270. $appRoot = defined('XS_APP_ROOT') ? XS_APP_ROOT : XS_LIB_ROOT . '/../app';
  271. }
  272. $file2 = $appRoot . '/' . $file . '.ini';
  273. if (is_file($file2)) {
  274. $file = $file2;
  275. }
  276. }
  277. $this->loadIniFile($file);
  278. self::$_lastXS = $this;
  279. }
  280. /**
  281. * 析构函数
  282. * 由于对象交叉引用, 如需提前销毁对象, 请强制调用该函数
  283. */
  284. public function __destruct()
  285. {
  286. $this->_index = null;
  287. $this->_search = null;
  288. }
  289. /**
  290. * 获取最新的 XS 实例
  291. * @return XS 最近创建的 XS 对象
  292. */
  293. public static function getLastXS()
  294. {
  295. return self::$_lastXS;
  296. }
  297. /**
  298. * 获取当前在用的字段方案
  299. * 通用于搜索结果文档和修改、添加的索引文档
  300. * @return XSFieldScheme 当前字段方案
  301. */
  302. public function getScheme()
  303. {
  304. return $this->_scheme;
  305. }
  306. /**
  307. * 设置当前在用的字段方案
  308. * @param XSFieldScheme $fs 一个有效的字段方案对象
  309. * @throw XSException 无效方案则直接抛出异常
  310. */
  311. public function setScheme(XSFieldScheme $fs)
  312. {
  313. $fs->checkValid(true);
  314. $this->_scheme = $fs;
  315. if ($this->_search !== null) {
  316. $this->_search->markResetScheme();
  317. }
  318. }
  319. /**
  320. * 还原字段方案为项目绑定方案
  321. */
  322. public function restoreScheme()
  323. {
  324. if ($this->_scheme !== $this->_bindScheme) {
  325. $this->_scheme = $this->_bindScheme;
  326. if ($this->_search !== null) {
  327. $this->_search->markResetScheme(true);
  328. }
  329. }
  330. }
  331. /**
  332. * @return array 获取配置原始数据
  333. */
  334. public function getConfig()
  335. {
  336. return $this->_config;
  337. }
  338. /**
  339. * 获取当前项目名称
  340. * @return string 当前项目名称
  341. */
  342. public function getName()
  343. {
  344. return $this->_config['project.name'];
  345. }
  346. /**
  347. * 修改当前项目名称
  348. * 注意,必须在 {@link getSearch} 和 {@link getIndex} 前调用才能起作用
  349. * @param string $name 项目名称
  350. */
  351. public function setName($name)
  352. {
  353. $this->_config['project.name'] = $name;
  354. }
  355. /**
  356. * 获取项目的默认字符集
  357. * @return string 默认字符集(已大写)
  358. */
  359. public function getDefaultCharset()
  360. {
  361. return isset($this->_config['project.default_charset']) ?
  362. strtoupper($this->_config['project.default_charset']) : 'UTF-8';
  363. }
  364. /**
  365. * 改变项目的默认字符集
  366. * @param string $charset 修改后的字符集
  367. */
  368. public function setDefaultCharset($charset)
  369. {
  370. $this->_config['project.default_charset'] = strtoupper($charset);
  371. }
  372. /**
  373. * 获取索引操作对象
  374. * @return XSIndex 索引操作对象
  375. */
  376. public function getIndex()
  377. {
  378. if ($this->_index === null) {
  379. $adds = array();
  380. $conn = isset($this->_config['server.index']) ? $this->_config['server.index'] : 8383;
  381. if (($pos = strpos($conn, ';')) !== false) {
  382. $adds = explode(';', substr($conn, $pos + 1));
  383. $conn = substr($conn, 0, $pos);
  384. }
  385. $this->_index = new XSIndex($conn, $this);
  386. $this->_index->setTimeout(0);
  387. foreach ($adds as $conn) {
  388. $conn = trim($conn);
  389. if ($conn !== '') {
  390. $this->_index->addServer($conn)->setTimeout(0);
  391. }
  392. }
  393. }
  394. return $this->_index;
  395. }
  396. /**
  397. * 获取搜索操作对象
  398. * @return XSSearch 搜索操作对象
  399. */
  400. public function getSearch()
  401. {
  402. if ($this->_search === null) {
  403. $conns = array();
  404. if (!isset($this->_config['server.search'])) {
  405. $conns[] = 8384;
  406. } else {
  407. foreach (explode(';', $this->_config['server.search']) as $conn) {
  408. $conn = trim($conn);
  409. if ($conn !== '') {
  410. $conns[] = $conn;
  411. }
  412. }
  413. }
  414. if (count($conns) > 1) {
  415. shuffle($conns);
  416. }
  417. for ($i = 0; $i < count($conns); $i++) {
  418. try {
  419. $this->_search = new XSSearch($conns[$i], $this);
  420. $this->_search->setCharset($this->getDefaultCharset());
  421. return $this->_search;
  422. } catch (XSException $e) {
  423. if (($i + 1) === count($conns)) {
  424. throw $e;
  425. }
  426. }
  427. }
  428. }
  429. return $this->_search;
  430. }
  431. /**
  432. * 创建 scws 分词连接
  433. * @return XSServer 分词服务器
  434. */
  435. public function getScwsServer()
  436. {
  437. if ($this->_scws === null) {
  438. $conn = isset($this->_config['server.search']) ? $this->_config['server.search'] : 8384;
  439. $this->_scws = new XSServer($conn, $this);
  440. }
  441. return $this->_scws;
  442. }
  443. /**
  444. * 获取当前主键字段
  445. * @return XSFieldMeta 类型为 ID 的字段
  446. * @see XSFieldScheme::getFieldId
  447. */
  448. public function getFieldId()
  449. {
  450. return $this->_scheme->getFieldId();
  451. }
  452. /**
  453. * 获取当前标题字段
  454. * @return XSFieldMeta 类型为 TITLE 的字段
  455. * @see XSFieldScheme::getFieldTitle
  456. */
  457. public function getFieldTitle()
  458. {
  459. return $this->_scheme->getFieldTitle();
  460. }
  461. /**
  462. * 获取当前内容字段
  463. * @return XSFieldMeta 类型为 BODY 的字段
  464. * @see XSFieldScheme::getFieldBody
  465. */
  466. public function getFieldBody()
  467. {
  468. return $this->_scheme->getFieldBody();
  469. }
  470. /**
  471. * 获取项目字段元数据
  472. * @param mixed $name 字段名称(string) 或字段序号(vno, int)
  473. * @param bool $throw 当字段不存在时是否抛出异常, 默认为 true
  474. * @return XSFieldMeta 字段元数据对象
  475. * @throw XSException 当字段不存在并且参数 throw 为 true 时抛出异常
  476. * @see XSFieldScheme::getField
  477. */
  478. public function getField($name, $throw = true)
  479. {
  480. return $this->_scheme->getField($name, $throw);
  481. }
  482. /**
  483. * 获取项目所有字段结构设置
  484. * @return XSFieldMeta[]
  485. */
  486. public function getAllFields()
  487. {
  488. return $this->_scheme->getAllFields();
  489. }
  490. /**
  491. * 智能加载类库文件
  492. * 要求以 Name.class.php 命名并与本文件存放在同一目录, 如: XSTokenizerXxx.class.php
  493. * @param string $name 类的名称
  494. */
  495. public static function autoload($name)
  496. {
  497. $file = XS_LIB_ROOT . '/' . $name . '.class.php';
  498. if (file_exists($file)) {
  499. require_once $file;
  500. }
  501. }
  502. /**
  503. * 字符集转换
  504. * 要求安装有 mbstring, iconv 中的一种
  505. * @param mixed $data 需要转换的数据, 支持 string 和 array, 数组会自动递归转换
  506. * @param string $to 转换后的字符集
  507. * @param string $from 转换前的字符集
  508. * @return mixed 转换后的数据
  509. * @throw XSEXception 如果没有合适的转换函数抛出异常
  510. */
  511. public static function convert($data, $to, $from)
  512. {
  513. // need not convert
  514. if ($to == $from) {
  515. return $data;
  516. }
  517. // array traverse
  518. if (is_array($data)) {
  519. foreach ($data as $key => $value) {
  520. $data[$key] = self::convert($value, $to, $from);
  521. }
  522. return $data;
  523. }
  524. // string contain 8bit characters
  525. if (is_string($data) && preg_match('/[\x81-\xfe]/', $data)) {
  526. // mbstring, iconv, throw ...
  527. if (function_exists('mb_convert_encoding')) {
  528. return mb_convert_encoding($data, $to, $from);
  529. } elseif (function_exists('iconv')) {
  530. return iconv($from, $to . '//TRANSLIT', $data);
  531. } else {
  532. throw new XSException('Cann\'t find the mbstring or iconv extension to convert encoding');
  533. }
  534. }
  535. return $data;
  536. }
  537. /**
  538. * 计算经纬度距离
  539. * @param float $lon1 原点经度
  540. * @param float $lat1 原点纬度
  541. * @param float $lon2 目标点经度
  542. * @param float $lat2 目标点纬度
  543. * @return float 两点大致距离,单位:米
  544. */
  545. public static function geoDistance($lon1, $lat1, $lon2, $lat2)
  546. {
  547. $dx = $lon1 - $lon2;
  548. $dy = $lat1 - $lat2;
  549. $b = ($lat1 + $lat2) / 2;
  550. $lx = 6367000.0 * deg2rad($dx) * cos(deg2rad($b));
  551. $ly = 6367000.0 * deg2rad($dy);
  552. return sqrt($lx * $lx + $ly * $ly);
  553. }
  554. /**
  555. * 解析INI配置文件
  556. * 由于 PHP 自带的 parse_ini_file 存在一些不兼容,故自行简易实现
  557. * @param string $data 文件内容
  558. * @return array 解析后的结果
  559. */
  560. private function parseIniData($data)
  561. {
  562. $ret = array();
  563. $cur = &$ret;
  564. $lines = explode("\n", $data);
  565. foreach ($lines as $line) {
  566. if ($line === '' || $line[0] == ';' || $line[0] == '#') {
  567. continue;
  568. }
  569. $line = trim($line);
  570. if ($line === '') {
  571. continue;
  572. }
  573. if ($line[0] === '[' && substr($line, -1, 1) === ']') {
  574. $sec = substr($line, 1, -1);
  575. $ret[$sec] = array();
  576. $cur = &$ret[$sec];
  577. continue;
  578. }
  579. if (($pos = strpos($line, '=')) === false) {
  580. continue;
  581. }
  582. $key = trim(substr($line, 0, $pos));
  583. $value = trim(substr($line, $pos + 1), " '\t\"");
  584. $cur[$key] = $value;
  585. }
  586. return $ret;
  587. }
  588. /**
  589. * 加载项目配置文件
  590. * @param string $file 配置文件路径
  591. * @throw XSException 出错时抛出异常
  592. * @see XSFieldMeta::fromConfig
  593. */
  594. private function loadIniFile($file)
  595. {
  596. // check cache
  597. $cache = false;
  598. $cache_write = '';
  599. if (strlen($file) < 255 && file_exists($file)) {
  600. $cache_key = md5(__CLASS__ . '::ini::' . realpath($file));
  601. if (function_exists('apc_fetch')) {
  602. $cache = apc_fetch($cache_key);
  603. $cache_write = 'apc_store';
  604. } elseif (function_exists('xcache_get') && php_sapi_name() !== 'cli') {
  605. $cache = xcache_get($cache_key);
  606. $cache_write = 'xcache_set';
  607. } elseif (function_exists('eaccelerator_get')) {
  608. $cache = eaccelerator_get($cache_key);
  609. $cache_write = 'eaccelerator_put';
  610. }
  611. if ($cache && isset($cache['mtime']) && isset($cache['scheme'])
  612. && filemtime($file) <= $cache['mtime']) {
  613. // cache HIT
  614. $this->_scheme = $this->_bindScheme = unserialize($cache['scheme']);
  615. $this->_config = $cache['config'];
  616. return;
  617. }
  618. $data = file_get_contents($file);
  619. } else {
  620. // parse ini string
  621. $data = $file;
  622. $file = substr(md5($file), 8, 8) . '.ini';
  623. }
  624. // parse ini file
  625. $this->_config = $this->parseIniData($data);
  626. if ($this->_config === false) {
  627. throw new XSException('Failed to parse project config file/string: \'' . substr($file, 0, 10) . '...\'');
  628. }
  629. // create the scheme object
  630. $scheme = new XSFieldScheme;
  631. foreach ($this->_config as $key => $value) {
  632. if (is_array($value)) {
  633. $scheme->addField($key, $value);
  634. }
  635. }
  636. $scheme->checkValid(true);
  637. // load default config
  638. if (!isset($this->_config['project.name'])) {
  639. $this->_config['project.name'] = basename($file, '.ini');
  640. }
  641. // save to cache
  642. $this->_scheme = $this->_bindScheme = $scheme;
  643. if ($cache_write != '') {
  644. $cache['mtime'] = filemtime($file);
  645. $cache['scheme'] = serialize($this->_scheme);
  646. $cache['config'] = $this->_config;
  647. call_user_func($cache_write, $cache_key, $cache);
  648. }
  649. }
  650. }
  651. /**
  652. * Add autoload handler to search classes on current directory
  653. * Class file should be named as Name.class.php
  654. */
  655. spl_autoload_register('XS::autoload', true, true);
  656. /**
  657. * 修改默认的错误处理函数
  658. * 把发生的错误修改为抛出异常, 方便统一处理
  659. */
  660. function xsErrorHandler($errno, $error, $file, $line)
  661. {
  662. if (($errno & ini_get('error_reporting')) && !strncmp($file, XS_LIB_ROOT, strlen(XS_LIB_ROOT))) {
  663. throw new XSErrorException($errno, $error, $file, $line);
  664. }
  665. return false;
  666. }
  667. set_error_handler('xsErrorHandler');