// +---------------------------------------------------------------------- namespace Think\Model; use Think\Model; /** * ThinkPHP 聚合模型扩展 */ class MergeModel extends Model { protected $modelList = array(); // 包含的模型列表 第一个必须是主表模型 protected $masterModel = ''; // 主模型 protected $joinType = 'INNER'; // 聚合模型的查询JOIN类型 protected $fk = ''; // 外键名 默认为主表名_id protected $mapFields = array(); // 需要处理的模型映射字段,避免混淆 array( id => 'user.id' ) /** * 架构函数 * 取得DB类的实例对象 字段检查 * @access public * @param string $name 模型名称 * @param string $tablePrefix 表前缀 * @param mixed $connection 数据库连接信息 */ public function __construct($name='',$tablePrefix='',$connection=''){ parent::__construct($name,$tablePrefix,$connection); // 聚合模型的字段信息 if(empty($this->fields) && !empty($this->modelList)){ $fields = array(); foreach($this->modelList as $model){ // 获取模型的字段信息 $result = $this->db->getFields(M($model)->getTableName()); $_fields = array_keys($result); // $this->mapFields = array_intersect($fields,$_fields); $fields = array_merge($fields,$_fields); } $this->fields = $fields; } // 设置第一个模型为主表模型 if(empty($this->masterModel) && !empty($this->modelList)){ $this->masterModel = $this->modelList[0]; } // 主表的主键名 $this->pk = M($this->masterModel)->getPk(); // 设置默认外键名 仅支持单一外键 if(empty($this->fk)){ $this->fk = strtolower($this->masterModel).'_id'; } } /** * 得到完整的数据表名 * @access public * @return string */ public function getTableName() { if(empty($this->trueTableName)) { $tableName = array(); $models = $this->modelList; foreach($models as $model){ $tableName[] = M($model)->getTableName().' '.$model; } $this->trueTableName = implode(',',$tableName); } return $this->trueTableName; } /** * 自动检测数据表信息 * @access protected * @return void */ protected function _checkTableInfo() {} /** * 新增聚合数据 * @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; } } // 启动事务 $this->startTrans(); // 写入主表数据 $result = M($this->masterModel)->strict(false)->add($data); if($result){ // 写入外键数据 $data[$this->fk] = $result; $models = $this->modelList; array_shift($models); // 写入附表数据 foreach($models as $model){ $res = M($model)->strict(false)->add($data); if(!$res){ $this->rollback(); return false; } } // 提交事务 $this->commit(); }else{ $this->rollback(); return false; } return $result; } /** * 对保存到数据库的数据进行处理 * @access protected * @param mixed $data 要操作的数据 * @return boolean */ protected function _facade($data) { // 检查数据字段合法性 if(!empty($this->fields)) { if(!empty($this->options['field'])) { $fields = $this->options['field']; unset($this->options['field']); if(is_string($fields)) { $fields = explode(',',$fields); } }else{ $fields = $this->fields; } foreach ($data as $key=>$val){ if(!in_array($key,$fields,true)){ unset($data[$key]); }elseif(array_key_exists($key,$this->mapFields)){ // 需要处理映射字段 $data[$this->mapFields[$key]] = $val; unset($data[$key]); } } } // 安全过滤 if(!empty($this->options['filter'])) { $data = array_map($this->options['filter'],$data); unset($this->options['filter']); } $this->_before_write($data); return $data; } /** * 保存聚合模型数据 * @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; } } if(empty($data)){ // 没有数据则不执行 $this->error = L('_DATA_TYPE_INVALID_'); return false; } // 如果存在主键数据 则自动作为更新条件 $pk = $this->pk; if(isset($data[$pk])) { $where[$pk] = $data[$pk]; $options['where'] = $where; unset($data[$pk]); } $options['join'] = ''; $options = $this->_parseOptions($options); // 更新操作不使用JOIN $options['table'] = $this->getTableName(); if(is_array($options['where']) && isset($options['where'][$pk])){ $pkValue = $options['where'][$pk]; } if(false === $this->_before_update($data,$options)) { return false; } $result = $this->db->update($data,$options); if(false !== $result) { if(isset($pkValue)) $data[$pk] = $pkValue; $this->_after_update($data,$options); } return $result; } /** * 删除聚合模型数据 * @access public * @param mixed $options 表达式 * @return mixed */ public function delete($options=array()){ $pk = $this->pk; if(empty($options) && empty($this->options['where'])) { // 如果删除条件为空 则删除当前数据对象所对应的记录 if(!empty($this->data) && isset($this->data[$pk])) return $this->delete($this->data[$pk]); else return false; } if(is_numeric($options) || is_string($options)) { // 根据主键删除记录 if(strpos($options,',')) { $where[$pk] = array('IN', $options); }else{ $where[$pk] = $options; } $options = array(); $options['where'] = $where; } // 分析表达式 $options['join'] = ''; $options = $this->_parseOptions($options); if(empty($options['where'])){ // 如果条件为空 不进行删除操作 除非设置 1=1 return false; } if(is_array($options['where']) && isset($options['where'][$pk])){ $pkValue = $options['where'][$pk]; } $options['table'] = implode(',',$this->modelList); $options['using'] = $this->getTableName(); if(false === $this->_before_delete($options)) { return false; } $result = $this->db->delete($options); if(false !== $result) { $data = array(); if(isset($pkValue)) $data[$pk] = $pkValue; $this->_after_delete($data,$options); } // 返回删除记录个数 return $result; } /** * 表达式过滤方法 * @access protected * @param string $options 表达式 * @return void */ protected function _options_filter(&$options) { if(!isset($options['join'])){ $models = $this->modelList; array_shift($models); foreach($models as $model){ $options['join'][] = $this->joinType.' JOIN '.M($model)->getTableName().' '.$model.' ON '.$this->masterModel.'.'.$this->pk.' = '.$model.'.'.$this->fk; } } $options['table'] = M($this->masterModel)->getTableName().' '.$this->masterModel; $options['field'] = $this->checkFields(isset($options['field'])?$options['field']:''); if(isset($options['group'])) $options['group'] = $this->checkGroup($options['group']); if(isset($options['where'])) $options['where'] = $this->checkCondition($options['where']); if(isset($options['order'])) $options['order'] = $this->checkOrder($options['order']); } /** * 检查条件中的聚合字段 * @access protected * @param mixed $data 条件表达式 * @return array */ protected function checkCondition($where) { if(is_array($where)) { $view = array(); foreach($where as $name=>$value){ if(array_key_exists($name,$this->mapFields)){ // 需要处理映射字段 $view[$this->mapFields[$name]] = $value; unset($where[$name]); } } $where = array_merge($where,$view); } return $where; } /** * 检查Order表达式中的聚合字段 * @access protected * @param string $order 字段 * @return string */ protected function checkOrder($order='') { if(is_string($order) && !empty($order)) { $orders = explode(',',$order); $_order = array(); foreach ($orders as $order){ $array = explode(' ',trim($order)); $field = $array[0]; $sort = isset($array[1])?$array[1]:'ASC'; if(array_key_exists($field,$this->mapFields)){ // 需要处理映射字段 $field = $this->mapFields[$field]; } $_order[] = $field.' '.$sort; } $order = implode(',',$_order); } return $order; } /** * 检查Group表达式中的聚合字段 * @access protected * @param string $group 字段 * @return string */ protected function checkGroup($group='') { if(!empty($group)) { $groups = explode(',',$group); $_group = array(); foreach ($groups as $field){ // 解析成聚合字段 if(array_key_exists($field,$this->mapFields)){ // 需要处理映射字段 $field = $this->mapFields[$field]; } $_group[] = $field; } $group = implode(',',$_group); } return $group; } /** * 检查fields表达式中的聚合字段 * @access protected * @param string $fields 字段 * @return string */ protected function checkFields($fields='') { if(empty($fields) || '*'==$fields ) { // 获取全部聚合字段 $fields = $this->fields; } if(!is_array($fields)) $fields = explode(',',$fields); // 解析成聚合字段 $array = array(); foreach ($fields as $field){ if(array_key_exists($field,$this->mapFields)){ // 需要处理映射字段 $array[] = $this->mapFields[$field].' AS '.$field; }else{ $array[] = $field; } } $fields = implode(',',$array); return $fields; } /** * 获取数据表字段信息 * @access public * @return array */ public function getDbFields(){ return $this->fields; } }