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


    }





}