AdvModel.class.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2006-2012 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. defined('THINK_PATH') or exit();
  12. /**
  13. * 高级模型扩展
  14. * @category Extend
  15. * @package Extend
  16. * @subpackage Model
  17. * @author liu21st <liu21st@gmail.com>
  18. */
  19. class AdvModel extends Model {
  20. protected $optimLock = 'lock_version';
  21. protected $returnType = 'array';
  22. protected $blobFields = array();
  23. protected $blobValues = null;
  24. protected $serializeField = array();
  25. protected $readonlyField = array();
  26. protected $_filter = array();
  27. protected $partition = array();
  28. public function __construct($name='',$tablePrefix='',$connection='') {
  29. if('' !== $name || is_subclass_of($this,'AdvModel') ){
  30. // 如果是AdvModel子类或者有传入模型名称则获取字段缓存
  31. }else{
  32. // 空的模型 关闭字段缓存
  33. $this->autoCheckFields = false;
  34. }
  35. parent::__construct($name,$tablePrefix,$connection);
  36. }
  37. /**
  38. * 利用__call方法重载 实现一些特殊的Model方法 (魔术方法)
  39. * @access public
  40. * @param string $method 方法名称
  41. * @param mixed $args 调用参数
  42. * @return mixed
  43. */
  44. public function __call($method,$args) {
  45. if(strtolower(substr($method,0,3))=='top'){
  46. // 获取前N条记录
  47. $count = substr($method,3);
  48. array_unshift($args,$count);
  49. return call_user_func_array(array(&$this, 'topN'), $args);
  50. }else{
  51. return parent::__call($method,$args);
  52. }
  53. }
  54. /**
  55. * 对保存到数据库的数据进行处理
  56. * @access protected
  57. * @param mixed $data 要操作的数据
  58. * @return boolean
  59. */
  60. protected function _facade($data) {
  61. // 检查序列化字段
  62. $data = $this->serializeField($data);
  63. return parent::_facade($data);
  64. }
  65. // 查询成功后的回调方法
  66. protected function _after_find(&$result,$options='') {
  67. // 检查序列化字段
  68. $this->checkSerializeField($result);
  69. // 获取文本字段
  70. $this->getBlobFields($result);
  71. // 检查字段过滤
  72. $result = $this->getFilterFields($result);
  73. // 缓存乐观锁
  74. $this->cacheLockVersion($result);
  75. }
  76. // 查询数据集成功后的回调方法
  77. protected function _after_select(&$resultSet,$options='') {
  78. // 检查序列化字段
  79. $resultSet = $this->checkListSerializeField($resultSet);
  80. // 获取文本字段
  81. $resultSet = $this->getListBlobFields($resultSet);
  82. // 检查列表字段过滤
  83. $resultSet = $this->getFilterListFields($resultSet);
  84. }
  85. // 写入前的回调方法
  86. protected function _before_insert(&$data,$options='') {
  87. // 记录乐观锁
  88. $data = $this->recordLockVersion($data);
  89. // 检查文本字段
  90. $data = $this->checkBlobFields($data);
  91. // 检查字段过滤
  92. $data = $this->setFilterFields($data);
  93. }
  94. protected function _after_insert($data,$options) {
  95. // 保存文本字段
  96. $this->saveBlobFields($data);
  97. }
  98. // 更新前的回调方法
  99. protected function _before_update(&$data,$options='') {
  100. // 检查乐观锁
  101. if(!$this->checkLockVersion($data,$options)) {
  102. return false;
  103. }
  104. // 检查文本字段
  105. $data = $this->checkBlobFields($data);
  106. // 检查只读字段
  107. $data = $this->checkReadonlyField($data);
  108. // 检查字段过滤
  109. $data = $this->setFilterFields($data);
  110. }
  111. protected function _after_update($data,$options) {
  112. // 保存文本字段
  113. $this->saveBlobFields($data);
  114. }
  115. protected function _after_delete($data,$options) {
  116. // 删除Blob数据
  117. $this->delBlobFields($data);
  118. }
  119. /**
  120. * 记录乐观锁
  121. * @access protected
  122. * @param array $data 数据对象
  123. * @return array
  124. */
  125. protected function recordLockVersion($data) {
  126. // 记录乐观锁
  127. if($this->optimLock && !isset($data[$this->optimLock]) ) {
  128. if(in_array($this->optimLock,$this->fields,true)) {
  129. $data[$this->optimLock] = 0;
  130. }
  131. }
  132. return $data;
  133. }
  134. /**
  135. * 缓存乐观锁
  136. * @access protected
  137. * @param array $data 数据对象
  138. * @return void
  139. */
  140. protected function cacheLockVersion($data) {
  141. if($this->optimLock) {
  142. if(isset($data[$this->optimLock]) && isset($data[$this->getPk()])) {
  143. // 只有当存在乐观锁字段和主键有值的时候才记录乐观锁
  144. $_SESSION[$this->name.'_'.$data[$this->getPk()].'_lock_version'] = $data[$this->optimLock];
  145. }
  146. }
  147. }
  148. /**
  149. * 检查乐观锁
  150. * @access protected
  151. * @param array $data 当前数据
  152. * @param array $options 查询表达式
  153. * @return mixed
  154. */
  155. protected function checkLockVersion(&$data,$options) {
  156. $id = $data[$this->getPk()];
  157. // 检查乐观锁
  158. $identify = $this->name.'_'.$id.'_lock_version';
  159. if($this->optimLock && isset($_SESSION[$identify])) {
  160. $lock_version = $_SESSION[$identify];
  161. $vo = $this->field($this->optimLock)->find($id);
  162. $_SESSION[$identify] = $lock_version;
  163. $curr_version = $vo[$this->optimLock];
  164. if(isset($curr_version)) {
  165. if($curr_version>0 && $lock_version != $curr_version) {
  166. // 记录已经更新
  167. $this->error = L('_RECORD_HAS_UPDATE_');
  168. return false;
  169. }else{
  170. // 更新乐观锁
  171. $save_version = $data[$this->optimLock];
  172. if($save_version != $lock_version+1) {
  173. $data[$this->optimLock] = $lock_version+1;
  174. }
  175. $_SESSION[$identify] = $lock_version+1;
  176. }
  177. }
  178. }
  179. return true;
  180. }
  181. /**
  182. * 查找前N个记录
  183. * @access public
  184. * @param integer $count 记录个数
  185. * @param array $options 查询表达式
  186. * @return array
  187. */
  188. public function topN($count,$options=array()) {
  189. $options['limit'] = $count;
  190. return $this->select($options);
  191. }
  192. /**
  193. * 查询符合条件的第N条记录
  194. * 0 表示第一条记录 -1 表示最后一条记录
  195. * @access public
  196. * @param integer $position 记录位置
  197. * @param array $options 查询表达式
  198. * @return mixed
  199. */
  200. public function getN($position=0,$options=array()) {
  201. if($position>=0) { // 正向查找
  202. $options['limit'] = $position.',1';
  203. $list = $this->select($options);
  204. return $list?$list[0]:false;
  205. }else{ // 逆序查找
  206. $list = $this->select($options);
  207. return $list?$list[count($list)-abs($position)]:false;
  208. }
  209. }
  210. /**
  211. * 获取满足条件的第一条记录
  212. * @access public
  213. * @param array $options 查询表达式
  214. * @return mixed
  215. */
  216. public function first($options=array()) {
  217. return $this->getN(0,$options);
  218. }
  219. /**
  220. * 获取满足条件的最后一条记录
  221. * @access public
  222. * @param array $options 查询表达式
  223. * @return mixed
  224. */
  225. public function last($options=array()) {
  226. return $this->getN(-1,$options);
  227. }
  228. /**
  229. * 返回数据
  230. * @access public
  231. * @param array $data 数据
  232. * @param string $type 返回类型 默认为数组
  233. * @return mixed
  234. */
  235. public function returnResult($data,$type='') {
  236. if('' === $type)
  237. $type = $this->returnType;
  238. switch($type) {
  239. case 'array' : return $data;
  240. case 'object': return (object)$data;
  241. default:// 允许用户自定义返回类型
  242. if(class_exists($type))
  243. return new $type($data);
  244. else
  245. throw_exception(L('_CLASS_NOT_EXIST_').':'.$type);
  246. }
  247. }
  248. /**
  249. * 获取数据的时候过滤数据字段
  250. * @access protected
  251. * @param mixed $result 查询的数据
  252. * @return array
  253. */
  254. protected function getFilterFields(&$result) {
  255. if(!empty($this->_filter)) {
  256. foreach ($this->_filter as $field=>$filter){
  257. if(isset($result[$field])) {
  258. $fun = $filter[1];
  259. if(!empty($fun)) {
  260. if(isset($filter[2]) && $filter[2]){
  261. // 传递整个数据对象作为参数
  262. $result[$field] = call_user_func($fun,$result);
  263. }else{
  264. // 传递字段的值作为参数
  265. $result[$field] = call_user_func($fun,$result[$field]);
  266. }
  267. }
  268. }
  269. }
  270. }
  271. return $result;
  272. }
  273. protected function getFilterListFields(&$resultSet) {
  274. if(!empty($this->_filter)) {
  275. foreach ($resultSet as $key=>$result)
  276. $resultSet[$key] = $this->getFilterFields($result);
  277. }
  278. return $resultSet;
  279. }
  280. /**
  281. * 写入数据的时候过滤数据字段
  282. * @access protected
  283. * @param mixed $result 查询的数据
  284. * @return array
  285. */
  286. protected function setFilterFields($data) {
  287. if(!empty($this->_filter)) {
  288. foreach ($this->_filter as $field=>$filter){
  289. if(isset($data[$field])) {
  290. $fun = $filter[0];
  291. if(!empty($fun)) {
  292. if(isset($filter[2]) && $filter[2]) {
  293. // 传递整个数据对象作为参数
  294. $data[$field] = call_user_func($fun,$data);
  295. }else{
  296. // 传递字段的值作为参数
  297. $data[$field] = call_user_func($fun,$data[$field]);
  298. }
  299. }
  300. }
  301. }
  302. }
  303. return $data;
  304. }
  305. /**
  306. * 返回数据列表
  307. * @access protected
  308. * @param array $resultSet 数据
  309. * @param string $type 返回类型 默认为数组
  310. * @return void
  311. */
  312. protected function returnResultSet(&$resultSet,$type='') {
  313. foreach ($resultSet as $key=>$data)
  314. $resultSet[$key] = $this->returnResult($data,$type);
  315. return $resultSet;
  316. }
  317. protected function checkBlobFields(&$data) {
  318. // 检查Blob文件保存字段
  319. if(!empty($this->blobFields)) {
  320. foreach ($this->blobFields as $field){
  321. if(isset($data[$field])) {
  322. if(isset($data[$this->getPk()]))
  323. $this->blobValues[$this->name.'/'.$data[$this->getPk()].'_'.$field] = $data[$field];
  324. else
  325. $this->blobValues[$this->name.'/@?id@_'.$field] = $data[$field];
  326. unset($data[$field]);
  327. }
  328. }
  329. }
  330. return $data;
  331. }
  332. /**
  333. * 获取数据集的文本字段
  334. * @access protected
  335. * @param mixed $resultSet 查询的数据
  336. * @param string $field 查询的字段
  337. * @return void
  338. */
  339. protected function getListBlobFields(&$resultSet,$field='') {
  340. if(!empty($this->blobFields)) {
  341. foreach ($resultSet as $key=>$result){
  342. $result = $this->getBlobFields($result,$field);
  343. $resultSet[$key] = $result;
  344. }
  345. }
  346. return $resultSet;
  347. }
  348. /**
  349. * 获取数据的文本字段
  350. * @access protected
  351. * @param mixed $data 查询的数据
  352. * @param string $field 查询的字段
  353. * @return void
  354. */
  355. protected function getBlobFields(&$data,$field='') {
  356. if(!empty($this->blobFields)) {
  357. $pk = $this->getPk();
  358. $id = $data[$pk];
  359. if(empty($field)) {
  360. foreach ($this->blobFields as $field){
  361. $identify = $this->name.'/'.$id.'_'.$field;
  362. $data[$field] = F($identify);
  363. }
  364. return $data;
  365. }else{
  366. $identify = $this->name.'/'.$id.'_'.$field;
  367. return F($identify);
  368. }
  369. }
  370. }
  371. /**
  372. * 保存File方式的字段
  373. * @access protected
  374. * @param mixed $data 保存的数据
  375. * @return void
  376. */
  377. protected function saveBlobFields(&$data) {
  378. if(!empty($this->blobFields)) {
  379. foreach ($this->blobValues as $key=>$val){
  380. if(strpos($key,'@?id@'))
  381. $key = str_replace('@?id@',$data[$this->getPk()],$key);
  382. F($key,$val);
  383. }
  384. }
  385. }
  386. /**
  387. * 删除File方式的字段
  388. * @access protected
  389. * @param mixed $data 保存的数据
  390. * @param string $field 查询的字段
  391. * @return void
  392. */
  393. protected function delBlobFields(&$data,$field='') {
  394. if(!empty($this->blobFields)) {
  395. $pk = $this->getPk();
  396. $id = $data[$pk];
  397. if(empty($field)) {
  398. foreach ($this->blobFields as $field){
  399. $identify = $this->name.'/'.$id.'_'.$field;
  400. F($identify,null);
  401. }
  402. }else{
  403. $identify = $this->name.'/'.$id.'_'.$field;
  404. F($identify,null);
  405. }
  406. }
  407. }
  408. /**
  409. * 字段值延迟增长
  410. * @access public
  411. * @param string $field 字段名
  412. * @param integer $step 增长值
  413. * @param integer $lazyTime 延时时间(s)
  414. * @return boolean
  415. */
  416. public function setLazyInc($field,$step=1,$lazyTime=0) {
  417. $condition = $this->options['where'];
  418. if(empty($condition)) { // 没有条件不做任何更新
  419. return false;
  420. }
  421. if($lazyTime>0) {// 延迟写入
  422. $guid = md5($this->name.'_'.$field.'_'.serialize($condition));
  423. $step = $this->lazyWrite($guid,$step,$lazyTime);
  424. if(false === $step ) return true; // 等待下次写入
  425. }
  426. return $this->setField($field,array('exp',$field.'+'.$step));
  427. }
  428. /**
  429. * 字段值延迟减少
  430. * @access public
  431. * @param string $field 字段名
  432. * @param integer $step 减少值
  433. * @param integer $lazyTime 延时时间(s)
  434. * @return boolean
  435. */
  436. public function setLazyDec($field,$step=1,$lazyTime=0) {
  437. $condition = $this->options['where'];
  438. if(empty($condition)) { // 没有条件不做任何更新
  439. return false;
  440. }
  441. if($lazyTime>0) {// 延迟写入
  442. $guid = md5($this->name.'_'.$field.'_'.serialize($condition));
  443. $step = $this->lazyWrite($guid,$step,$lazyTime);
  444. if(false === $step ) return true; // 等待下次写入
  445. }
  446. return $this->setField($field,array('exp',$field.'-'.$step));
  447. }
  448. /**
  449. * 延时更新检查 返回false表示需要延时
  450. * 否则返回实际写入的数值
  451. * @access public
  452. * @param string $guid 写入标识
  453. * @param integer $step 写入步进值
  454. * @param integer $lazyTime 延时时间(s)
  455. * @return false|integer
  456. */
  457. protected function lazyWrite($guid,$step,$lazyTime) {
  458. if(false !== ($value = F($guid))) { // 存在缓存写入数据
  459. if(time()>F($guid.'_time')+$lazyTime) {
  460. // 延时更新时间到了,删除缓存数据 并实际写入数据库
  461. F($guid,NULL);
  462. F($guid.'_time',NULL);
  463. return $value+$step;
  464. }else{
  465. // 追加数据到缓存
  466. F($guid,$value+$step);
  467. return false;
  468. }
  469. }else{ // 没有缓存数据
  470. F($guid,$step);
  471. // 计时开始
  472. F($guid.'_time',time());
  473. return false;
  474. }
  475. }
  476. /**
  477. * 检查序列化数据字段
  478. * @access protected
  479. * @param array $data 数据
  480. * @return array
  481. */
  482. protected function serializeField(&$data) {
  483. // 检查序列化字段
  484. if(!empty($this->serializeField)) {
  485. // 定义方式 $this->serializeField = array('ser'=>array('name','email'));
  486. foreach ($this->serializeField as $key=>$val){
  487. if(empty($data[$key])) {
  488. $serialize = array();
  489. foreach ($val as $name){
  490. if(isset($data[$name])) {
  491. $serialize[$name] = $data[$name];
  492. unset($data[$name]);
  493. }
  494. }
  495. if(!empty($serialize)) {
  496. $data[$key] = serialize($serialize);
  497. }
  498. }
  499. }
  500. }
  501. return $data;
  502. }
  503. // 检查返回数据的序列化字段
  504. protected function checkSerializeField(&$result) {
  505. // 检查序列化字段
  506. if(!empty($this->serializeField)) {
  507. foreach ($this->serializeField as $key=>$val){
  508. if(isset($result[$key])) {
  509. $serialize = unserialize($result[$key]);
  510. foreach ($serialize as $name=>$value)
  511. $result[$name] = $value;
  512. unset($serialize,$result[$key]);
  513. }
  514. }
  515. }
  516. return $result;
  517. }
  518. // 检查数据集的序列化字段
  519. protected function checkListSerializeField(&$resultSet) {
  520. // 检查序列化字段
  521. if(!empty($this->serializeField)) {
  522. foreach ($this->serializeField as $key=>$val){
  523. foreach ($resultSet as $k=>$result){
  524. if(isset($result[$key])) {
  525. $serialize = unserialize($result[$key]);
  526. foreach ($serialize as $name=>$value)
  527. $result[$name] = $value;
  528. unset($serialize,$result[$key]);
  529. $resultSet[$k] = $result;
  530. }
  531. }
  532. }
  533. }
  534. return $resultSet;
  535. }
  536. /**
  537. * 检查只读字段
  538. * @access protected
  539. * @param array $data 数据
  540. * @return array
  541. */
  542. protected function checkReadonlyField(&$data) {
  543. if(!empty($this->readonlyField)) {
  544. foreach ($this->readonlyField as $key=>$field){
  545. if(isset($data[$field]))
  546. unset($data[$field]);
  547. }
  548. }
  549. return $data;
  550. }
  551. /**
  552. * 批处理执行SQL语句
  553. * 批处理的指令都认为是execute操作
  554. * @access public
  555. * @param array $sql SQL批处理指令
  556. * @return boolean
  557. */
  558. public function patchQuery($sql=array()) {
  559. if(!is_array($sql)) return false;
  560. // 自动启动事务支持
  561. $this->startTrans();
  562. try{
  563. foreach ($sql as $_sql){
  564. $result = $this->execute($_sql);
  565. if(false === $result) {
  566. // 发生错误自动回滚事务
  567. $this->rollback();
  568. return false;
  569. }
  570. }
  571. // 提交事务
  572. $this->commit();
  573. } catch (ThinkException $e) {
  574. $this->rollback();
  575. }
  576. return true;
  577. }
  578. /**
  579. * 得到分表的的数据表名
  580. * @access public
  581. * @param array $data 操作的数据
  582. * @return string
  583. */
  584. public function getPartitionTableName($data=array()) {
  585. // 对数据表进行分区
  586. if(isset($data[$this->partition['field']])) {
  587. $field = $data[$this->partition['field']];
  588. switch($this->partition['type']) {
  589. case 'id':
  590. // 按照id范围分表
  591. $step = $this->partition['expr'];
  592. $seq = floor($field / $step)+1;
  593. break;
  594. case 'year':
  595. // 按照年份分表
  596. if(!is_numeric($field)) {
  597. $field = strtotime($field);
  598. }
  599. $seq = date('Y',$field)-$this->partition['expr']+1;
  600. break;
  601. case 'mod':
  602. // 按照id的模数分表
  603. $seq = ($field % $this->partition['num'])+1;
  604. break;
  605. case 'md5':
  606. // 按照md5的序列分表
  607. $seq = (ord(substr(md5($field),0,1)) % $this->partition['num'])+1;
  608. break;
  609. default :
  610. if(function_exists($this->partition['type'])) {
  611. // 支持指定函数哈希
  612. $fun = $this->partition['type'];
  613. $seq = (ord(substr($fun($field),0,1)) % $this->partition['num'])+1;
  614. }else{
  615. // 按照字段的首字母的值分表
  616. $seq = (ord($field{0}) % $this->partition['num'])+1;
  617. }
  618. }
  619. return $this->getTableName().'_'.$seq;
  620. }else{
  621. // 当设置的分表字段不在查询条件或者数据中
  622. // 进行联合查询,必须设定 partition['num']
  623. $tableName = array();
  624. for($i=0;$i<$this->partition['num'];$i++)
  625. $tableName[] = 'SELECT * FROM '.$this->getTableName().'_'.($i+1);
  626. $tableName = '( '.implode(" UNION ",$tableName).') AS '.$this->name;
  627. return $tableName;
  628. }
  629. }
  630. }