XSDataSource.class.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899
  1. <?php
  2. /**
  3. * XSDataSource 批量索引数据源定义文件
  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. /**
  12. * 索引数据源抽象基类
  13. * 此部分代码仅用于 indexer 工具程序
  14. *
  15. * @author hightman <hightman@twomice.net>
  16. * @version 1.0.0
  17. * @package XS.utilf
  18. */
  19. abstract class XSDataSource
  20. {
  21. protected $type, $arg;
  22. private $dataList, $dataPos;
  23. /**
  24. * 构造函数
  25. * @param mixed $arg 对象参数, 常为 SQL 语句或要导入的文件路径
  26. */
  27. public function __construct($type, $arg)
  28. {
  29. $this->type = $type;
  30. $this->arg = $arg;
  31. $this->init();
  32. }
  33. /**
  34. * 取得数据源对象实例
  35. * @param string $type 数据源类型, 如: mysql://.., json, csv ...
  36. * @param mixed $arg 建立对象的参数, 如 SQL 语句, JSON/CSV 文件
  37. * @return XSDataSource 初始化完毕的数据源对象
  38. */
  39. public static function instance($type, $arg = null)
  40. {
  41. $type2 = ($pos = strpos($type, ':')) ? 'database' : $type;
  42. $class = 'XS' . ucfirst(strtolower($type2)) . 'DataSource';
  43. if (!class_exists($class))
  44. throw new XSException("Undefined data source type: `$type2'");
  45. return new $class($type, $arg);
  46. }
  47. /**
  48. * 从数据源中提取一条数据
  49. * 实际使用时, 一般是循环调用此函数提取数据, 每条数据是由字段名为键的关联数组
  50. * <pre>
  51. * while ($ds->getData() !== false)
  52. * {
  53. * ...
  54. * }
  55. * </pre>
  56. * @return mixed 返回一条完整数据, 若无数据则返回 false
  57. */
  58. final public function getData()
  59. {
  60. if ($this->dataPos === null || $this->dataPos === count($this->dataList))
  61. {
  62. $this->dataPos = 0;
  63. $this->dataList = $this->getDataList();
  64. if (!is_array($this->dataList) || count($this->dataList) === 0)
  65. {
  66. $this->deinit();
  67. $this->dataList = $this->dataPos = null;
  68. return false;
  69. }
  70. }
  71. $data = $this->dataList[$this->dataPos];
  72. $this->dataPos++;
  73. return $data;
  74. }
  75. /**
  76. * 取得数据源的准确字符集
  77. * 如不能确定字符集, 请直接返回 false
  78. * @return string 字符集名称
  79. */
  80. public function getCharset()
  81. {
  82. return false;
  83. }
  84. /**
  85. * 执行数据提取的准备工作
  86. * 将自动在第一次提取数据前调用, 请在具体的数据源重载此函数
  87. */
  88. protected function init()
  89. {
  90. }
  91. /**
  92. * 执行数据提取完毕后的清理工作
  93. * 将自动在没有更多数据供提取时调用此函数, 请在具体的数据源重载此函数
  94. */
  95. protected function deinit()
  96. {
  97. }
  98. /**
  99. * 从数据源中提取若干条数据
  100. * 必须在数据源中定义此函数, 返回值必须是各条数据的数组
  101. * @return array
  102. */
  103. protected function getDataList()
  104. {
  105. return false;
  106. }
  107. }
  108. /**
  109. * SQL 数据库源
  110. *
  111. * @author hightman <hightman@twomice.net>
  112. * @version 1.0.0
  113. * @package XS.util
  114. */
  115. class XSDatabaseDataSource extends XSDataSource
  116. {
  117. const PLIMIT = 1000;
  118. private $sql, $offset, $limit;
  119. private $db; /* @var $db XSDatabase */
  120. /**
  121. * 返回数据库输出字符集
  122. * @return mixed 如果数据库不支持 UTF-8 转换则返回 false
  123. */
  124. public function getCharset()
  125. {
  126. if ($this->db->setUtf8())
  127. return 'UTF-8';
  128. return parent::getCharset();
  129. }
  130. protected function init()
  131. {
  132. if (strstr($this->type, 'sqlite'))
  133. {
  134. $pos = strpos($this->type, ':');
  135. $param = array('scheme' => substr($this->type, 0, $pos));
  136. $param['path'] = substr($this->type, $pos + (substr($this->type, $pos + 1, 2) === '//' ? 3 : 1));
  137. }
  138. else if (!($param = parse_url($this->type)))
  139. {
  140. throw new XSException('Wrong format of DB connection parameter');
  141. }
  142. else
  143. {
  144. if (isset($param['user']))
  145. $param['user'] = urldecode($param['user']);
  146. if (isset($param['pass']))
  147. $param['pass'] = urldecode($param['pass']);
  148. $param['path'] = isset($param['path']) ? trim($param['path'], '/') : '';
  149. if (empty($param['path']))
  150. throw new XSException('Not contain dbname of DB connection parameter');
  151. if (($pos = strpos($param['path'], '/')) === false)
  152. $param['dbname'] = $param['path'];
  153. else
  154. {
  155. $param['dbname'] = substr($param['path'], 0, $pos);
  156. $param['table'] = substr($param['path'], $pos + 1);
  157. }
  158. }
  159. // get driver
  160. $driver = self::getDriverName($param['scheme']);
  161. $class = 'XSDatabase' . ucfirst($driver);
  162. if (!class_exists($class))
  163. throw new XSException("Undefined database driver: '$driver'");
  164. $this->db = new $class;
  165. $this->db->connect($param);
  166. // set SQL & parse limit/offset
  167. $this->limit = $this->offset = 0;
  168. $sql = $this->arg;
  169. if (empty($sql))
  170. {
  171. if (!isset($param['table']))
  172. throw new XSException('Not specified any query SQL or db table');
  173. $sql = 'SELECT * FROM ' . $param['table'];
  174. }
  175. else if (preg_match('/ limit\s+(\d+)(?:\s*,\s*(\d+)|\s+offset\s+(\d+))?\s*$/i', $sql, $match))
  176. {
  177. if (isset($match[3])) // LIMIT xxx OFFSET yyy
  178. {
  179. $this->offset = intval($match[3]);
  180. $this->limit = intval($match[1]);
  181. }
  182. else if (isset($match[2])) // LIMIT yyy, xxx
  183. {
  184. $this->offset = intval($match[1]);
  185. $this->limit = intval($match[2]);
  186. }
  187. else // lIMIT xxx
  188. {
  189. $this->limit = intval($match[1]);
  190. }
  191. $sql = substr($sql, 0, strlen($sql) - strlen($match[0]));
  192. }
  193. $this->sql = $sql;
  194. if ($this->limit == 0)
  195. {
  196. $sql = preg_replace('/SELECT\s+.+?FROM\s/i', 'SELECT COUNT(*) AS count FROM ', $sql);
  197. $res = $this->db->query1($sql);
  198. $this->limit = $res['count'] - $this->offset;
  199. }
  200. }
  201. protected function deinit()
  202. {
  203. $this->db->close();
  204. }
  205. /**
  206. * 返回一批数据
  207. * @return 结果数组, 没有更多数据时返回 false
  208. */
  209. protected function getDataList()
  210. {
  211. if ($this->limit <= 0)
  212. return false;
  213. $sql = $this->sql . ' LIMIT ' . min(self::PLIMIT, $this->limit) . ' OFFSET ' . $this->offset;
  214. $this->limit -= self::PLIMIT;
  215. $this->offset += self::PLIMIT;
  216. return $this->db->query($sql);
  217. }
  218. /**
  219. * 取解数据连接驱动名
  220. */
  221. private static function getDriverName($scheme)
  222. {
  223. $name = strtr(strtolower($scheme), '.-', '__');
  224. if ($name == 'mysql' && !function_exists('mysql_connect'))
  225. {
  226. if (class_exists('mysqli'))
  227. $name = 'mysqli';
  228. else if (extension_loaded('pdo_mysql'))
  229. $name = 'pdo_mysql';
  230. }
  231. if ($name == 'sqlite' && !function_exists('sqlite_open'))
  232. {
  233. if (class_exists('sqlite3'))
  234. $name = 'sqlite3';
  235. else if (extension_loaded('pdo_sqlite'))
  236. $name = 'pdo_sqlite';
  237. }
  238. if ($name == 'sqlite3' && !class_exists('sqlite3') && extension_loaded('pdo_sqlite'))
  239. $name = 'pdo_sqlite';
  240. if (substr($name, 0, 4) != 'pdo_' && extension_loaded('pdo_' . $name))
  241. $name = 'pdo_' . $name;
  242. return $name;
  243. }
  244. }
  245. /**
  246. * JSON 数据源
  247. * 要求以 \n (换行符) 分割, 每行为一条完整的 json 数据
  248. *
  249. * @author hightman <hightman@twomice.net>
  250. * @version 1.0.0
  251. * @package XS.util
  252. */
  253. class XSJsonDataSource extends XSDataSource
  254. {
  255. private $fd, $line;
  256. protected function init()
  257. {
  258. $file = $this->arg;
  259. if (empty($file))
  260. {
  261. echo "WARNING: input file not specified, read data from <STDIN>\n";
  262. $file = 'php://stdin';
  263. }
  264. if (!($this->fd = fopen($file, 'r')))
  265. throw new XSException("Can not open input file: '$file'");
  266. $this->line = 0;
  267. }
  268. protected function deinit()
  269. {
  270. if ($this->fd)
  271. {
  272. fclose($this->fd);
  273. $this->fd = null;
  274. }
  275. }
  276. protected function getDataList()
  277. {
  278. // read line (check to timeout?)
  279. $line = '';
  280. while (true)
  281. {
  282. $buf = fgets($this->fd, 8192);
  283. if ($buf === false || strlen($buf) === 0)
  284. break;
  285. $line .= $buf;
  286. if (strlen($buf) < 8191 || substr($buf, - 1, 1) === "\n")
  287. break;
  288. }
  289. // empty line (end of file)
  290. if (empty($line))
  291. {
  292. echo "INFO: reach end of the file, total lines: " . $this->line . "\n";
  293. return false;
  294. }
  295. // try to decode the line
  296. $this->line++;
  297. $line = rtrim($line, "\r\n");
  298. if (strlen($line) === 0)
  299. {
  300. echo "WARNING: empty line #" . $this->line . "\n";
  301. return $this->getDataList();
  302. }
  303. $item = json_decode($line, true);
  304. if (!is_array($item) || count($item) === 0)
  305. {
  306. switch (json_last_error())
  307. {
  308. case JSON_ERROR_DEPTH:
  309. $error = ' - Maximum stack depth exceeded';
  310. break;
  311. case JSON_ERROR_CTRL_CHAR:
  312. $error = ' - Unexpected control character found';
  313. break;
  314. case JSON_ERROR_SYNTAX:
  315. $error = ' - Syntax error, malformed JSON';
  316. break;
  317. default :
  318. $error = (count($item) === 0 ? ' - Empty array' : '');
  319. break;
  320. }
  321. echo "WARNING: invalid line #" . $this->line . $error . "\n";
  322. return $this->getDataList();
  323. }
  324. return array($item);
  325. }
  326. }
  327. /**
  328. * CSV 数据源
  329. * 可在文件开头指定字段(必须是有效字段), 否则将默认按照 {@link XS} 项目字段顺序填充
  330. *
  331. * @author hightman <hightman@twomice.net>
  332. * @version 1.0.0
  333. * @package XS.util
  334. */
  335. class XSCsvDataSource extends XSDataSource
  336. {
  337. private $delim = ',';
  338. protected function init()
  339. {
  340. $file = $this->arg;
  341. if (empty($file))
  342. {
  343. echo "WARNING: input file not specified, read data from <STDIN>\n";
  344. $file = 'php://stdin';
  345. }
  346. if (!($this->fd = fopen($file, 'r')))
  347. throw new XSException("Can not open input file: '$file'");
  348. $this->line = 0;
  349. if (isset($_SERVER['XS_CSV_DELIMITER']))
  350. $this->delim = $_SERVER['XS_CSV_DELIMITER'];
  351. }
  352. protected function deinit()
  353. {
  354. if ($this->fd)
  355. {
  356. fclose($this->fd);
  357. $this->fd = null;
  358. }
  359. }
  360. protected function getDataList()
  361. {
  362. if (($item = fgetcsv($this->fd, 0, $this->delim)) === false)
  363. {
  364. echo "INFO: reach end of file or error occured, total lines: " . $this->line . "\n";
  365. return false;
  366. }
  367. $this->line++;
  368. if (count($item) === 1 && is_null($item[0]))
  369. {
  370. echo "WARNING: invalid csv line #" . $this->line . "\n";
  371. return $this->getDataList();
  372. }
  373. return array($item);
  374. }
  375. }
  376. /**
  377. * 数据库操作基类
  378. * 定义了 SQL 数据库源的四个基本操作: connect/query/close/setUtf8
  379. *
  380. * @author hightman <hightman@twomice.net>
  381. * @version 1.0.0
  382. * @package XS.util.db
  383. */
  384. abstract class XSDatabase
  385. {
  386. /**
  387. * 连接数据库
  388. * @param array $param 连接参数, 采用 parse_url 解析, 可能包含: scheme,user,pass,host,path,table,dbname ...
  389. */
  390. abstract public function connect($param);
  391. /**
  392. * 关闭数据库连接
  393. */
  394. abstract public function close();
  395. /**
  396. * 查询 SQL 语句
  397. * @return mixed 非 SELECT 语句返回执行结果(true/false), SELECT 语句返回所有结果行的数组
  398. */
  399. abstract public function query($sql);
  400. /**
  401. * 设置数据库字符集为 UTF-8
  402. * @return bool 如果数据库能直接输出 UTF-8 编码则返回 true 否则返回 false
  403. */
  404. public function setUtf8()
  405. {
  406. return false;
  407. }
  408. /**
  409. * 查询数据库首行
  410. * @param string $sql
  411. * @return 查询结果首行, 失败或无数据则返回 false
  412. */
  413. public function query1($sql)
  414. {
  415. $sql = preg_replace('/ limit\s+(\d+)(?:\s*,\s*(\d+)|\s+offset\s+(\d+))?\s*$/i', '', $sql);
  416. $sql .= ' LIMIT 1';
  417. $res = $this->query($sql);
  418. return (is_array($res) && isset($res[0])) ? $res[0] : false;
  419. }
  420. }
  421. /**
  422. * 使用传统 MySQL 扩展
  423. *
  424. * @author hightman <hightman@twomice.net>
  425. * @version 1.0.0
  426. * @package XS.util.db
  427. */
  428. class XSDatabaseMySQL extends XSDatabase
  429. {
  430. private $link;
  431. /**
  432. * 连接数据库
  433. * @param array $param 连接参数, 包含: user,pass,host,table,dbname ...
  434. */
  435. public function connect($param)
  436. {
  437. $host = isset($param['host']) ? $param['host'] : ini_get('mysql.default_host');
  438. $host .= ( (isset($param['port']) && $param['port'] != 3306) ? ':' . $param['port'] : '');
  439. $user = isset($param['user']) ? $param['user'] : ini_get('mysql.default_user');
  440. $pass = isset($param['pass']) ? $param['pass'] : ini_get('mysql.default_pw');
  441. if (($this->link = mysql_connect($host, $user, $pass)) === false)
  442. throw new XSException("Can not connect to mysql server: '$uesr@$host'");
  443. if (!mysql_select_db($param['dbname'], $this->link))
  444. {
  445. $this->close();
  446. throw new XSException("Can not switch to database name: '{$param['dbname']}'");
  447. }
  448. $this->setUtf8();
  449. }
  450. /**
  451. * 关闭数据库连接
  452. */
  453. public function close()
  454. {
  455. if ($this->link)
  456. {
  457. mysql_close($this->link);
  458. $this->link = null;
  459. }
  460. }
  461. /**
  462. * 执行 SQL 语句查询
  463. * @param string $sql 要执行的 SQL 语句
  464. * @return mixed
  465. */
  466. public function query($sql)
  467. {
  468. //echo "[DEBUG] SQL: $sql\n";
  469. $res = mysql_query($sql, $this->link);
  470. if ($res === false)
  471. throw new XSException('MySQL ERROR(#' . mysql_errno($this->link) . '): ' . mysql_error($this->link));
  472. if (!is_resource($res))
  473. $ret = $res;
  474. else
  475. {
  476. $ret = array();
  477. while ($tmp = mysql_fetch_assoc($res))
  478. {
  479. $ret[] = $tmp;
  480. }
  481. mysql_free_result($res);
  482. }
  483. return $ret;
  484. }
  485. /**
  486. * 将输出字符集设置为 UTF-8
  487. * @return bool MySQL 自 4.1.0 起支持字符集
  488. */
  489. public function setUtf8()
  490. {
  491. if (version_compare(mysql_get_server_info($this->link), '4.1.0', '>='))
  492. return @mysql_query("SET NAMES utf8", $this->link);
  493. return false;
  494. }
  495. }
  496. /**
  497. * 面向对象的 MySQLI 扩展
  498. *
  499. * @author hightman <hightman@twomice.net>
  500. * @version 1.0.0
  501. * @package XS.util.db
  502. */
  503. class XSDatabaseMySQLI extends XSDatabase
  504. {
  505. private $obj;
  506. /**
  507. * 连接数据库
  508. * @param array $param 连接参数, 包含: user,pass,host,table,dbname ...
  509. */
  510. public function connect($param)
  511. {
  512. $host = isset($param['host']) ? $param['host'] : ini_get('mysqli.default_host');
  513. $user = isset($param['user']) ? $param['user'] : ini_get('mysqli.default_user');
  514. $pass = isset($param['pass']) ? $param['pass'] : ini_get('mysqli.default_pw');
  515. $port = isset($param['port']) ? $param['port'] : ini_get('mysqli.default_port');
  516. $this->obj = new mysqli($host, $user, $pass, '', $port);
  517. if ($this->obj->connect_error)
  518. throw new XSException("Can not connect to mysql server: '$uesr@$host'");
  519. if (!$this->obj->select_db($param['dbname']))
  520. {
  521. $this->close();
  522. throw new XSException("Can not switch to database name: '{$param['dbname']}'");
  523. }
  524. $this->setUtf8();
  525. }
  526. /**
  527. * 关闭数据库连接
  528. */
  529. public function close()
  530. {
  531. if ($this->obj)
  532. {
  533. $this->obj->close();
  534. $this->obj = null;
  535. }
  536. }
  537. /**
  538. * 执行 SQL 语句查询
  539. * @param string $sql 要执行的 SQL 语句
  540. * @return mixed
  541. */
  542. public function query($sql)
  543. {
  544. //echo "[DEBUG] SQL: $sql\n";
  545. $res = $this->obj->query($sql);
  546. if ($res === false)
  547. throw new XSException('MySQL ERROR(#' . $this->obj->error . '): ' . $this->obj->errno);
  548. if (!is_object($res))
  549. $ret = $res;
  550. else
  551. {
  552. $ret = array();
  553. while ($tmp = $res->fetch_assoc())
  554. {
  555. $ret[] = $tmp;
  556. }
  557. $res->free();
  558. }
  559. return $ret;
  560. }
  561. /**
  562. * 将输出字符集设置为 UTF-8
  563. * @return bool 始终返回 true
  564. */
  565. public function setUtf8()
  566. {
  567. $this->obj->set_charset('utf8');
  568. return true;
  569. }
  570. }
  571. /**
  572. * 使用传统的 SQLite 扩展
  573. *
  574. * @author hightman <hightman@twomice.net>
  575. * @version 1.0.0
  576. * @package XS.util.db
  577. */
  578. class XSDatabaseSQLite extends XSDatabase
  579. {
  580. private $link;
  581. /**
  582. * 打开数据库
  583. * @param array $param 连接参数, 包含: path
  584. */
  585. public function connect($param)
  586. {
  587. if (($this->link = sqlite_open($param['path'])) === false)
  588. throw new XSException("Can not open sqlite file: '{$param['path']}'");
  589. }
  590. /**
  591. * 关闭数据库
  592. */
  593. public function close()
  594. {
  595. if ($this->link)
  596. {
  597. sqlite_close($this->link);
  598. $this->link = null;
  599. }
  600. }
  601. /**
  602. * 执行 SQL 语句查询
  603. * @param string $sql 要执行的 SQL 语句
  604. * @return mixed
  605. */
  606. public function query($sql)
  607. {
  608. //echo "[DEBUG] SQL: $sql\n";
  609. $res = sqlite_query($this->link, $sql);
  610. if ($res === false)
  611. throw new XSException('SQLITE ERROR: ' . sqlite_error_string($this->link));
  612. if (!is_resource($res))
  613. $ret = $res;
  614. else
  615. {
  616. $ret = array();
  617. while ($tmp = sqlite_fetch_array($res, SQLITE_ASSOC))
  618. {
  619. $ret[] = $tmp;
  620. }
  621. }
  622. return $ret;
  623. }
  624. }
  625. /**
  626. * 面向对象的 SQLite3 扩展
  627. *
  628. * @author hightman <hightman@twomice.net>
  629. * @version 1.0.0
  630. * @package XS.util.db
  631. */
  632. class XSDatabaseSQLite3 extends XSDatabase
  633. {
  634. private $obj;
  635. /**
  636. * 打开数据库
  637. * @param array $param 连接参数, 包含: path
  638. */
  639. public function connect($param)
  640. {
  641. try
  642. {
  643. $this->obj = new SQLite3($param['path'], SQLITE3_OPEN_READONLY);
  644. }
  645. catch (Exception $e)
  646. {
  647. throw new XSException($e->getMessage());
  648. }
  649. }
  650. /**
  651. * 关闭数据库
  652. */
  653. public function close()
  654. {
  655. if ($this->obj)
  656. {
  657. $this->obj->close();
  658. $this->obj = null;
  659. }
  660. }
  661. /**
  662. * 执行 SQL 语句查询
  663. * @param string $sql 要执行的 SQL 语句
  664. * @return mixed
  665. */
  666. public function query($sql)
  667. {
  668. //echo "[DEBUG] SQL: $sql\n";
  669. $res = $this->obj->query($sql);
  670. if ($res === false)
  671. throw new XSException('SQLITE3 ERROR(#' . $this->obj->lastErrorCode() . '): ' . $this->obj->lastErrorMsg());
  672. if (!is_object($res))
  673. $ret = $res;
  674. else
  675. {
  676. $ret = array();
  677. while ($tmp = $res->fetchArray(SQLITE3_ASSOC))
  678. {
  679. $ret[] = $tmp;
  680. }
  681. $res->finalize();
  682. }
  683. return $ret;
  684. }
  685. }
  686. /**
  687. * 面向对象的 PDO 扩展基类
  688. *
  689. * @author hightman <hightman@twomice.net>
  690. * @version 1.0.0
  691. * @package XS.util.db
  692. */
  693. abstract class XSDatabasePDO extends XSDatabase
  694. {
  695. protected $obj;
  696. /**
  697. * 连接数据库
  698. * 具体的每个类必须实现 {@link makeDsn} 来将参数转换为 dsn
  699. * @param array $param 连接参数, 包含: user, pass ...
  700. * @see makeDsn
  701. */
  702. public function connect($param)
  703. {
  704. $dsn = $this->makeDsn($param);
  705. $user = isset($param['user']) ? $param['user'] : 'root';
  706. $pass = isset($param['pass']) ? $param['pass'] : '';
  707. try
  708. {
  709. $this->obj = new PDO($dsn, $user, $pass);
  710. }
  711. catch (PDOException $e)
  712. {
  713. throw new XSException($e->getMessage());
  714. }
  715. }
  716. /**
  717. * 关闭数据库
  718. */
  719. public function close()
  720. {
  721. $this->obj = null;
  722. }
  723. /**
  724. * 执行 SQL 语句
  725. * @param string $sql 要执行的 SQL 语句
  726. * @return mixed
  727. */
  728. public function query($sql)
  729. {
  730. //echo "[DEBUG] SQL: $sql\n";
  731. $res = $this->obj->query($sql);
  732. if ($res === false)
  733. {
  734. $info = $this->obj->errorInfo();
  735. throw new XSException('SQLSTATE[' . $info[0] . '] [' . $info[1] . '] ' . $info[2]);
  736. }
  737. $ret = $res->fetchAll(PDO::FETCH_ASSOC);
  738. return $ret;
  739. }
  740. /**
  741. * 提取参数内容生成 PDO 连接专用的 DSN
  742. * @param array $param
  743. */
  744. abstract protected function makeDsn($param);
  745. }
  746. /**
  747. * PDO.MySQL 实现
  748. *
  749. * @author hightman <hightman@twomice.net>
  750. * @version 1.0.0
  751. * @package XS.util.db
  752. */
  753. class XSDatabasePDO_MySQL extends XSDatabasePDO
  754. {
  755. /**
  756. * 生成 MySQL DSN
  757. * @param array $param 包含 host, port, dbname
  758. * @return string
  759. */
  760. protected function makeDsn($param)
  761. {
  762. $dsn = 'mysql:host=' . (isset($param['host']) ? $param['host'] : 'localhost');
  763. if (isset($param['port']) && $param['port'] != 3306)
  764. $dsn .= ';port=' . $param['port'];
  765. $dsn .= ';dbname=' . $param['dbname'];
  766. return $dsn;
  767. }
  768. /**
  769. * 将输出字符集设置为 UTF-8
  770. * @return bool 始终返回 true
  771. */
  772. public function setUtf8()
  773. {
  774. // BUGFIXED: 此处应为不带引号的 utf8
  775. return $this->obj->prepare("SET NAMES utf8")->execute();
  776. }
  777. }
  778. /**
  779. * PDO.SQLite 实现
  780. *
  781. * @author hightman <hightman@twomice.net>
  782. * @version 1.0.0
  783. * @package XS.util.db
  784. */
  785. class XSDatabasePDO_SQLite extends XSDatabasePDO
  786. {
  787. /**
  788. * 生成 SQLite DSN
  789. * @param array $param 包含 path 为数据库路径
  790. * @return string
  791. */
  792. protected function makeDsn($param)
  793. {
  794. $dsn = 'sqlite:' . $param['path'];
  795. return $dsn;
  796. }
  797. }
  798. /**
  799. * 数据过滤器的接口
  800. * 以便在提交到索引前有一个修改和调整数据的机会
  801. *
  802. * @author hightman <hightman@twomice.net>
  803. * @since 1.1.0
  804. * @package XS.util
  805. */
  806. interface XSDataFilter
  807. {
  808. /**
  809. * 数据处理函数, 返回 false 则跳过不处理
  810. * @param array $data 字段名和值组成的数据数组
  811. * @param mixed $cs 数据字符集, 如无法确定则为 false
  812. */
  813. public function process($data, $cs = false);
  814. }
  815. /**
  816. * 内置调试过滤器, 直接打印数据内容
  817. *
  818. * @author hightman <hightman@twomice.net>
  819. * @version 1.0.0
  820. * @package XS.util
  821. */
  822. class XSDebugFilter implements XSDataFilter
  823. {
  824. public function process($data, $cs = false)
  825. {
  826. echo "\n----- DEBUG DATA INFO -----\n";
  827. print_r($data);
  828. return $data;
  829. }
  830. }