Lite.class.php 15 KB

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