📄 正在查看:twcms/kongphp/base/model.class.php
1<?php
2/**
3 * Copyright (C) 2013-2014 www.kongphp.com All rights reserved.
4 * Licensed http://www.gnu.org/licenses/lgpl.html
5 * Author: wuzhaohuan <kongphp@gmail.com>
6 */
7
8/*
9在模型设计上我思考了很久,最终确定参考xiunophp的设计:统一 cache+db 接口,并设计了二级缓存,进一步减轻数据库压力。 2013.06.27
10开启 cache 时:
111、读取:先读cache,缓存没有时读db,并写入cache。
122、写入:同时写入 cache 和 db。
13
14对外开放的最常用的15个方法
15cache + db + model:
16 $this->create();
17 $this->update();
18 $this->read();
19 $this->delete();
20
21 $this->get();
22 $this->set();
23 $this->truncate();
24 $this->maxid();
25 $this->count();
26
27 $this->find_fetch(); // 支持二级缓存
28 $this->find_fetch_key(); // 支持二级缓存
29 $this->find_update();
30 $this->find_delete();
31 $this->find_maxid();
32 $this->find_count();
33*/
34
35class model{
36 // 每个模型都可以有自己的 db、cache 服务器
37 //public $db_conf = array();
38 //public $cache_conf = array();
39
40 // 必须指定这三项
41 public $table; // 表名
42 public $pri = array(); // 主键字段,如 ('cid'), ('cid', 'id')
43 public $maxid; // 自增字段
44
45 // 避免重复链接
46 static $dbs = array();
47 static $caches = array();
48 private $unique = array(); // 防止重复查询
49
50 /**
51 * 创建一次 db/cache 对象
52 * @param string $var 只能是 db cache
53 * @return object
54 */
55 function __get($var) {
56 switch ($var) {
57 case 'db':
58 return $this->db = $this->load_db();
59 case 'cache':
60 return $this->cache = $this->load_cache();
61 case 'db_conf':
62 return $this->db_conf = &$_ENV['_config']['db'];
63 case 'cache_conf':
64 return $this->cache_conf = &$_ENV['_config']['cache'];
65 default:
66 return $this->$var = core::model($var);
67 }
68 }
69
70 /**
71 * 未定义的模型方法抛出异常
72 * @param string $method 不存在的方法名
73 */
74 function __call($method, $args) {
75 throw new Exception("方法 $method 不存在");
76 }
77
78 /**
79 * 加载 db 对象
80 * @return object
81 */
82 public function load_db() {
83 $type = $this->db_conf['type'];
84 if(isset($this->db_conf['master'])) {
85 $m = $this->db_conf['master'];
86 $id = $type.'-'.$m['host'].'-'.$m['user'].'-'.$m['password'].'-'.$m['name'].'-'.$m['tablepre'];
87 }else{
88 $id = $type;
89 }
90
91 if(isset(self::$dbs[$id])) {
92 return self::$dbs[$id];
93 }else{
94 $db = 'db_'.$type;
95 self::$dbs[$id] = new $db($this->db_conf);
96 return self::$dbs[$id];
97 }
98 }
99
100 /**
101 * 加载 cache 对象
102 * @return object
103 */
104 public function load_cache() {
105 $type = $this->cache_conf['type'];
106
107 if(isset($this->cache_conf[$type])) {
108 $c = $this->cache_conf[$type];
109 $id = $type.'-'.$c['host'].'-'.$c['port'];
110 }else{
111 $id = $type;
112 }
113
114 if(isset(self::$caches[$id])) {
115 return self::$caches[$id];
116 }else{
117 $cache = 'cache_'.$type;
118 self::$caches[$id] = new $cache($this->cache_conf);
119 return self::$caches[$id];
120 }
121 }
122
123 // +---------------------------------------------------------------
124 // | cache + db + 模型 封装方法,所有符合标准的表结构都可以使用以下方法
125 // +---------------------------------------------------------------
126 /**
127 * 创建一条数据
128 * @param array $data 数据 (注意:不要包含自增字段)
129 * @return boot
130 */
131 public function create($data) {
132 // 如果没有自增字段,则不统计 count() maxid()
133 if(empty($this->maxid)) {
134 $key = $this->pri2key($data);
135 return $this->cache_db_set($key, $data);
136 }else{
137 // 注意:因为考虑了多种情况,更换顺序或简化代码会造成问题
138 $data[$this->maxid] = $this->maxid('+1');
139 $key = $this->pri2key($data);
140 $this->count('+1');
141 if($this->cache_db_set($key, $data)) {
142 return $data[$this->maxid];
143 }else{
144 $this->maxid('-1');
145 $this->count('-1');
146 return FALSE;
147 }
148 }
149 }
150
151 /**
152 * 写入一条数据
153 * @param array $key 键名数组
154 * @param array $data 数据
155 * @param int $life 缓存时间 (默认为永久)
156 * @return bool
157 */
158 /*
159 此接口中的 $key 参数格式不同于 cache, db 中的 set()
160 例子:
161 $this->user->set(1, array('username'=>'2b', 'password'=>'123'));
162 $this->user->set(array(1, 2), array('username'=>'2b', 'password'=>'123'));
163 */
164 public function set($key, $data, $life = 0) {
165 $key = $this->arr2key($key);
166 $this->unique[$key] = $data;
167 return $this->cache_db_set($key, $data, $life);
168 }
169
170 /**
171 * 读取一条数据 (简化数组, 如: read(1,2,3,4) 表示 get(array(1,2,3,4)) 最多支持4个参数)
172 * @param string $arg1-$arg4 参数1-参数4
173 * @return array
174 */
175 public function read($arg1, $arg2 = FALSE, $arg3 = FALSE, $arg4 = FALSE) {
176 $arr = ($arg2 !== FALSE) ? $this->arg2arr($arg1, $arg2, $arg3, $arg4) : (array)$arg1;
177 return $this->get($arr);
178 }
179
180 /**
181 * 读取一条数据
182 * @param array $arr 键名数组 提示:主键一列:array(1) 主键多列:array(1, 2)
183 * @return array
184 */
185 // 技巧:可以简写成 1 程序会自动转换成 array(1)
186 public function get($arr) {
187 $key = $this->arr2key($arr);
188 if(!isset($this->unique[$key])) {
189 $this->unique[$key] = $this->cache_db_get($key);
190 }
191 return $this->unique[$key];
192 }
193
194 /**
195 * 读取多条数据 (multi_get 简写成 mget)
196 * @param array $arr 多列键名数组 (提示: 主键一列时使用一维数组,主键多列时使用二维数组)
197 * @return array
198 */
199 // 主键一列:mget(array(1, 2, 3));
200 // 主键多列:mget(array(array(1, 1), array(1, 2), array(1, 3)));
201 public function mget($arr) {
202 $data = array();
203 foreach($arr as $k=>&$key) {
204 $key = $this->arr2key($key);
205 if(isset($this->unique[$key])) {
206 $data[$key] = $this->unique[$key];
207 unset($arr[$k]);
208 }else{
209 $this->unique[$key] = $data[$key] = NULL; // 占位 保证返回数组顺序
210 }
211 }
212 $data2 = $this->cache_db_multi_get($arr);
213 return array_merge($data, $data2);
214 }
215
216 /**
217 * 更新一条数据
218 * @param array $data 数据 (必须包含主键)
219 * @param int $life 缓存时间 (默认为永久)
220 * @return bool
221 */
222 public function update($data, $life = 0) {
223 $key = $this->pri2key($data);
224 $this->unique[$key] = $data;
225 return $this->cache_db_update($key, $data, $life);
226 }
227
228 /**
229 * 删除一条数据 (简化数组, 如: delete(1,2,3,4) 表示 del(array(1,2,3,4)) 最多支持4个参数)
230 * @param string $arg1-$arg4 参数1-参数4
231 * @return array
232 */
233 public function delete($arg1, $arg2 = FALSE, $arg3 = FALSE, $arg4 = FALSE) {
234 $arr = ($arg2 !== FALSE) ? $this->arg2arr($arg1, $arg2, $arg3, $arg4) : (array)$arg1;
235 return $this->del($arr);
236 }
237
238 /**
239 * 删除一条数据
240 * @param string $arr 键名数组
241 * @return bool
242 */
243 public function del($arr) {
244 $key = $this->arr2key($arr);
245 $ret = $this->cache_db_delete($key);
246 if($ret) {
247 unset($this->unique[$key]);
248 $this->count('-1');
249 }
250 return $ret;
251 }
252
253 /**
254 * cache+db 清空
255 * @return boot
256 */
257 public function truncate() {
258 return $this->cache_db_truncate();
259 }
260
261 /**
262 * cache+db 读取/设置 表最大ID
263 * @param string $val 设置值 有三种情况 1.不填为读取(默认) 2.基础上增加 如:'+1' 3.设置指定值
264 * @return int 返回最大ID
265 */
266 public function maxid($val = FALSE) {
267 return $this->cache_db_maxid($val);
268 }
269
270 /**
271 * cache+db 读取/设置 表的总行数
272 * @param string $val 设置值 如:count() 读取、 count(100) 设置为100、 count('+1') 设置加1、 count('-1') 设置减1
273 * @return int
274 */
275 public function count($val = FALSE) {
276 return $this->cache_db_count($val);
277 }
278
279 /**
280 * 根据条件读取数据
281 * @param array $where 条件
282 * @param array $order 排序
283 * @param int $start 开始位置
284 * @param int $limit 读取几条
285 * @param int $life 二级缓存时间 (默认为永久)
286 * @return array
287 */
288 public function find_fetch($where = array(), $order = array(), $start = 0, $limit = 0, $life = 0) {
289 return $this->cache_db_find_fetch($this->table, $this->pri, $where, $order, $start, $limit, $life);
290 }
291
292 /**
293 * 根据条件返回 key 数组
294 * @param array $where 条件
295 * @param array $order 排序
296 * @param int $start 开始位置
297 * @param int $limit 读取几条
298 * @param int $life 二级缓存时间 (默认为永久)
299 * @return array
300 */
301 public function find_fetch_key($where = array(), $order = array(), $start = 0, $limit = 0, $life = 0) {
302 return $this->cache_db_find_fetch_key($this->table, $this->pri, $where, $order, $start, $limit, $life);
303 }
304
305 /**
306 * 根据条件批量更新数据 (不建议用来更新大量数据,太暴力了)
307 * @param array $where 条件
308 * @param array $lowprority 是否开启不锁定表
309 * @return int 返回影响的记录行数
310 */
311 public function find_update($where, $data, $lowprority = FALSE) {
312 $this->unique = array();
313 if($this->cache_conf['enable']) {
314 $n = $this->find_count($where);
315 if($n > 2000) {
316 $this->cache->truncate($this->table);
317 }else{
318 $keys = $this->find_fetch_key($where);
319 foreach($keys as $key) {
320 $this->cache->delete($key);
321 }
322 }
323 }
324 return $this->db->find_update($this->table, $where, $data, $lowprority);
325 }
326
327 /**
328 * 根据条件批量删除数据 (不建议用来删除大量数据,太暴力了)
329 * @param array $where 条件
330 * @param array $lowprority 是否开启不锁定表
331 * @return int 返回影响的记录行数
332 */
333 public function find_delete($where, $lowprority = FALSE) {
334 $this->unique = array();
335 if($this->cache_conf['enable']) {
336 $n = $this->find_count($where);
337 if($n > 2000) {
338 $this->cache->truncate($this->table);
339 }else{
340 $keys = $this->find_fetch_key($where);
341 foreach($keys as $key) {
342 $this->cache_db_delete($key);
343 }
344 }
345 }
346 $num = $this->db->find_delete($this->table, $where, $lowprority);
347
348 if(!empty($this->maxid) && $num > 0) {
349 $this->count('-'.$num);
350 }
351 return $num;
352 }
353
354 /**
355 * 准确获取最大ID (速度慢)
356 * @param string $key 键名
357 * @return int 返回ID
358 */
359 public function find_maxid() {
360 return isset($this->maxid) ? $this->db->find_maxid($this->table.'-'.$this->maxid) : 0;
361 }
362
363 /**
364 * 准确获取总条数 (速度慢)
365 * @param array $where 条件
366 * @return int 返回条数
367 */
368 public function find_count($where = array()) {
369 return $this->db->find_count($this->table, $where);
370 }
371
372 /**
373 * 创建索引
374 * @param array $index 键名数组 // array('uid'=>1, 'dateline'=>-1, 'unique'=>TRUE, 'dropDups'=>TRUE) 为了配合 mongodb 的索引才这样设计的
375 * @return boot 返回ID
376 */
377 public function index_create($index) {
378 return $this->db->index_create($this->table, $index);
379 }
380
381 /**
382 * 删除索引
383 * @param array $index 键名数组
384 * @return boot 返回ID
385 */
386 public function index_drop($index) {
387 return $this->db->index_drop($this->table, $index);
388 }
389
390 /**
391 * 主键 转 key
392 * @param array $arr 数组 (关联数组)
393 * @return string 返回标准KEY
394 */
395 public function pri2key($arr) {
396 $s = $this->table;
397 foreach($this->pri as $v) {
398 $s .= "-$v-".$arr[$v];
399 }
400 return $s;
401 }
402
403 /**
404 * 数组 转 key
405 * @param array $arr 数组 (数字数组)
406 * @return string 返回标准KEY
407 */
408 public function arr2key($arr) {
409 $arr = (array)$arr;
410 $s = $this->table;
411 foreach($this->pri as $k=>$v) {
412 if(!isset($arr[$k])) {
413 $err = array();
414 foreach($this->pri as $pk=>$pv) {
415 $var = isset($arr[$pk]) ? $arr[$pk] : 'null';
416 $err[] = "'$pv => $var";
417 }
418 throw new Exception('非法键名数组: array('.implode(', ', $err).');');
419 }
420 $s .= "-$v-".$arr[$k];
421 }
422 return $s;
423 }
424
425 /**
426 * 多参数 转 数组
427 * @param int $arg1-$arg4 参数1-参数4
428 * @return array
429 */
430 public function arg2arr($arg1, $arg2, $arg3 = FALSE, $arg4 = FALSE) {
431 $arr = (array)$arg1;
432 array_push($arr, $arg2);
433 $arg3 !== FALSE && array_push($arr, $arg3);
434 $arg4 !== FALSE && array_push($arr, $arg4);
435 return $arr;
436 }
437
438 // +------------------------------------------------------------------------------
439 // | cache + db 封装方法 (启用cache时优先读缓存) 不推荐外部使用
440 // +------------------------------------------------------------------------------
441 /**
442 * cache+db 读取一条数据
443 * @param string $key 键名
444 * @return mixed
445 */
446 public function cache_db_get($key) {
447 if($this->cache_conf['enable']) {
448 $data = $this->cache->get($key);
449 if(empty($data)) {
450 $data = $this->db->get($key);
451 $this->cache->set($key, $data);
452 }
453 return $data;
454 }else{
455 return $this->db->get($key);
456 }
457 }
458
459 /**
460 * cache+db 读取多条数据
461 * @param array $keys 键名数组
462 * @return array
463 */
464 public function cache_db_multi_get($keys) {
465 if($this->cache_conf['enable']) {
466 $data = $this->cache->multi_get($keys);
467 if(empty($data)) {
468 $data = $this->db->multi_get($keys);
469 foreach((array)$data as $k=>$v) {
470 $this->cache->set($k, $v);
471 }
472 }else{
473 foreach($data as $k=>&$v) {
474 if($v === FALSE) { // 等于 FALSE 时表示缓存不存在
475 $v = $this->db->get($k);
476 $this->cache->set($k, $v);
477 }
478 }
479 }
480 return $data;
481 }else{
482 return $this->db->multi_get($keys);
483 }
484 }
485
486 /**
487 * cache+db 写入一条数据
488 * @param string $key 键名
489 * @param mixed $data 数据
490 * @param int $life 缓存时间 (默认为永久)
491 * @return boot
492 */
493 public function cache_db_set($key, $data, $life = 0) {
494 $this->cache_conf['enable'] && $this->cache->set($key, $data, $life);
495 return $this->db->set($key, $data);
496 }
497
498 /**
499 * cache+db 更新一条数据
500 * @param string $key 键名
501 * @param array $data 数据
502 * @param int $life 缓存时间 (默认为永久)
503 * @return boot
504 */
505 public function cache_db_update($key, $data, $life = 0) {
506 $this->cache_conf['enable'] && $this->cache->update($key, $data, $life);
507 return $this->db->update($key, $data);
508 }
509
510 /**
511 * cache+db 删除一条数据
512 * @param string $key 键名
513 * @return boot
514 */
515 public function cache_db_delete($key) {
516 $this->cache_conf['enable'] && $this->cache->delete($key);
517 return $this->db->delete($key);
518 }
519
520 /**
521 * cache+db 清空数据
522 * @return boot
523 */
524 public function cache_db_truncate() {
525 $this->cache_conf['enable'] && $this->cache->truncate($this->table);
526 return $this->db->truncate($this->table);
527 }
528
529 /**
530 * cache+db 读取/设置 表最大ID
531 * @param string $val 设置值 有三种情况 1.不填为读取(默认) 2.基础上增加 如:'+1' 3.设置指定值
532 * @return int 返回最大ID
533 */
534 public function cache_db_maxid($val = FALSE) {
535 $key = $this->table.'-'.$this->maxid;
536 if($this->cache_conf['enable']) {
537 if($val === FALSE) {
538 $maxid = $this->cache->maxid($key, $val);
539 if(empty($maxid)) {
540 $maxid = $this->db->maxid($key, $val);
541 $this->cache->maxid($key, $maxid);
542 }
543 return $maxid;
544 }else{
545 $maxid = $this->db->maxid($key, $val);
546 return $this->cache->maxid($key, $maxid);
547 }
548 }else{
549 return $this->db->maxid($key, $val);
550 }
551 }
552
553 /**
554 * cache+db 读取/设置 表的总行数
555 * @param string $val 设置值 有四种情况 1.不填为读取(默认) 2.基础上增加 如:'+1' 3.基础上减少 如:'-1' 4.设置指定值
556 * @return int
557 */
558 public function cache_db_count($val = FALSE) {
559 $key = $this->table;
560 if($this->cache_conf['enable']) {
561 if($val === FALSE) {
562 $rows = $this->cache->count($key, $val);
563 if(empty($rows)) {
564 $rows = $this->db->count($key, $val);
565 $this->cache->count($key, $rows);
566 }
567 return $rows;
568 }else{
569 $rows = $this->db->count($key, $val);
570 return $this->cache->count($key, $rows);
571 }
572 }else{
573 return $this->db->count($key, $val);
574 }
575 }
576
577 /**
578 * cache+db 根据条件读取数据
579 * @param string $table 表名
580 * @param array $pri 主键
581 * @param array $where 条件
582 * @param array $order 排序
583 * @param int $start 开始位置
584 * @param int $limit 读取几条
585 * @param int $life 二级缓存时间 (默认为永久)
586 * @return array
587 */
588 public function cache_db_find_fetch($table, $pri, $where = array(), $order = array(), $start = 0, $limit = 0, $life = 0) {
589 // 如果是 mongodb 就直接取数据,不支持缓存
590 if($this->db_conf['type'] == 'mongodb') {
591 return $this->db->find_fetch($table, $pri, $where, $order, $start, $limit);
592 }else{
593 $keys = $this->cache_db_find_fetch_key($table, $pri, $where, $order, $start, $limit, $life);
594 return $this->cache_db_multi_get($keys);
595 }
596 }
597
598 /**
599 * cache+db 根据条件返回 key 数组
600 * @param string $table 表名
601 * @param array $pri 主键
602 * @param array $where 条件
603 * @param array $order 排序
604 * @param int $start 开始位置
605 * @param int $limit 读取几条
606 * @param int $life 二级缓存时间 (默认为永久)
607 * @return array
608 */
609 public function cache_db_find_fetch_key($table, $pri, $where = array(), $order = array(), $start = 0, $limit = 0, $life = 0) {
610 if($this->cache_conf['enable'] && $this->cache_conf['l2_cache'] === 1) {
611 $key = $table.'_'.md5(serialize(array($pri, $where, $order, $start, $limit)));
612 $keys = $this->cache->l2_cache_get($key);
613 if(empty($keys)) {
614 $keys = $this->db->find_fetch_key($table, $pri, $where, $order, $start, $limit);
615 $this->cache->l2_cache_set($key, $keys, $life);
616 }
617 }else{
618 $keys = $this->db->find_fetch_key($table, $pri, $where, $order, $start, $limit);
619 }
620 return $keys;
621 }
622}
623