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.
403 lines
14 KiB
PHP
403 lines
14 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;
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
} |