You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
595 lines
20 KiB
PHP
595 lines
20 KiB
PHP
<?php
|
|
// +----------------------------------------------------------------------
|
|
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
|
// +----------------------------------------------------------------------
|
|
// | Copyright (c) 2006-2014 http://thinkphp.cn All rights reserved.
|
|
// +----------------------------------------------------------------------
|
|
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
|
// +----------------------------------------------------------------------
|
|
// | Author: liu21st <liu21st@gmail.com>
|
|
// +----------------------------------------------------------------------
|
|
namespace Think\Model;
|
|
use Think\Model;
|
|
/**
|
|
* 高级模型扩展
|
|
*/
|
|
class AdvModel extends Model {
|
|
protected $optimLock = 'lock_version';
|
|
protected $returnType = 'array';
|
|
protected $blobFields = array();
|
|
protected $blobValues = null;
|
|
protected $serializeField = array();
|
|
protected $readonlyField = array();
|
|
protected $_filter = array();
|
|
protected $partition = array();
|
|
|
|
public function __construct($name='',$tablePrefix='',$connection='') {
|
|
if('' !== $name || is_subclass_of($this,'AdvModel') ){
|
|
// 如果是AdvModel子类或者有传入模型名称则获取字段缓存
|
|
}else{
|
|
// 空的模型 关闭字段缓存
|
|
$this->autoCheckFields = false;
|
|
}
|
|
parent::__construct($name,$tablePrefix,$connection);
|
|
}
|
|
|
|
/**
|
|
* 利用__call方法重载 实现一些特殊的Model方法 (魔术方法)
|
|
* @access public
|
|
* @param string $method 方法名称
|
|
* @param mixed $args 调用参数
|
|
* @return mixed
|
|
*/
|
|
public function __call($method,$args) {
|
|
if(strtolower(substr($method,0,3))=='top'){
|
|
// 获取前N条记录
|
|
$count = substr($method,3);
|
|
array_unshift($args,$count);
|
|
return call_user_func_array(array(&$this, 'topN'), $args);
|
|
}else{
|
|
return parent::__call($method,$args);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 对保存到数据库的数据进行处理
|
|
* @access protected
|
|
* @param mixed $data 要操作的数据
|
|
* @return boolean
|
|
*/
|
|
protected function _facade($data) {
|
|
// 检查序列化字段
|
|
$data = $this->serializeField($data);
|
|
return parent::_facade($data);
|
|
}
|
|
|
|
// 查询成功后的回调方法
|
|
protected function _after_find(&$result,$options='') {
|
|
// 检查序列化字段
|
|
$this->checkSerializeField($result);
|
|
// 获取文本字段
|
|
$this->getBlobFields($result);
|
|
// 检查字段过滤
|
|
$result = $this->getFilterFields($result);
|
|
// 缓存乐观锁
|
|
$this->cacheLockVersion($result);
|
|
}
|
|
|
|
// 查询数据集成功后的回调方法
|
|
protected function _after_select(&$resultSet,$options='') {
|
|
// 检查序列化字段
|
|
$resultSet = $this->checkListSerializeField($resultSet);
|
|
// 获取文本字段
|
|
$resultSet = $this->getListBlobFields($resultSet);
|
|
// 检查列表字段过滤
|
|
$resultSet = $this->getFilterListFields($resultSet);
|
|
}
|
|
|
|
// 写入前的回调方法
|
|
protected function _before_insert(&$data,$options='') {
|
|
// 记录乐观锁
|
|
$data = $this->recordLockVersion($data);
|
|
// 检查文本字段
|
|
$data = $this->checkBlobFields($data);
|
|
// 检查字段过滤
|
|
$data = $this->setFilterFields($data);
|
|
}
|
|
|
|
protected function _after_insert($data,$options) {
|
|
// 保存文本字段
|
|
$this->saveBlobFields($data);
|
|
}
|
|
|
|
// 更新前的回调方法
|
|
protected function _before_update(&$data,$options='') {
|
|
// 检查乐观锁
|
|
$pk = $this->getPK();
|
|
if(isset($options['where'][$pk])){
|
|
$id = $options['where'][$pk];
|
|
if(!$this->checkLockVersion($id,$data)) {
|
|
return false;
|
|
}
|
|
}
|
|
// 检查文本字段
|
|
$data = $this->checkBlobFields($data);
|
|
// 检查只读字段
|
|
$data = $this->checkReadonlyField($data);
|
|
// 检查字段过滤
|
|
$data = $this->setFilterFields($data);
|
|
}
|
|
|
|
protected function _after_update($data,$options) {
|
|
// 保存文本字段
|
|
$this->saveBlobFields($data);
|
|
}
|
|
|
|
protected function _after_delete($data,$options) {
|
|
// 删除Blob数据
|
|
$this->delBlobFields($data);
|
|
}
|
|
|
|
/**
|
|
* 记录乐观锁
|
|
* @access protected
|
|
* @param array $data 数据对象
|
|
* @return array
|
|
*/
|
|
protected function recordLockVersion($data) {
|
|
// 记录乐观锁
|
|
if($this->optimLock && !isset($data[$this->optimLock]) ) {
|
|
if(in_array($this->optimLock,$this->fields,true)) {
|
|
$data[$this->optimLock] = 0;
|
|
}
|
|
}
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* 缓存乐观锁
|
|
* @access protected
|
|
* @param array $data 数据对象
|
|
* @return void
|
|
*/
|
|
protected function cacheLockVersion($data) {
|
|
if($this->optimLock) {
|
|
if(isset($data[$this->optimLock]) && isset($data[$this->getPk()])) {
|
|
// 只有当存在乐观锁字段和主键有值的时候才记录乐观锁
|
|
$_SESSION[$this->name.'_'.$data[$this->getPk()].'_lock_version'] = $data[$this->optimLock];
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 检查乐观锁
|
|
* @access protected
|
|
* @param inteter $id 当前主键
|
|
* @param array $data 当前数据
|
|
* @return mixed
|
|
*/
|
|
protected function checkLockVersion($id,&$data) {
|
|
// 检查乐观锁
|
|
$identify = $this->name.'_'.$id.'_lock_version';
|
|
if($this->optimLock && isset($_SESSION[$identify])) {
|
|
$lock_version = $_SESSION[$identify];
|
|
$vo = $this->field($this->optimLock)->find($id);
|
|
$_SESSION[$identify] = $lock_version;
|
|
$curr_version = $vo[$this->optimLock];
|
|
if(isset($curr_version)) {
|
|
if($curr_version>0 && $lock_version != $curr_version) {
|
|
// 记录已经更新
|
|
$this->error = L('_RECORD_HAS_UPDATE_');
|
|
return false;
|
|
}else{
|
|
// 更新乐观锁
|
|
$save_version = $data[$this->optimLock];
|
|
if($save_version != $lock_version+1) {
|
|
$data[$this->optimLock] = $lock_version+1;
|
|
}
|
|
$_SESSION[$identify] = $lock_version+1;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 查找前N个记录
|
|
* @access public
|
|
* @param integer $count 记录个数
|
|
* @param array $options 查询表达式
|
|
* @return array
|
|
*/
|
|
public function topN($count,$options=array()) {
|
|
$options['limit'] = $count;
|
|
return $this->select($options);
|
|
}
|
|
|
|
/**
|
|
* 查询符合条件的第N条记录
|
|
* 0 表示第一条记录 -1 表示最后一条记录
|
|
* @access public
|
|
* @param integer $position 记录位置
|
|
* @param array $options 查询表达式
|
|
* @return mixed
|
|
*/
|
|
public function getN($position=0,$options=array()) {
|
|
if($position>=0) { // 正向查找
|
|
$options['limit'] = $position.',1';
|
|
$list = $this->select($options);
|
|
return $list?$list[0]:false;
|
|
}else{ // 逆序查找
|
|
$list = $this->select($options);
|
|
return $list?$list[count($list)-abs($position)]:false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取满足条件的第一条记录
|
|
* @access public
|
|
* @param array $options 查询表达式
|
|
* @return mixed
|
|
*/
|
|
public function first($options=array()) {
|
|
return $this->getN(0,$options);
|
|
}
|
|
|
|
/**
|
|
* 获取满足条件的最后一条记录
|
|
* @access public
|
|
* @param array $options 查询表达式
|
|
* @return mixed
|
|
*/
|
|
public function last($options=array()) {
|
|
return $this->getN(-1,$options);
|
|
}
|
|
|
|
/**
|
|
* 返回数据
|
|
* @access public
|
|
* @param array $data 数据
|
|
* @param string $type 返回类型 默认为数组
|
|
* @return mixed
|
|
*/
|
|
public function returnResult($data,$type='') {
|
|
if('' === $type)
|
|
$type = $this->returnType;
|
|
switch($type) {
|
|
case 'array' : return $data;
|
|
case 'object': return (object)$data;
|
|
default:// 允许用户自定义返回类型
|
|
if(class_exists($type))
|
|
return new $type($data);
|
|
else
|
|
E(L('_CLASS_NOT_EXIST_').':'.$type);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取数据的时候过滤数据字段
|
|
* @access protected
|
|
* @param mixed $result 查询的数据
|
|
* @return array
|
|
*/
|
|
protected function getFilterFields(&$result) {
|
|
if(!empty($this->_filter)) {
|
|
foreach ($this->_filter as $field=>$filter){
|
|
if(isset($result[$field])) {
|
|
$fun = $filter[1];
|
|
if(!empty($fun)) {
|
|
if(isset($filter[2]) && $filter[2]){
|
|
// 传递整个数据对象作为参数
|
|
$result[$field] = call_user_func($fun,$result);
|
|
}else{
|
|
// 传递字段的值作为参数
|
|
$result[$field] = call_user_func($fun,$result[$field]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
protected function getFilterListFields(&$resultSet) {
|
|
if(!empty($this->_filter)) {
|
|
foreach ($resultSet as $key=>$result)
|
|
$resultSet[$key] = $this->getFilterFields($result);
|
|
}
|
|
return $resultSet;
|
|
}
|
|
|
|
/**
|
|
* 写入数据的时候过滤数据字段
|
|
* @access protected
|
|
* @param mixed $result 查询的数据
|
|
* @return array
|
|
*/
|
|
protected function setFilterFields($data) {
|
|
if(!empty($this->_filter)) {
|
|
foreach ($this->_filter as $field=>$filter){
|
|
if(isset($data[$field])) {
|
|
$fun = $filter[0];
|
|
if(!empty($fun)) {
|
|
if(isset($filter[2]) && $filter[2]) {
|
|
// 传递整个数据对象作为参数
|
|
$data[$field] = call_user_func($fun,$data);
|
|
}else{
|
|
// 传递字段的值作为参数
|
|
$data[$field] = call_user_func($fun,$data[$field]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* 返回数据列表
|
|
* @access protected
|
|
* @param array $resultSet 数据
|
|
* @param string $type 返回类型 默认为数组
|
|
* @return void
|
|
*/
|
|
protected function returnResultSet(&$resultSet,$type='') {
|
|
foreach ($resultSet as $key=>$data)
|
|
$resultSet[$key] = $this->returnResult($data,$type);
|
|
return $resultSet;
|
|
}
|
|
|
|
protected function checkBlobFields(&$data) {
|
|
// 检查Blob文件保存字段
|
|
if(!empty($this->blobFields)) {
|
|
foreach ($this->blobFields as $field){
|
|
if(isset($data[$field])) {
|
|
if(isset($data[$this->getPk()]))
|
|
$this->blobValues[$this->name.'/'.$data[$this->getPk()].'_'.$field] = $data[$field];
|
|
else
|
|
$this->blobValues[$this->name.'/@?id@_'.$field] = $data[$field];
|
|
unset($data[$field]);
|
|
}
|
|
}
|
|
}
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* 获取数据集的文本字段
|
|
* @access protected
|
|
* @param mixed $resultSet 查询的数据
|
|
* @param string $field 查询的字段
|
|
* @return void
|
|
*/
|
|
protected function getListBlobFields(&$resultSet,$field='') {
|
|
if(!empty($this->blobFields)) {
|
|
foreach ($resultSet as $key=>$result){
|
|
$result = $this->getBlobFields($result,$field);
|
|
$resultSet[$key] = $result;
|
|
}
|
|
}
|
|
return $resultSet;
|
|
}
|
|
|
|
/**
|
|
* 获取数据的文本字段
|
|
* @access protected
|
|
* @param mixed $data 查询的数据
|
|
* @param string $field 查询的字段
|
|
* @return void
|
|
*/
|
|
protected function getBlobFields(&$data,$field='') {
|
|
if(!empty($this->blobFields)) {
|
|
$pk = $this->getPk();
|
|
$id = $data[$pk];
|
|
if(empty($field)) {
|
|
foreach ($this->blobFields as $field){
|
|
$identify = $this->name.'/'.$id.'_'.$field;
|
|
$data[$field] = F($identify);
|
|
}
|
|
return $data;
|
|
}else{
|
|
$identify = $this->name.'/'.$id.'_'.$field;
|
|
return F($identify);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 保存File方式的字段
|
|
* @access protected
|
|
* @param mixed $data 保存的数据
|
|
* @return void
|
|
*/
|
|
protected function saveBlobFields(&$data) {
|
|
if(!empty($this->blobFields)) {
|
|
foreach ($this->blobValues as $key=>$val){
|
|
if(strpos($key,'@?id@'))
|
|
$key = str_replace('@?id@',$data[$this->getPk()],$key);
|
|
F($key,$val);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 删除File方式的字段
|
|
* @access protected
|
|
* @param mixed $data 保存的数据
|
|
* @param string $field 查询的字段
|
|
* @return void
|
|
*/
|
|
protected function delBlobFields(&$data,$field='') {
|
|
if(!empty($this->blobFields)) {
|
|
$pk = $this->getPk();
|
|
$id = $data[$pk];
|
|
if(empty($field)) {
|
|
foreach ($this->blobFields as $field){
|
|
$identify = $this->name.'/'.$id.'_'.$field;
|
|
F($identify,null);
|
|
}
|
|
}else{
|
|
$identify = $this->name.'/'.$id.'_'.$field;
|
|
F($identify,null);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 检查序列化数据字段
|
|
* @access protected
|
|
* @param array $data 数据
|
|
* @return array
|
|
*/
|
|
protected function serializeField(&$data) {
|
|
// 检查序列化字段
|
|
if(!empty($this->serializeField)) {
|
|
// 定义方式 $this->serializeField = array('ser'=>array('name','email'));
|
|
foreach ($this->serializeField as $key=>$val){
|
|
if(empty($data[$key])) {
|
|
$serialize = array();
|
|
foreach ($val as $name){
|
|
if(isset($data[$name])) {
|
|
$serialize[$name] = $data[$name];
|
|
unset($data[$name]);
|
|
}
|
|
}
|
|
if(!empty($serialize)) {
|
|
$data[$key] = serialize($serialize);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return $data;
|
|
}
|
|
|
|
// 检查返回数据的序列化字段
|
|
protected function checkSerializeField(&$result) {
|
|
// 检查序列化字段
|
|
if(!empty($this->serializeField)) {
|
|
foreach ($this->serializeField as $key=>$val){
|
|
if(isset($result[$key])) {
|
|
$serialize = unserialize($result[$key]);
|
|
foreach ($serialize as $name=>$value)
|
|
$result[$name] = $value;
|
|
unset($serialize,$result[$key]);
|
|
}
|
|
}
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
// 检查数据集的序列化字段
|
|
protected function checkListSerializeField(&$resultSet) {
|
|
// 检查序列化字段
|
|
if(!empty($this->serializeField)) {
|
|
foreach ($this->serializeField as $key=>$val){
|
|
foreach ($resultSet as $k=>$result){
|
|
if(isset($result[$key])) {
|
|
$serialize = unserialize($result[$key]);
|
|
foreach ($serialize as $name=>$value)
|
|
$result[$name] = $value;
|
|
unset($serialize,$result[$key]);
|
|
$resultSet[$k] = $result;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return $resultSet;
|
|
}
|
|
|
|
/**
|
|
* 检查只读字段
|
|
* @access protected
|
|
* @param array $data 数据
|
|
* @return array
|
|
*/
|
|
protected function checkReadonlyField(&$data) {
|
|
if(!empty($this->readonlyField)) {
|
|
foreach ($this->readonlyField as $key=>$field){
|
|
if(isset($data[$field]))
|
|
unset($data[$field]);
|
|
}
|
|
}
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* 批处理执行SQL语句
|
|
* 批处理的指令都认为是execute操作
|
|
* @access public
|
|
* @param array $sql SQL批处理指令
|
|
* @return boolean
|
|
*/
|
|
public function patchQuery($sql=array()) {
|
|
if(!is_array($sql)) return false;
|
|
// 自动启动事务支持
|
|
$this->startTrans();
|
|
try{
|
|
foreach ($sql as $_sql){
|
|
$result = $this->execute($_sql);
|
|
if(false === $result) {
|
|
// 发生错误自动回滚事务
|
|
$this->rollback();
|
|
return false;
|
|
}
|
|
}
|
|
// 提交事务
|
|
$this->commit();
|
|
} catch (ThinkException $e) {
|
|
$this->rollback();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 得到分表的的数据表名
|
|
* @access public
|
|
* @param array $data 操作的数据
|
|
* @return string
|
|
*/
|
|
public function getPartitionTableName($data=array()) {
|
|
// 对数据表进行分区
|
|
if(isset($data[$this->partition['field']])) {
|
|
$field = $data[$this->partition['field']];
|
|
switch($this->partition['type']) {
|
|
case 'id':
|
|
// 按照id范围分表
|
|
$step = $this->partition['expr'];
|
|
$seq = floor($field / $step)+1;
|
|
break;
|
|
case 'year':
|
|
// 按照年份分表
|
|
if(!is_numeric($field)) {
|
|
$field = strtotime($field);
|
|
}
|
|
$seq = date('Y',$field)-$this->partition['expr']+1;
|
|
break;
|
|
case 'mod':
|
|
// 按照id的模数分表
|
|
$seq = ($field % $this->partition['num'])+1;
|
|
break;
|
|
case 'md5':
|
|
// 按照md5的序列分表
|
|
$seq = (ord(substr(md5($field),0,1)) % $this->partition['num'])+1;
|
|
break;
|
|
default :
|
|
if(function_exists($this->partition['type'])) {
|
|
// 支持指定函数哈希
|
|
$fun = $this->partition['type'];
|
|
$seq = (ord(substr($fun($field),0,1)) % $this->partition['num'])+1;
|
|
}else{
|
|
// 按照字段的首字母的值分表
|
|
$seq = (ord($field{0}) % $this->partition['num'])+1;
|
|
}
|
|
}
|
|
return $this->getTableName().'_'.$seq;
|
|
}else{
|
|
// 当设置的分表字段不在查询条件或者数据中
|
|
// 进行联合查询,必须设定 partition['num']
|
|
$tableName = array();
|
|
for($i=0;$i<$this->partition['num'];$i++)
|
|
$tableName[] = 'SELECT * FROM '.$this->getTableName().'_'.($i+1);
|
|
$tableName = '( '.implode(" UNION ",$tableName).') AS '.$this->name;
|
|
return $tableName;
|
|
}
|
|
}
|
|
} |