/*** ThinkPHP Model模型类* 实现了ORM和ActiveRecords模式* @category   Think* @package  Think* @subpackage  Core* @author    liu21st <liu21st@gmail.com>*/
class Model {// 操作状态const MODEL_INSERT          =   1;      //  插入模型数据const MODEL_UPDATE          =   2;      //  更新模型数据const MODEL_BOTH            =   3;      //  包含上面两种方式const MUST_VALIDATE         =   1;// 必须验证const EXISTS_VALIDATE       =   0;// 表单存在字段则验证const VALUE_VALIDATE        =   2;// 表单值不为空则验证// 当前使用的扩展模型private   $_extModel        =   null;// 当前数据库操作对象protected $db               =   null;// 主键名称protected $pk               =   'id';// 数据表前缀protected $tablePrefix      =   '';// 模型名称protected $name             =   '';// 数据库名称protected $dbName           =   '';//数据库配置protected $connection       =   '';// 数据表名(不包含表前缀)protected $tableName        =   '';// 实际数据表名(包含表前缀)protected $trueTableName    =   '';// 最近错误信息protected $error            =   '';// 数据表的所有字段信息protected $fields           =   array();// 数据信息protected $data             =   array();// 查询表达式参数protected $options          =   array();protected $_validate        =   array();  // 自动验证定义protected $_auto            =   array();  // 自动完成定义protected $_map             =   array();  // 字段映射定义protected $_scope           =   array();  // 命名范围定义// 是否自动检测数据表字段信息protected $autoCheckFields  =   true;// 是否批处理验证protected $patchValidate    =   false;// 链操作方法列表protected $methods          =   array('table','order','alias','having','group','lock','distinct','auto','filter','validate');/*** 架构函数* 取得DB类的实例对象 字段检查* @access public* @param string $name 模型名称* @param string $tablePrefix 表前缀* @param mixed $connection 数据库连接信息*/public function __construct($name='',$tablePrefix='',$connection='') {// 模型初始化$this->_initialize();// 获取模型名称if(!empty($name)) {if(strpos($name,'.')) { // 支持 数据库名.模型名的 定义//属性1-数据库名   属性2-模型表名(数据表名)list($this->dbName,$this->name) = explode('.',$name);}else{//模型名$this->name   =  $name;}}elseif(empty($this->name)){//模型名为空的情况$this->name =   $this->getModelName();}// 设置表前缀,检查$tablePrefix表前缀参数是否为Null,注意:不是检查表前缀参数是否为空if(is_null($tablePrefix)) {// 如果表前缀参数为Null表示没有前缀$this->tablePrefix = '';//没有前缀的情况}elseif('' != $tablePrefix) {//如果表前缀参数不为空$this->tablePrefix = $tablePrefix;}else{//默认执行:如果类中未指定表前缀,那么将采用系统默认指定的表前缀$this->tablePrefix = $this->tablePrefix?$this->tablePrefix:C('DB_PREFIX');}// 数据库初始化操作// 获取数据库操作对象// 当前模型有独立的数据库连接信息$this->db(0,empty($this->connection)?$connection:$this->connection);}/*** 自动检测数据表信息* @access protected* @return void*/protected function _checkTableInfo() {// 如果不是Model类 自动记录数据表信息// 只在第一次执行记录if(empty($this->fields)) {//默认执行// 如果数据表字段没有定义则自动获取//调试模式下不执行if(C('DB_FIELDS_CACHE')) {//字段缓存的启用$db   =  $this->dbName?$this->dbName:C('DB_NAME');//数据库名$fields = F('_fields/'.strtolower($db.'.'.$this->name));if($fields) {$version    =   C('DB_FIELD_VERISON');if(empty($version) || $fields['_version']== $version) {$this->fields   =   $fields;return ;}}}// 每次都会读取数据表信息$this->flush();}}/*** 获取字段信息并缓存* @access public* @return void*/public function flush() {//$this->db:数据库驱动类Db,class.php实例化对象,查看$this->db()方法// 缓存不存在则查询数据表信息//var_dump($this->db);//DbMysql.class.php驱动类对象//setModel()方法为Db.class.php中方法//class DbMysql extends Db{}$this->db->setModel($this->name);//$this->getTableName():获取表名,如:think_user//getFields方法获取数据表每个字段的信息/***[id] => Array(*   [name] => id*   [type] => smallint(4) unsigned*   [notnull] => *   [default] => *   [primary] => 1*   [autoinc] => 1*    )*/$fields =   $this->db->getFields($this->getTableName());if(!$fields) { // 无法获取字段信息return false;}/*** Array(*        [0] => id*        [1] => title*        [2] => content*        [3] => create_time*    )*/$this->fields   =   array_keys($fields);$this->fields['_autoinc'] = false;//注意:循环的不是$this->fieldsforeach ($fields as $key=>$val){// 记录字段类型$type[$key]    =   $val['type'];//如:$type[id]=intif($val['primary']) {$this->fields['_pk'] = $key;//主键字段,这里的$key为字段名if($val['autoinc']) $this->fields['_autoinc']   =   true;//自动增长}}// 记录字段类型信息$this->fields['_type'] =  $type;//表中各个字段的类型if(C('DB_FIELD_VERISON')) $this->fields['_version'] =   C('DB_FIELD_VERISON');// 2008-3-7 增加缓存开关控制//调试模式下,为禁用,即:不会缓存if(C('DB_FIELDS_CACHE')){// 永久缓存数据表信息$db   =  $this->dbName?$this->dbName:C('DB_NAME');F('_fields/'.strtolower($db.'.'.$this->name),$this->fields);}}/*** 设置数据对象的值* @access public* @param string $name 名称* @param mixed $value 值* @return void*/public function __set($name,$value) {// 设置数据对象属性$this->data[$name]  =   $value;}/*** 获取数据对象的值* @access public* @param string $name 名称* @return mixed*/public function __get($name) {return isset($this->data[$name])?$this->data[$name]:null;}/*** 检测数据对象的值* @access public* @param string $name 名称* @return boolean*/public function __isset($name) {return isset($this->data[$name]);}/*** 销毁数据对象的值* @access public* @param string $name 名称* @return void*/public function __unset($name) {unset($this->data[$name]);}/*** 利用__call方法实现一些特殊的Model方法* @access public* @param string $method 方法名称* @param array $args 调用参数* @return mixed*/public function __call($method,$args) {if(in_array(strtolower($method),$this->methods,true)) {// 连贯操作的实现$this->options[strtolower($method)] =   $args[0];return $this;}elseif(in_array(strtolower($method),array('count','sum','min','max','avg'),true)){// 统计查询的实现$field =  isset($args[0])?$args[0]:'*';return $this->getField(strtoupper($method).'('.$field.') AS tp_'.$method);}elseif(strtolower(substr($method,0,5))=='getby') {// 根据某个字段获取记录$field   =   parse_name(substr($method,5));$where[$field] =  $args[0];return $this->where($where)->find();}elseif(strtolower(substr($method,0,10))=='getfieldby') {// 根据某个字段获取记录的某个值$name   =   parse_name(substr($method,10));$where[$name] =$args[0];return $this->where($where)->getField($args[1]);}elseif(isset($this->_scope[$method])){// 命名范围的单独调用支持return $this->scope($method,$args[0]);}else{throw_exception(__CLASS__.':'.$method.L('_METHOD_NOT_EXIST_'));return;}}// 回调方法 初始化模型protected function _initialize() {}/*** 对保存到数据库的数据进行处理* @access protected* @param mixed $data 要操作的数据* @return boolean*/protected function _facade($data) {// 检查非数据字段if(!empty($this->fields)) {foreach ($data as $key=>$val){if(!in_array($key,$this->fields,true)){unset($data[$key]);}elseif(is_scalar($val)) {// 字段类型检查$this->_parseType($data,$key);}}}// 安全过滤if(!empty($this->options['filter'])) {$data = array_map($this->options['filter'],$data);unset($this->options['filter']);}$this->_before_write($data);return $data;}// 写入数据前的回调方法 包括新增和更新protected function _before_write(&$data) {}/*** 新增数据* @access public* @param mixed $data 数据* @param array $options 表达式* @param boolean $replace 是否replace* @return mixed*/public function add($data='',$options=array(),$replace=false) {if(empty($data)) {// 没有传递数据,获取当前数据对象的值if(!empty($this->data)) {$data           =   $this->data;//数据对象中的数据// 重置数据$this->data     = array();}else{$this->error    = L('_DATA_TYPE_INVALID_');return false;}}// 分析表达式$options    =   $this->_parseOptions($options);// 数据处理$data       =   $this->_facade($data);//对保存到数据库中的数据进行处理if(false === $this->_before_insert($data,$options)) {return false;}// 写入数据到数据库/*** $options:默认值有下面两个* Array([table] => think_form[model] => Form)*/$result = $this->db->insert($data,$options,$replace);if(false !== $result ) {$insertId   =   $this->getLastInsID();if($insertId) {// 自增主键返回插入ID$data[$this->getPk()]  = $insertId;$this->_after_insert($data,$options);return $insertId;}$this->_after_insert($data,$options);}return $result;}// 插入数据前的回调方法protected function _before_insert(&$data,$options) {}// 插入成功后的回调方法protected function _after_insert($data,$options) {}public function addAll($dataList,$options=array(),$replace=false){if(empty($dataList)) {$this->error = L('_DATA_TYPE_INVALID_');return false;}// 分析表达式$options =  $this->_parseOptions($options);// 数据处理foreach ($dataList as $key=>$data){$dataList[$key] = $this->_facade($data);}// 写入数据到数据库$result = $this->db->insertAll($dataList,$options,$replace);if(false !== $result ) {$insertId   =   $this->getLastInsID();if($insertId) {return $insertId;}}return $result;}/*** 通过Select方式添加记录* @access public* @param string $fields 要插入的数据表字段名* @param string $table 要插入的数据表名* @param array $options 表达式* @return boolean*/public function selectAdd($fields='',$table='',$options=array()) {// 分析表达式$options =  $this->_parseOptions($options);// 写入数据到数据库if(false === $result = $this->db->selectInsert($fields?$fields:$options['field'],$table?$table:$this->getTableName(),$options)){// 数据库插入操作失败$this->error = L('_OPERATION_WRONG_');return false;}else {// 插入成功return $result;}}/*** 保存数据* @access public* @param mixed $data 数据* @param array $options 表达式* @return boolean*/public function save($data='',$options=array()) {if(empty($data)) {// 没有传递数据,获取当前数据对象的值if(!empty($this->data)) {$data           =   $this->data;// 重置数据$this->data     =   array();}else{$this->error    =   L('_DATA_TYPE_INVALID_');return false;}}// 数据处理$data       =   $this->_facade($data);// 分析表达式$options    =   $this->_parseOptions($options);if(false === $this->_before_update($data,$options)) {return false;}if(!isset($options['where']) ) {// 如果存在主键数据 则自动作为更新条件if(isset($data[$this->getPk()])) {$pk                 =   $this->getPk();$where[$pk]         =   $data[$pk];$options['where']   =   $where;$pkValue            =   $data[$pk];unset($data[$pk]);}else{// 如果没有任何更新条件则不执行$this->error        =   L('_OPERATION_WRONG_');return false;}}$result     =   $this->db->update($data,$options);if(false !== $result) {if(isset($pkValue)) $data[$pk]   =  $pkValue;$this->_after_update($data,$options);}return $result;}// 更新数据前的回调方法protected function _before_update(&$data,$options) {}// 更新成功后的回调方法protected function _after_update($data,$options) {}/*** 删除数据* @access public* @param mixed $options 表达式* @return mixed*/public function delete($options=array()) {if(empty($options) && empty($this->options['where'])) {// 如果删除条件为空 则删除当前数据对象所对应的记录if(!empty($this->data) && isset($this->data[$this->getPk()]))return $this->delete($this->data[$this->getPk()]);elsereturn false;}if(is_numeric($options)  || is_string($options)) {// 根据主键删除记录$pk   =  $this->getPk();if(strpos($options,',')) {$where[$pk]     =  array('IN', $options);}else{$where[$pk]     =  $options;}$pkValue            =  $where[$pk];$options            =  array();$options['where']   =  $where;}// 分析表达式$options =  $this->_parseOptions($options);$result=    $this->db->delete($options);if(false !== $result) {$data = array();if(isset($pkValue)) $data[$pk]   =  $pkValue;$this->_after_delete($data,$options);}// 返回删除记录个数return $result;}// 删除成功后的回调方法protected function _after_delete($data,$options) {}/*** 查询数据集* @access public* @param array $options 表达式参数* @return mixed*/public function select($options=array()) {if(is_string($options) || is_numeric($options)) {// 根据主键查询$pk   =  $this->getPk();if(strpos($options,',')) {$where[$pk]     =  array('IN',$options);}else{$where[$pk]     =  $options;}$options            =  array();$options['where']   =  $where;}elseif(false === $options){ // 用于子查询 不查询只返回SQL$options            =  array();// 分析表达式$options            =  $this->_parseOptions($options);return  '( '.$this->db->buildSelectSql($options).' )';}// 分析表达式$options    =  $this->_parseOptions($options);$resultSet  = $this->db->select($options);if(false === $resultSet) {return false;}if(empty($resultSet)) { // 查询结果为空return null;}$this->_after_select($resultSet,$options);return $resultSet;}// 查询成功后的回调方法protected function _after_select(&$resultSet,$options) {}/*** 生成查询SQL 可用于子查询* @access public* @param array $options 表达式参数* @return string*/public function buildSql($options=array()) {// 分析表达式$options =  $this->_parseOptions($options);return  '( '.$this->db->buildSelectSql($options).' )';}/*** 分析表达式* @access proteced* @param array $options 表达式参数* @return array*/protected function _parseOptions($options=array()) {if(is_array($options))$options =  array_merge($this->options,$options);// 查询过后清空sql表达式组装 避免影响下次查询$this->options  =   array();if(!isset($options['table']))// 自动获取表名$options['table']   =   $this->getTableName();if(!empty($options['alias'])) {$options['table']  .=   ' '.$options['alias'];}// 记录操作的模型名称$options['model']       =   $this->name;// 字段类型验证if(isset($options['where']) && is_array($options['where']) && !empty($this->fields)) {// 对数组查询条件进行字段类型检查foreach ($options['where'] as $key=>$val){$key            =   trim($key);if(in_array($key,$this->fields,true)){if(is_scalar($val)) {$this->_parseType($options['where'],$key);}}elseif('_' != substr($key,0,1) && false === strpos($key,'.') && false === strpos($key,'|') && false === strpos($key,'&')){unset($options['where'][$key]);}}}// 表达式过滤$this->_options_filter($options);return $options;}// 表达式过滤回调方法protected function _options_filter(&$options) {}/*** 数据类型检测* @access protected* @param mixed $data 数据* @param string $key 字段名* @return void*/protected function _parseType(&$data,$key) {$fieldType = strtolower($this->fields['_type'][$key]);if(false === strpos($fieldType,'bigint') && false !== strpos($fieldType,'int')) {$data[$key]   =  intval($data[$key]);}elseif(false !== strpos($fieldType,'float') || false !== strpos($fieldType,'double')){$data[$key]   =  floatval($data[$key]);}elseif(false !== strpos($fieldType,'bool')){$data[$key]   =  (bool)$data[$key];}}/*** 查询数据* @access public* @param mixed $options 表达式参数* @return mixed*/public function find($options=array()) {if(is_numeric($options) || is_string($options)) {$where[$this->getPk()]  =   $options;$options                =   array();$options['where']       =   $where;}// 总是查找一条记录$options['limit']   =   1;// 分析表达式$options            =   $this->_parseOptions($options);$resultSet          =   $this->db->select($options);if(false === $resultSet) {return false;}if(empty($resultSet)) {// 查询结果为空return null;}$this->data         =   $resultSet[0];$this->_after_find($this->data,$options);return $this->data;}// 查询成功的回调方法protected function _after_find(&$result,$options) {}/*** 处理字段映射* @access public* @param array $data 当前数据* @param integer $type 类型 0 写入 1 读取* @return array*/public function parseFieldsMap($data,$type=1) {// 检查字段映射if(!empty($this->_map)) {foreach ($this->_map as $key=>$val){if($type==1) { // 读取if(isset($data[$val])) {$data[$key] =   $data[$val];unset($data[$val]);}}else{if(isset($data[$key])) {$data[$val] =   $data[$key];unset($data[$key]);}}}}return $data;}/*** 设置记录的某个字段值* 支持使用数据库字段和方法* @access public* @param string|array $field  字段名* @param string $value  字段值* @return boolean*/public function setField($field,$value='') {if(is_array($field)) {$data           =   $field;}else{$data[$field]   =   $value;}return $this->save($data);}/*** 字段值增长* @access public* @param string $field  字段名* @param integer $step  增长值* @return boolean*/public function setInc($field,$step=1) {return $this->setField($field,array('exp',$field.'+'.$step));}/*** 字段值减少* @access public* @param string $field  字段名* @param integer $step  减少值* @return boolean*/public function setDec($field,$step=1) {return $this->setField($field,array('exp',$field.'-'.$step));}/*** 获取一条记录的某个字段值* @access public* @param string $field  字段名* @param string $spea  字段数据间隔符号 NULL返回数组* @return mixed*/public function getField($field,$sepa=null) {$options['field']       =   $field;$options                =   $this->_parseOptions($options);$field                  =   trim($field);if(strpos($field,',')) { // 多字段$options['limit']   =   is_numeric($sepa)?$sepa:'';$resultSet          =   $this->db->select($options);if(!empty($resultSet)) {$_field         =   explode(',', $field);$field          =   array_keys($resultSet[0]);$key            =   array_shift($field);$key2           =   array_shift($field);$cols           =   array();$count          =   count($_field);foreach ($resultSet as $result){$name   =  $result[$key];if(2==$count) {$cols[$name]   =  $result[$key2];}else{$cols[$name]   =  is_string($sepa)?implode($sepa,array_slice($result,1)):$result;}}return $cols;}}else{   // 查找一条记录// 返回数据个数if(true !== $sepa) {// 当sepa指定为true的时候 返回所有数据$options['limit']   =   is_numeric($sepa)?$sepa:1;}$result = $this->db->select($options);if(!empty($result)) {if(true !== $sepa && 1==$options['limit']) return reset($result[0]);foreach ($result as $val){$array[]    =   $val[$field];}return $array;}}return null;}/*** 创建数据对象 但不保存到数据库,数据对象就是指的Model.class.php类中的$data属性* @access public* @param mixed $data 创建数据* @param string $type 状态* @return mixed*/public function create($data='',$type='') {// 如果没有传值默认取POST数据if(empty($data)) {$data   =   $_POST;//自动获取$_POST过来的表单数据}elseif(is_object($data)){$data   =   get_object_vars($data);}// 验证数据if(empty($data) || !is_array($data)) {$this->error = L('_DATA_TYPE_INVALID_');return false;}// 检查字段映射$data = $this->parseFieldsMap($data,0);//参数2:0-写入   1-读取// 状态:1-插入模型数据$type = $type?$type:(!empty($data[$this->getPk()])?self::MODEL_UPDATE:self::MODEL_INSERT);// 检测提交字段的合法性,适用于$this->field('field1,field2...')->create()情况if(isset($this->options['field'])) { // $this->field('field1,field2...')->create()$fields =   $this->options['field'];unset($this->options['field']);}elseif($type == self::MODEL_INSERT && isset($this->insert_fields)) {$fields =   $this->insert_fields;}elseif($type == self::MODEL_UPDATE && isset($this->update_fields)) {$fields =   $this->update_fields;}if(isset($fields)) {//都是filed()方法中指定的字段if(is_string($fields)) {$fields =   explode(',',$fields);}foreach ($data as $key=>$val){if(!in_array($key,$fields)) {unset($data[$key]);}}}// 数据自动验证if(!$this->autoValidation($data,$type)) return false;// 表单令牌验证if(C('TOKEN_ON') && !$this->autoCheckToken($data)) {$this->error = L('_TOKEN_ERROR_');return false;}// 验证完成生成数据对象if($this->autoCheckFields) { // 开启字段检测 则过滤非法字段数据$fields =   $this->getDbFields();//获取数据表所有字段名foreach ($data as $key=>$val){//提交过来的表单数据名必须在数据表中存在相同的字段名if(!in_array($key,$fields)) {unset($data[$key]);}elseif(MAGIC_QUOTES_GPC && is_string($val)){$data[$key] =   stripslashes($val);//对提交的标题数据进行过滤处理}}}// 创建完成对数据进行自动处理$this->autoOperation($data,$type);//返回$data// 赋值当前数据对象$this->data =   $data;//将提交过来的表单数据赋值给当前的data属性// 返回创建的数据以供其他调用return $data;//返回提交过来的表单数据}// 自动表单令牌验证// TODO  ajax无刷新多次提交暂不能满足public function autoCheckToken($data) {if(C('TOKEN_ON')){$name   = C('TOKEN_NAME');if(!isset($data[$name]) || !isset($_SESSION[$name])) { // 令牌数据无效return false;}// 令牌验证list($key,$value)  =  explode('_',$data[$name]);if($_SESSION[$name][$key] == $value) { // 防止重复提交unset($_SESSION[$name][$key]); // 验证完成销毁sessionreturn true;}// 开启TOKEN重置if(C('TOKEN_RESET')) unset($_SESSION[$name][$key]);return false;}return true;}/*** 使用正则验证数据* @access public* @param string $value  要验证的数据* @param string $rule 验证规则* @return boolean*/public function regex($value,$rule) {$validate = array('require'   =>  '/.+/','email'     =>  '/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/','url'       =>  '/^http(s?):\/\/(?:[A-za-z0-9-]+\.)+[A-za-z]{2,4}(?:[\/\?#][\/=\?%\-&~`@[\]\':+!#\w]*)?$/','currency'  =>  '/^\d+(\.\d+)?$/','number'    =>  '/^\d+$/','zip'       =>  '/^\d{6}$/','integer'   =>  '/^[-\+]?\d+$/','double'    =>  '/^[-\+]?\d+(\.\d+)?$/','english'   =>  '/^[A-Za-z]+$/',);// 检查是否有内置的正则表达式if(isset($validate[strtolower($rule)]))$rule       =   $validate[strtolower($rule)];return preg_match($rule,$value)===1;}/*** 自动表单处理* @access public* @param array $data 创建数据* @param string $type 创建类型* @return mixed*/private function autoOperation(&$data,$type) {if(!empty($this->options['auto'])) {//默认为空$_auto   =   $this->options['auto'];unset($this->options['auto']);}elseif(!empty($this->_auto)){//默认为空$_auto   =   $this->_auto;}// 自动填充if(isset($_auto)) {foreach ($_auto as $auto){// 填充因子定义格式// array('field','填充内容','填充条件','附加规则',[额外参数])if(empty($auto[2])) $auto[2] = self::MODEL_INSERT; // 默认为新增的时候自动填充if( $type == $auto[2] || $auto[2] == self::MODEL_BOTH) {switch(trim($auto[3])) {case 'function':    //  使用函数进行填充 字段的值作为参数case 'callback': // 使用回调方法$args = isset($auto[4])?(array)$auto[4]:array();if(isset($data[$auto[0]])) {array_unshift($args,$data[$auto[0]]);}if('function'==$auto[3]) {$data[$auto[0]]  = call_user_func_array($auto[1], $args);}else{$data[$auto[0]]  =  call_user_func_array(array(&$this,$auto[1]), $args);}break;case 'field':    // 用其它字段的值进行填充$data[$auto[0]] = $data[$auto[1]];break;case 'string':default: // 默认作为字符串填充$data[$auto[0]] = $auto[1];}if(false === $data[$auto[0]] )   unset($data[$auto[0]]);}}}return $data;//返回$data}/*** 自动表单验证* @access protected* @param array $data 创建数据* @param string $type 创建类型* @return boolean*/protected function autoValidation($data,$type) {if(!empty($this->options['validate'])) {$_validate   =   $this->options['validate'];unset($this->options['validate']);}elseif(!empty($this->_validate)){$_validate   =   $this->_validate;}// 属性验证if(isset($_validate)) { // 如果设置了数据自动验证则进行数据验证if($this->patchValidate) { // 重置验证错误信息$this->error = array();}foreach($_validate as $key=>$val) {// 验证因子定义格式// array(field,rule,message,condition,type,when,params)// 判断是否需要执行验证if(empty($val[5]) || $val[5]== self::MODEL_BOTH || $val[5]== $type ) {if(0==strpos($val[2],'{%') && strpos($val[2],'}'))// 支持提示信息的多语言 使用 {%语言定义} 方式$val[2]  =  L(substr($val[2],2,-1));$val[3]  =  isset($val[3])?$val[3]:self::EXISTS_VALIDATE;$val[4]  =  isset($val[4])?$val[4]:'regex';// 判断验证条件switch($val[3]) {case self::MUST_VALIDATE:   // 必须验证 不管表单是否有设置该字段if(false === $this->_validationField($data,$val)) return false;break;case self::VALUE_VALIDATE:    // 值不为空的时候才验证if('' != trim($data[$val[0]]))if(false === $this->_validationField($data,$val)) return false;break;default:    // 默认表单存在该字段就验证if(isset($data[$val[0]]))if(false === $this->_validationField($data,$val)) return false;}}}// 批量验证的时候最后返回错误if(!empty($this->error)) return false;}return true;}/*** 验证表单字段 支持批量验证* 如果批量验证返回错误的数组信息* @access protected* @param array $data 创建数据* @param array $val 验证因子* @return boolean*/protected function _validationField($data,$val) {if(false === $this->_validationFieldItem($data,$val)){if($this->patchValidate) {$this->error[$val[0]]   =   $val[2];}else{$this->error            =   $val[2];return false;}}return ;}/*** 根据验证因子验证字段* @access protected* @param array $data 创建数据* @param array $val 验证因子* @return boolean*/protected function _validationFieldItem($data,$val) {switch(strtolower(trim($val[4]))) {case 'function':// 使用函数进行验证case 'callback':// 调用方法进行验证$args = isset($val[6])?(array)$val[6]:array();if(is_string($val[0]) && strpos($val[0], ','))$val[0] = explode(',', $val[0]);if(is_array($val[0])){// 支持多个字段验证foreach($val[0] as $field)$_data[$field] = $data[$field];array_unshift($args, $_data);}else{array_unshift($args, $data[$val[0]]);}if('function'==$val[4]) {return call_user_func_array($val[1], $args);}else{return call_user_func_array(array(&$this, $val[1]), $args);}case 'confirm': // 验证两个字段是否相同return $data[$val[0]] == $data[$val[1]];case 'unique': // 验证某个值是否唯一if(is_string($val[0]) && strpos($val[0],','))$val[0]  =  explode(',',$val[0]);$map = array();if(is_array($val[0])) {// 支持多个字段验证foreach ($val[0] as $field)$map[$field]   =  $data[$field];}else{$map[$val[0]] = $data[$val[0]];}if(!empty($data[$this->getPk()])) { // 完善编辑的时候验证唯一$map[$this->getPk()] = array('neq',$data[$this->getPk()]);}if($this->where($map)->find())   return false;return true;default:  // 检查附加规则return $this->check($data[$val[0]],$val[1],$val[4]);}}/*** 验证数据 支持 in between equal length regex expire ip_allow ip_deny* @access public* @param string $value 验证数据* @param mixed $rule 验证表达式* @param string $type 验证方式 默认为正则验证* @return boolean*/public function check($value,$rule,$type='regex'){switch(strtolower(trim($type))) {case 'in': // 验证是否在某个指定范围之内 逗号分隔字符串或者数组$range   = is_array($rule)?$rule:explode(',',$rule);return in_array($value ,$range);case 'between': // 验证是否在某个范围if (is_array($rule)){$min    =    $rule[0];$max    =    $rule[1];}else{list($min,$max)   =  explode(',',$rule);}return $value>=$min && $value<=$max;case 'equal': // 验证是否等于某个值return $value == $rule;case 'length': // 验证长度$length  =  mb_strlen($value,'utf-8'); // 当前数据长度if(strpos($rule,',')) { // 长度区间list($min,$max)   =  explode(',',$rule);return $length >= $min && $length <= $max;}else{// 指定长度return $length == $rule;}case 'expire':list($start,$end)   =  explode(',',$rule);if(!is_numeric($start)) $start   =  strtotime($start);if(!is_numeric($end)) $end   =  strtotime($end);return $_SERVER['REQUEST_TIME'] >= $start && $_SERVER['REQUEST_TIME'] <= $end;case 'ip_allow': // IP 操作许可验证return in_array(get_client_ip(),explode(',',$rule));case 'ip_deny': // IP 操作禁止验证return !in_array(get_client_ip(),explode(',',$rule));case 'regex':default:    // 默认使用正则验证 可以使用验证类中定义的验证名称// 检查附加规则return $this->regex($value,$rule);}}/*** SQL查询* @access public* @param string $sql  SQL指令* @param mixed $parse  是否需要解析SQL* @return mixed*/public function query($sql,$parse=false) {if(!is_bool($parse) && !is_array($parse)) {$parse = func_get_args();array_shift($parse);}$sql  =   $this->parseSql($sql,$parse);return $this->db->query($sql);}/*** 执行SQL语句* @access public* @param string $sql  SQL指令* @param mixed $parse  是否需要解析SQL* @return false | integer*/public function execute($sql,$parse=false) {if(!is_bool($parse) && !is_array($parse)) {$parse = func_get_args();array_shift($parse);}$sql  =   $this->parseSql($sql,$parse);return $this->db->execute($sql);}/*** 解析SQL语句* @access public* @param string $sql  SQL指令* @param boolean $parse  是否需要解析SQL* @return string*/protected function parseSql($sql,$parse) {// 分析表达式if(true === $parse) {$options =  $this->_parseOptions();$sql  =   $this->db->parseSql($sql,$options);}elseif(is_array($parse)){ // SQL预处理$sql  = vsprintf($sql,$parse);}else{if(strpos($sql,'__TABLE__'))$sql    =   str_replace('__TABLE__',$this->getTableName(),$sql);}$this->db->setModel($this->name);return $sql;}/*** 切换当前的数据库连接* @access public* @param integer $linkNum  连接序号* @param mixed $config  数据库连接信息* @param array $params  模型参数* @return Model*/public function db($linkNum='',$config='',$params=array()){if(''===$linkNum && $this->db) {//如果当前数据库操作对象存在,则直接返回当前数据库操作对象return $this->db;//当前数据库操作对象}static $_linkNum    =   array();//连接序号static $_db = array();//默认执行ifif(!isset($_db[$linkNum]) || (isset($_db[$linkNum]) && $config && $_linkNum[$linkNum]!=$config) ) {// 创建一个新的实例,默认不执行if(!empty($config) && is_string($config) && false === strpos($config,'/')) { // 支持读取配置参数//默认不执行$config  =  C($config);}//默认执行,返回一个数据库驱动类Db,class.php实例化对象/*** Db::getInstance($config);详解* 第一步:查找Db.class.php类库的getInstance()方法* 第二步:getInstance()方法调用common.php文件的get_instance_of(__CLASS__,'factory',$args);* 第三步:get_instance_of(__CLASS__,'factory',$args)再次调用Db.class.php类的factory()方法* 第四步:Db.class.php中的factory()方法,会加载数据库驱动类文件DbMysql.class.php,并返回一个数据库驱动类实例化对象*/$_db[$linkNum]            =    Db::getInstance($config);//返回一个数据库驱动类DbMysql.class.php实例化对象}elseif(NULL === $config){$_db[$linkNum]->close(); // 关闭数据库连接unset($_db[$linkNum]);return ;}if(!empty($params)) {//默认不执行if(is_string($params))    parse_str($params,$params);foreach ($params as $name=>$value){$this->setProperty($name,$value);}}// 记录连接信息$_linkNum[$linkNum] =   $config;// 切换数据库连接$this->db   =    $_db[$linkNum];//数据库驱动类Db,class.php实例化对象$this->_after_db();// 字段检测                        是否自动检测数据表字段信息if(!empty($this->name) && $this->autoCheckFields)   $this->_checkTableInfo();return $this;//返回当前对象,其中就包括数据库连接对象}// 数据库切换后回调方法protected function _after_db() {}/*** 得到当前的数据对象名称* @access public* @return string*/public function getModelName() {if(empty($this->name))$this->name =   substr(get_class($this),0,-5);return $this->name;}/*** 得到完整的数据表名* @access public* @return string*/public function getTableName() {if(empty($this->trueTableName)) {$tableName  = !empty($this->tablePrefix) ? $this->tablePrefix : '';//think_if(!empty($this->tableName)) {//默认不执行$tableName .= $this->tableName;}else{//parse_name()函数将模型名转化为小写,如,User转换为user//think_user$tableName .= parse_name($this->name);}//真实表名$this->trueTableName    =   strtolower($tableName);}//think_userreturn (!empty($this->dbName)?$this->dbName.'.':'').$this->trueTableName;}/*** 启动事务* @access public* @return void*/public function startTrans() {$this->commit();$this->db->startTrans();return ;}/*** 提交事务* @access public* @return boolean*/public function commit() {return $this->db->commit();}/*** 事务回滚* @access public* @return boolean*/public function rollback() {return $this->db->rollback();}/*** 返回模型的错误信息* @access public* @return string*/public function getError(){return $this->error;}/*** 返回数据库的错误信息* @access public* @return string*/public function getDbError() {return $this->db->getError();}/*** 返回最后插入的ID* @access public* @return string*/public function getLastInsID() {return $this->db->getLastInsID();}/*** 返回最后执行的sql语句* @access public* @return string*/public function getLastSql() {return $this->db->getLastSql($this->name);}// 鉴于getLastSql比较常用 增加_sql 别名public function _sql(){return $this->getLastSql();}/*** 获取主键名称* @access public* @return string*/public function getPk() {return isset($this->fields['_pk'])?$this->fields['_pk']:$this->pk;}/*** 获取数据表字段信息* @access public* @return array*/public function getDbFields(){if(isset($this->options['table'])) {// 动态指定表名$fields     =   $this->db->getFields($this->options['table']);return  $fields?array_keys($fields):false;}if($this->fields) {$fields     =  $this->fields;unset($fields['_autoinc'],$fields['_pk'],$fields['_type'],$fields['_version']);return $fields;}return false;}/*** 设置数据对象值* @access public* @param mixed $data 数据* @return Model*/public function data($data=''){if('' === $data && !empty($this->data)) {return $this->data;}if(is_object($data)){$data   =   get_object_vars($data);}elseif(is_string($data)){parse_str($data,$data);}elseif(!is_array($data)){throw_exception(L('_DATA_TYPE_INVALID_'));}$this->data = $data;return $this;}/*** 查询SQL组装 join* @access public* @param mixed $join* @return Model*/public function join($join) {if(is_array($join)) {$this->options['join']      =   $join;}elseif(!empty($join)) {$this->options['join'][]    =   $join;}return $this;}/*** 查询SQL组装 union* @access public* @param mixed $union* @param boolean $all* @return Model*/public function union($union,$all=false) {if(empty($union)) return $this;if($all) {$this->options['union']['_all']  =   true;}if(is_object($union)) {$union   =  get_object_vars($union);}// 转换union表达式if(is_string($union) ) {$options =  $union;}elseif(is_array($union)){if(isset($union[0])) {$this->options['union']  =  array_merge($this->options['union'],$union);return $this;}else{$options =  $union;}}else{throw_exception(L('_DATA_TYPE_INVALID_'));}$this->options['union'][]  =   $options;return $this;}/*** 查询缓存* @access public* @param mixed $key* @param integer $expire* @param string $type* @return Model*/public function cache($key=true,$expire=null,$type=''){$this->options['cache']  =  array('key'=>$key,'expire'=>$expire,'type'=>$type);return $this;}/*** 指定查询字段 支持字段排除* @access public* @param mixed $field* @param boolean $except 是否排除* @return Model*/public function field($field,$except=false){if(true === $field) {// 获取全部字段$fields     =  $this->getDbFields();$field      =  $fields?$fields:'*';}elseif($except) {// 字段排除if(is_string($field)) {$field  =  explode(',',$field);}$fields     =  $this->getDbFields();$field      =  $fields?array_diff($fields,$field):$field;}$this->options['field']   =   $field;return $this;}/*** 调用命名范围* @access public* @param mixed $scope 命名范围名称 支持多个 和直接定义* @param array $args 参数* @return Model*/public function scope($scope='',$args=NULL){if('' === $scope) {if(isset($this->_scope['default'])) {// 默认的命名范围$options    =   $this->_scope['default'];}else{return $this;}}elseif(is_string($scope)){ // 支持多个命名范围调用 用逗号分割$scopes         =   explode(',',$scope);$options        =   array();foreach ($scopes as $name){if(!isset($this->_scope[$name])) continue;$options    =   array_merge($options,$this->_scope[$name]);}if(!empty($args) && is_array($args)) {$options    =   array_merge($options,$args);}}elseif(is_array($scope)){ // 直接传入命名范围定义$options        =   $scope;}if(is_array($options) && !empty($options)){$this->options  =   array_merge($this->options,array_change_key_case($options));}return $this;}/*** 指定查询条件 支持安全过滤* @access public* @param mixed $where 条件表达式* @param mixed $parse 预处理参数* @return Model*/public function where($where,$parse=null){if(!is_null($parse) && is_string($where)) {if(!is_array($parse)) {$parse = func_get_args();array_shift($parse);}$parse = array_map(array($this->db,'escapeString'),$parse);$where =   vsprintf($where,$parse);}elseif(is_object($where)){$where  =   get_object_vars($where);}$this->options['where'] =   $where;return $this;}/*** 指定查询数量* @access public* @param mixed $offset 起始位置* @param mixed $length 查询数量* @return Model*/public function limit($offset,$length=null){$this->options['limit'] =   is_null($length)?$offset:$offset.','.$length;return $this;}/*** 指定分页* @access public* @param mixed $page 页数* @param mixed $listRows 每页数量* @return Model*/public function page($page,$listRows=null){$this->options['page'] =   is_null($listRows)?$page:$page.','.$listRows;return $this;}/*** 设置模型的属性值* @access public* @param string $name 名称* @param mixed $value 值* @return Model*/public function setProperty($name,$value) {if(property_exists($this,$name))$this->$name = $value;return $this;}}?>

【thinkphp3.x】ThinkPHP/Lib/Core/Model.class.php文件分析相关推荐

  1. libcoredb.class.php,ThinkPHP/Lib/Core/Db.class.php中pdo处理逻辑似乎不完善,导致config中pdo配置失效...

    在ThinkPHP/Lib/Core/Db.class.php中逻辑判断似乎不完善 源码 // 如果配置为空,读取配置文件设置 if( C('DB_DSN') && 'pdo' != ...

  2. 什么是php model类,thinkphp的自定义model类有什么作用?

    请问,thinkphp的自定义model类有什么作用?如: <?php //自定义Modle类 namespace Home\Model; use Think\Model; class StuM ...

  3. EF Core Model更新迁移

    EF Core 迁移 感觉就是以前EF Code First的自动同步数据库功能 内容:在你新增.更新TableModel后,如何自动化的更新DB中的真实Table.以及对这些更改进行一个版本控制. ...

  4. thinkphp模型层Model、Logic、Service讲解

    thinkphp模型层Model.Logic.Service讲解 时间:2014-08-24 15:54:56   编辑:一切随缘   文章来源:php教程网 已阅读:771 次 js特效源码,就从这 ...

  5. 去除Reloaded modules: lib, lib.utils, lib.metrics, lib.data_preparation, model, model.model_config

    Spyder(python 3.6) IDE运行如下程序: import argparse def main():     parser = argparse.ArgumentParser(descr ...

  6. thinkphp通过模型查询mysql_6月16 ThinkPHP连接数据库及Model数据模型层--------查询及数据添加...

    连接数据库配置及Model数据模型层 convertion.php config.php 1.在config.php做数据库连接配置 2.修改配置 /*数据库设置*/ 'DB_TYPE' => ...

  7. php中的setinc,thinkphp3.2.0中setInc方法的源码分析

    下面为大家分享一篇thinkphp3.2.0 setInc方法 源码全面解析,具有很好的参考价值,希望对大家有所帮助. 我们先来看一下setInc的官方示例: 需要一个字段和一个自增的值(默认为1) ...

  8. 使用SonarCloud对.NET Core项目进行静态代码分析

    本文将介绍如何使用SonarCloud进行.NET Core项目的静态代码分析.SonarCloud是SonarQube提供的基于云的版本,特别针对于开源项目是免费的. 首先,在sonarcloud. ...

  9. ThinkPHP V5.0.5漏洞_ThinkPHP漏洞分析与利用

    一.组件介绍 1.1 基本信息 ThinkPHP是一个快速.兼容而且简单的轻量级国产PHP开发框架,遵循Apache 2开源协议发布,使用面向对象的开发结构和MVC模式,融合了Struts的思想和Ta ...

最新文章

  1. hibernate mysql 自动建表_配置hibernate根据实体类自动建表功能
  2. 你知道面试必问的AOP吗(1),2021吊打面试官系列
  3. django模板导入js,css等外部文件
  4. 前端学习(2850):简单秒杀系统学习之绝对定位
  5. 使用python实现对于chineseocr的API调用
  6. 程序员面试金典 - 面试题 03.03. 堆盘子 (vector(stack))
  7. 微信红包随机生成算法(PHP版)
  8. 读书笔记_打开量化投资的黑箱06
  9. 你知道 ES6~ES12等叫法是怎么来的吗?
  10. Spring RestController
  11. Android 实现圆角头像(使用第三方开源库)
  12. 随手练——O(n)解决无序数组排序后的相邻最大差值
  13. 如何正确复制CSDN文章到自己的博客
  14. 开源软件、自由软件及免费软件的区别
  15. 那些APP活动中的刷量与作弊
  16. 在图书馆使用计算机管理属于,图书馆计算机使用管理规定
  17. 自己写的一个AI输入滤波函数
  18. 内存占用过高,缓存不释放导致死机处理方案
  19. 汽车系统安装linux,linux系统不仅是电脑上的系统 更是会车载主流系统
  20. Linux服务器个人常用命令

热门文章

  1. 阿里在使用一种更灵活的软件集成发布模式
  2. linux mint 1.9 qq 安装
  3. zabbix 监控percona
  4. 【ASM】udev简介及配置、多路径(multipath)等
  5. Com原理及應用——Com對象和接口
  6. 一个具有对象计数功能的基类
  7. Silverlight使用RSA加密socket tcp通讯数据
  8. C#自定义控件七水波纹
  9. 一个不错的验证码的例子
  10. vue实现动态改变title