<?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;
    }

}