Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

533 linhas
16 KiB

há 3 anos
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2006-2014 http://thinkphp.cn All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
  8. // +----------------------------------------------------------------------
  9. // | Author: liu21st <liu21st@gmail.com>
  10. // +----------------------------------------------------------------------
  11. namespace Think\Db;
  12. use PDO;
  13. use Think\Config;
  14. use Think\Debug;
  15. class Lite
  16. {
  17. // PDO操作实例
  18. protected $PDOStatement = null;
  19. // 当前操作所属的模型名
  20. protected $model = '_think_';
  21. // 当前SQL指令
  22. protected $queryStr = '';
  23. protected $modelSql = array();
  24. // 最后插入ID
  25. protected $lastInsID = null;
  26. // 返回或者影响记录数
  27. protected $numRows = 0;
  28. // 事物操作PDO实例
  29. protected $transPDO = null;
  30. // 事务指令数
  31. protected $transTimes = 0;
  32. // 错误信息
  33. protected $error = '';
  34. // 数据库连接ID 支持多个连接
  35. protected $linkID = array();
  36. // 当前连接ID
  37. protected $_linkID = null;
  38. // 数据库连接参数配置
  39. protected $config = array(
  40. 'type' => '', // 数据库类型
  41. 'hostname' => '127.0.0.1', // 服务器地址
  42. 'database' => '', // 数据库名
  43. 'username' => '', // 用户名
  44. 'password' => '', // 密码
  45. 'hostport' => '', // 端口
  46. 'dsn' => '', //
  47. 'params' => array(), // 数据库连接参数
  48. 'charset' => 'utf8', // 数据库编码默认采用utf8
  49. 'prefix' => '', // 数据库表前缀
  50. 'debug' => false, // 数据库调试模式
  51. 'deploy' => 0, // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
  52. 'rw_separate' => false, // 数据库读写是否分离 主从式有效
  53. 'master_num' => 1, // 读写分离后 主服务器数量
  54. 'slave_no' => '', // 指定从服务器序号
  55. );
  56. // 数据库表达式
  57. protected $comparison = array('eq' => '=', 'neq' => '<>', 'gt' => '>', 'egt' => '>=', 'lt' => '<', 'elt' => '<=', 'notlike' => 'NOT LIKE', 'like' => 'LIKE', 'in' => 'IN', 'notin' => 'NOT IN');
  58. // 查询表达式
  59. protected $selectSql = 'SELECT%DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%%LIMIT% %UNION%%COMMENT%';
  60. // 查询次数
  61. protected $queryTimes = 0;
  62. // 执行次数
  63. protected $executeTimes = 0;
  64. // PDO连接参数
  65. protected $options = array(
  66. PDO::ATTR_CASE => PDO::CASE_LOWER,
  67. PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
  68. PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
  69. PDO::ATTR_STRINGIFY_FETCHES => false,
  70. );
  71. /**
  72. * 架构函数 读取数据库配置信息
  73. * @access public
  74. * @param array $config 数据库配置数组
  75. */
  76. public function __construct($config = '')
  77. {
  78. if (!empty($config)) {
  79. $this->config = array_merge($this->config, $config);
  80. if (is_array($this->config['params'])) {
  81. $this->options += $this->config['params'];
  82. }
  83. }
  84. }
  85. /**
  86. * 连接数据库方法
  87. * @access public
  88. */
  89. public function connect($config = '', $linkNum = 0)
  90. {
  91. if (!isset($this->linkID[$linkNum])) {
  92. if (empty($config)) {
  93. $config = $this->config;
  94. }
  95. try {
  96. if (empty($config['dsn'])) {
  97. $config['dsn'] = $this->parseDsn($config);
  98. }
  99. if (version_compare(PHP_VERSION, '5.3.6', '<=')) {
  100. //禁用模拟预处理语句
  101. $this->options[PDO::ATTR_EMULATE_PREPARES] = false;
  102. }
  103. $this->linkID[$linkNum] = new PDO($config['dsn'], $config['username'], $config['password'], $this->options);
  104. } catch (\PDOException $e) {
  105. E($e->getMessage());
  106. }
  107. }
  108. return $this->linkID[$linkNum];
  109. }
  110. /**
  111. * 解析pdo连接的dsn信息
  112. * @access public
  113. * @param array $config 连接信息
  114. * @return string
  115. */
  116. protected function parseDsn($config)
  117. {}
  118. /**
  119. * 释放查询结果
  120. * @access public
  121. */
  122. public function free()
  123. {
  124. $this->PDOStatement = null;
  125. }
  126. /**
  127. * 执行查询 返回数据集
  128. * @access public
  129. * @param string $str sql指令
  130. * @param array $bind 参数绑定
  131. * @return mixed
  132. */
  133. public function query($str, $bind = array())
  134. {
  135. $this->initConnect(false);
  136. if (!$this->_linkID) {
  137. return false;
  138. }
  139. $this->queryStr = $str;
  140. if (!empty($bind)) {
  141. $that = $this;
  142. $this->queryStr = strtr($this->queryStr, array_map(function ($val) use ($that) {return '\'' . $that->escapeString($val) . '\'';}, $bind));
  143. }
  144. //释放前次的查询结果
  145. if (!empty($this->PDOStatement)) {
  146. $this->free();
  147. }
  148. $this->queryTimes++;
  149. N('db_query', 1); // 兼容代码
  150. // 调试开始
  151. $this->debug(true);
  152. $this->PDOStatement = $this->_linkID->prepare($str);
  153. if (false === $this->PDOStatement) {
  154. E($this->error());
  155. }
  156. foreach ($bind as $key => $val) {
  157. if (is_array($val)) {
  158. $this->PDOStatement->bindValue($key, $val[0], $val[1]);
  159. } else {
  160. $this->PDOStatement->bindValue($key, $val);
  161. }
  162. }
  163. $result = $this->PDOStatement->execute();
  164. // 调试结束
  165. $this->debug(false);
  166. if (false === $result) {
  167. $this->error();
  168. return false;
  169. } else {
  170. return $this->getResult();
  171. }
  172. }
  173. /**
  174. * 执行语句
  175. * @access public
  176. * @param string $str sql指令
  177. * @param array $bind 参数绑定
  178. * @return integer
  179. */
  180. public function execute($str, $bind = array())
  181. {
  182. $this->initConnect(true);
  183. if (!$this->_linkID) {
  184. return false;
  185. }
  186. $this->queryStr = $str;
  187. if (!empty($bind)) {
  188. $that = $this;
  189. $this->queryStr = strtr($this->queryStr, array_map(function ($val) use ($that) {return '\'' . $that->escapeString($val) . '\'';}, $bind));
  190. }
  191. //释放前次的查询结果
  192. if (!empty($this->PDOStatement)) {
  193. $this->free();
  194. }
  195. $this->executeTimes++;
  196. N('db_write', 1); // 兼容代码
  197. // 记录开始执行时间
  198. $this->debug(true);
  199. $this->PDOStatement = $this->_linkID->prepare($str);
  200. if (false === $this->PDOStatement) {
  201. E($this->error());
  202. }
  203. foreach ($bind as $key => $val) {
  204. if (is_array($val)) {
  205. $this->PDOStatement->bindValue($key, $val[0], $val[1]);
  206. } else {
  207. $this->PDOStatement->bindValue($key, $val);
  208. }
  209. }
  210. $result = $this->PDOStatement->execute();
  211. $this->debug(false);
  212. if (false === $result) {
  213. $this->error();
  214. return false;
  215. } else {
  216. $this->numRows = $this->PDOStatement->rowCount();
  217. if (preg_match("/^\s*(INSERT\s+INTO|REPLACE\s+INTO)\s+/i", $str)) {
  218. $this->lastInsID = $this->_linkID->lastInsertId();
  219. }
  220. return $this->numRows;
  221. }
  222. }
  223. /**
  224. * 启动事务
  225. * @access public
  226. * @return void
  227. */
  228. public function startTrans()
  229. {
  230. $this->initConnect(true);
  231. if (!$this->_linkID) {
  232. return false;
  233. }
  234. //数据rollback 支持
  235. if (0 == $this->transTimes) {
  236. // 记录当前操作PDO
  237. $this->transPdo = $this->_linkID;
  238. $this->_linkID->beginTransaction();
  239. }
  240. $this->transTimes++;
  241. return;
  242. }
  243. /**
  244. * 用于非自动提交状态下面的查询提交
  245. * @access public
  246. * @return boolean
  247. */
  248. public function commit()
  249. {
  250. if ($this->transTimes == 1) {
  251. // 由嵌套事物的最外层进行提交
  252. $result = $this->_linkID->commit();
  253. $this->transTimes = 0;
  254. $this->transPdo = null;
  255. if (!$result) {
  256. $this->error();
  257. return false;
  258. }
  259. } else {
  260. $this->transTimes--;
  261. }
  262. return true;
  263. }
  264. /**
  265. * 事务回滚
  266. * @access public
  267. * @return boolean
  268. */
  269. public function rollback()
  270. {
  271. if ($this->transTimes > 0) {
  272. $result = $this->_linkID->rollback();
  273. $this->transTimes = 0;
  274. $this->transPdo = null;
  275. if (!$result) {
  276. $this->error();
  277. return false;
  278. }
  279. }
  280. return true;
  281. }
  282. /**
  283. * 获得所有的查询数据
  284. * @access private
  285. * @return array
  286. */
  287. private function getResult()
  288. {
  289. //返回数据集
  290. $result = $this->PDOStatement->fetchAll(PDO::FETCH_ASSOC);
  291. $this->numRows = count($result);
  292. return $result;
  293. }
  294. /**
  295. * 获得查询次数
  296. * @access public
  297. * @param boolean $execute 是否包含所有查询
  298. * @return integer
  299. */
  300. public function getQueryTimes($execute = false)
  301. {
  302. return $execute ? $this->queryTimes + $this->executeTimes : $this->queryTimes;
  303. }
  304. /**
  305. * 获得执行次数
  306. * @access public
  307. * @return integer
  308. */
  309. public function getExecuteTimes()
  310. {
  311. return $this->executeTimes;
  312. }
  313. /**
  314. * 关闭数据库
  315. * @access public
  316. */
  317. public function close()
  318. {
  319. $this->_linkID = null;
  320. }
  321. /**
  322. * 数据库错误信息
  323. * 并显示当前的SQL语句
  324. * @access public
  325. * @return string
  326. */
  327. public function error()
  328. {
  329. if ($this->PDOStatement) {
  330. $error = $this->PDOStatement->errorInfo();
  331. $this->error = $error[1] . ':' . $error[2];
  332. } else {
  333. $this->error = '';
  334. }
  335. if ('' != $this->queryStr) {
  336. $this->error .= "\n [ SQL语句 ] : " . $this->queryStr;
  337. }
  338. // 记录错误日志
  339. trace($this->error, '', 'ERR');
  340. if ($this->config['debug']) {
  341. // 开启数据库调试模式
  342. E($this->error);
  343. } else {
  344. return $this->error;
  345. }
  346. }
  347. /**
  348. * 获取最近一次查询的sql语句
  349. * @param string $model 模型名
  350. * @access public
  351. * @return string
  352. */
  353. public function getLastSql($model = '')
  354. {
  355. return $model ? $this->modelSql[$model] : $this->queryStr;
  356. }
  357. /**
  358. * 获取最近插入的ID
  359. * @access public
  360. * @return string
  361. */
  362. public function getLastInsID()
  363. {
  364. return $this->lastInsID;
  365. }
  366. /**
  367. * 获取最近的错误信息
  368. * @access public
  369. * @return string
  370. */
  371. public function getError()
  372. {
  373. return $this->error;
  374. }
  375. /**
  376. * SQL指令安全过滤
  377. * @access public
  378. * @param string $str SQL字符串
  379. * @return string
  380. */
  381. public function escapeString($str)
  382. {
  383. return addslashes($str);
  384. }
  385. /**
  386. * 设置当前操作模型
  387. * @access public
  388. * @param string $model 模型名
  389. * @return void
  390. */
  391. public function setModel($model)
  392. {
  393. $this->model = $model;
  394. }
  395. /**
  396. * 数据库调试 记录当前SQL
  397. * @access protected
  398. * @param boolean $start 调试开始标记 true 开始 false 结束
  399. */
  400. protected function debug($start)
  401. {
  402. if ($this->config['debug']) {
  403. // 开启数据库调试模式
  404. if ($start) {
  405. G('queryStartTime');
  406. } else {
  407. $this->modelSql[$this->model] = $this->queryStr;
  408. //$this->model = '_think_';
  409. // 记录操作结束时间
  410. G('queryEndTime');
  411. trace($this->queryStr . ' [ RunTime:' . G('queryStartTime', 'queryEndTime') . 's ]', '', 'SQL');
  412. }
  413. }
  414. }
  415. /**
  416. * 初始化数据库连接
  417. * @access protected
  418. * @param boolean $master 主服务器
  419. * @return void
  420. */
  421. protected function initConnect($master = true)
  422. {
  423. // 开启事物时用同一个连接进行操作
  424. if ($this->transPDO) {
  425. return $this->transPDO;
  426. }
  427. if (!empty($this->config['deploy']))
  428. // 采用分布式数据库
  429. {
  430. $this->_linkID = $this->multiConnect($master);
  431. } else
  432. // 默认单数据库
  433. if (!$this->_linkID) {
  434. $this->_linkID = $this->connect();
  435. }
  436. }
  437. /**
  438. * 连接分布式服务器
  439. * @access protected
  440. * @param boolean $master 主服务器
  441. * @return void
  442. */
  443. protected function multiConnect($master = false)
  444. {
  445. // 分布式数据库配置解析
  446. $_config['username'] = explode(',', $this->config['username']);
  447. $_config['password'] = explode(',', $this->config['password']);
  448. $_config['hostname'] = explode(',', $this->config['hostname']);
  449. $_config['hostport'] = explode(',', $this->config['hostport']);
  450. $_config['database'] = explode(',', $this->config['database']);
  451. $_config['dsn'] = explode(',', $this->config['dsn']);
  452. $_config['charset'] = explode(',', $this->config['charset']);
  453. // 数据库读写是否分离
  454. if ($this->config['rw_separate']) {
  455. // 主从式采用读写分离
  456. if ($master)
  457. // 主服务器写入
  458. {
  459. $r = floor(mt_rand(0, $this->config['master_num'] - 1));
  460. } else {
  461. if (is_numeric($this->config['slave_no'])) {
  462. // 指定服务器读
  463. $r = $this->config['slave_no'];
  464. } else {
  465. // 读操作连接从服务器
  466. $r = floor(mt_rand($this->config['master_num'], count($_config['hostname']) - 1)); // 每次随机连接的数据库
  467. }
  468. }
  469. } else {
  470. // 读写操作不区分服务器
  471. $r = floor(mt_rand(0, count($_config['hostname']) - 1)); // 每次随机连接的数据库
  472. }
  473. $db_config = array(
  474. 'username' => isset($_config['username'][$r]) ? $_config['username'][$r] : $_config['username'][0],
  475. 'password' => isset($_config['password'][$r]) ? $_config['password'][$r] : $_config['password'][0],
  476. 'hostname' => isset($_config['hostname'][$r]) ? $_config['hostname'][$r] : $_config['hostname'][0],
  477. 'hostport' => isset($_config['hostport'][$r]) ? $_config['hostport'][$r] : $_config['hostport'][0],
  478. 'database' => isset($_config['database'][$r]) ? $_config['database'][$r] : $_config['database'][0],
  479. 'dsn' => isset($_config['dsn'][$r]) ? $_config['dsn'][$r] : $_config['dsn'][0],
  480. 'charset' => isset($_config['charset'][$r]) ? $_config['charset'][$r] : $_config['charset'][0],
  481. );
  482. return $this->connect($db_config, $r);
  483. }
  484. /**
  485. * 析构方法
  486. * @access public
  487. */
  488. public function __destruct()
  489. {
  490. // 释放查询
  491. if ($this->PDOStatement) {
  492. $this->free();
  493. }
  494. // 关闭连接
  495. $this->close();
  496. }
  497. }