<?php
/* SVN FILE: $Id: Manip.php 606 2008-07-14 14:48:05Z bb_yujiro $ */
/**
 * Manipulation
 *
 * PHP versions 5
 *
 *      hitSuji : Social Network Service <http://rakuto.net/rktSNS/>
 *      Copyright (c) 2007 Yujiro Takahashi
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @filesource
 * @package         hitSuji Model
 * @copyright       Copyright (c) 2007 Yujiro Takahashi
 * @link            http://rakuto.net/
 * @author          Yujiro Takahashi <yujiro@rakuto.net>
 * @version         $Revision: 606 $
 * @modifiedby      $LastChangedBy: bb_yujiro $
 * @lastmodified    $Date: 2008-07-14 23:48:05 +0900 (月, 14 7 2008) $
 * @license         http://opensource.org/licenses/mit-license.php The MIT License
 */

/**
 * Manipulationクラス
 *
 * @category        Model::Manipulation
 * @package         hitSuji
 * @copyright       Copyright (c) 2007 Yujiro Takahashi
 * @link            http://rakuto.net/
 * @author          Yujiro Takahashi <yujiro@rakuto.net>
 * @version         $Revision: 606 $
 * @modifiedby      $LastChangedBy: bb_yujiro $
 * @lastmodified    $Date: 2008-07-14 23:48:05 +0900 (月, 14 7 2008) $
 * @license         http://opensource.org/licenses/mit-license.php The MIT License
 */
class Model_Manip extends RKT_Model
{
    /**
     * 新しい値
     * @var array
     */
    public $new_values = array();

    /**
     * 古い値
     * @var array
     */
    public $old_values = array();

    /**
     * 参照テーブル
     * @var array
     */
    protected $reference = array();

    /**
     * カレントID
     * @var integer
     */
    protected $cur_id = null;

    /**
     * 更新日時
     * @var string
     */
    protected $modified = null;

    /**
     * テーブル名
     * @var string
     */
    protected $tbl_name = null;

    /**
     * シーケンス名
     * @var string
     */
    protected $seq_name = null;

    /**
     * プライマリキー
     * @var string
     */
    protected $primary_key = null;

    /**
     * 項目情報
     * @var array
     */
    protected $column = array();

    /**
     * カンマ区切り全項目
     * @var string
     */
    protected $cs_columns = null;

    /**
     * baindフォーマットの全項目
     * @var string
     */
    protected $bind_columns = null;

    /**
     * Update用baindフォーマットの全項目
     * @var string
     */
    protected $set_columns = null;

    /**
     * 更新・削除用where句指定
     * @var array
     */
    private $_condition = null;

    /**
     * コンストラクタ
     *
     * @access public
     * @param string $tbl_name テーブル名
     */
    public function __construct()
    {
        parent::__construct();

        $driver = $this->objdb->getAttribute(PDO::ATTR_DRIVER_NAME);
        $seqs = array(
            'mysql'=>  $this->primary_key,
            'pgsql'=>  'seq_'.$this->tbl_name,
            'sqlite'=> $this->primary_key,
        );
        $this->seq_name = $seqs[$driver];
    }

    /**
     * Factory メソッド
     *
     * @access public
     * @param string $driver
     * @return void
     */
    public static function factory($tbl_name='', $driver='driver')
    {
        $drivers = array(
            'mysql'=> 'MySQL',
            'pgsql'=> 'PostgreSQL',
            'sqlite'=>'SQLite',
        );
        if (empty($drivers[$driver])){
            $driver = RKT_DB::exec()->getAttribute(PDO::ATTR_DRIVER_NAME);
            $driver = $drivers[$driver];
        }
        if (include LIB_DIR.'rktModel/Manip/'.$driver.'.php') {
            $classname = 'Model_Manip_'.$driver;
            return new $classname($tbl_name);
        } else {
            throw new Exception ('Driver not found');
        }
    }

    /**
     * テーブル情報の設定
     *
     * @access public
     * @param string $init_sql 初期値を取得するSELECT文
     */
    public function setTable($tbl_name = '')
    {
        $this->action_flag = RKT_Model::QUERY_NONE;
        $this->tbl_name    = empty($tbl_name)? $this->tbl_name:$tbl_name;

        $objmdr = self::factory($this->tbl_name);
        $this->column      = $objmdr->parseColumns();
        $this->primary_key = $objmdr->getPrimaryKey();

        /* バインド用文字列の作成 */
        $keys = array_keys($this->column);
        $bind = array();
        $sets = array();

        foreach ($keys as $key){
            $bind[] = ':'.$key;
            $sets[] = $key.'=:'.$key;
        }

        $this->cs_columns   = implode(',',  $keys);
        $this->bind_columns = implode(',',  $bind);
        $this->set_columns  = implode(', ', $sets);
    }

    /**
     * 入力内容の取得
     *
     * @access public
     * @return void
     */
    public function catchRequest($request='_POST')
    {
        $this->objrqt->setRequestType($request);
        foreach ($this->column as $key=>$column){
            if (RKT_Model::INPUT_CHATCH == $column['input']){
                if ($this->objrqt->catchValidate($key, $column)){
                    $value = $this->objrqt->getRequest($key);

                    $this->$key             = $value;
                    $this->new_values[$key] = $value;
                }
            }
        } // foreach ($this->column as $key=>$column)
    }

    /**
     * 項目の初期化
     *
     * @access public
     */
    public function refreshColumn()
    {
        foreach ($this->column as $key=>$row){
            $this->$key = null;
        }
    }

    /**
     * カレントIDの設定
     *
     * @access public
     * @param integer $cur_id カレントID
     */
    public function setCurId($cur_id)
    {
        $this->cur_id = $cur_id;
        $this->_condition['cur_id'] = $this->primary_key.' = :cur_id';
    }

    /**
     * 挿入・更新情報の設定
     *
     * @access public
     * @parmas string $key 項目の名
     * @parmas mixed $value 挿入・更新情報
     */
    public function setValue($key, $value, $set_condition=false)
    {
        $this->$key             = $value;
        $this->new_values[$key] = $value;
        if ($set_condition){
            $this->_condition[$key]  = $key.' = :'.$key;
        }
    }

    /**
     * カレントIDの取得
     *
     * @access public
     * @return integer カレントID
     */
    public function getCurId()
    {
        return $this->cur_id;
    }

    /**
     * 入力条件の取得
     *
     * @access public
     * @param array $skip
     * @return array
     */
    public function getCondition()
    {
        $validates = array();
        foreach ($this->column as $key=>$value){
            if ($value['input'] == RKT_Model::INPUT_CHATCH){
                $validates[$key] = $value;
            }
        } // foreach ($this->column as $key=>$value)

        return $validates;
    }

    /**
     * 既存値の取得
     *
     * @access public
     * @parmas string $key フィールド名
     * @return mixed 対象の既存値
     */
    public function getValue($key)
    {
        return isSet($this->$key) ? 
                        $this->$key:null;
    }

    /**
     * 古い値の取得
     *
     * @access public
     * @parmas string $key フィールド名
     * @return mixed 対象の既存値
     */
    public function getOldValue($key)
    {
        return isSet($this->old_values[$key]) ? 
                        $this->old_values[$key]:null;
    }

    /**
     * テーブル操作実行
     *
     * @access public
     * @return boolean
     */
    public function manip()
    {
        /* 更新日の設定 */
        $this->modified = date('Y-m-d H:i:s');

        /* 挿入・更新の設定 */
        if (!is_object($this->objstmt)){
            if (empty($this->cur_id)){
                $this->setInsert();
            } else {
                $this->setUpdate();
            }
        } // if (!is_object($this->objstmt))

        $this->triggerBefore();     // トリガー

        /* バインドパラムの設定 */
        $this->_bindParams();

        /* 実行 */
        $result = $this->objstmt->execute();
        if ($result){
            if (empty($this->cur_id)){
                $this->cur_id = $this->objdb->lastInsertId($this->seq_name);
            }
            $this->triggerAfter();  // トリガー 
        } // if ($result)
        return $result;
    }

    /**
     * 初期値を取得する
     *
     * @access public
     * @return boolean
     */
    public function setInitValue()
    {
        /* SELECT文の生成 */
        if (is_array($this->_condition)){
            $this->action_flag = RKT_Model::QUERY_SELECT;
            $sql = 'SELECT * FROM '.DB_PREFIX.$this->tbl_name.' WHERE ';
            $sql .= implode(' AND ', $this->_condition);
            $this->objstmt = $this->objdb->prepare($sql);
            $this->_bindParams();
            $this->objstmt->execute(); 
            $this->old_values = $this->objstmt->fetch(PDO::FETCH_ASSOC);
            $this->objstmt->closeCursor();
            $this->objstmt = null;
        }
        if (!$this->old_values){
            foreach ($this->column as $column){
                $this->old_values[$column['column']] = null;
                $this->new_values[$column['column']] = null;
            }
            return false;
        }
        $this->setCurId($this->old_values[$this->primary_key]);
        unset($this->old_values[$this->primary_key]);

        $this->new_values = $this->old_values;
        foreach ($this->new_values as $column=> $value){
            $this->$column = is_null($this->$column)? $value:$this->$column;
        }
        $this->initEffect();

        return true;
    }

    /**
     * 挿入処理設定
     *
     * @access public
     */
    public function setInsert()
    {
        $this->action_flag = RKT_Model::QUERY_INSERT;

        $sql = 'INSERT INTO '.DB_PREFIX.$this->tbl_name.' ('.
               $this->cs_columns.') VALUES ('.$this->bind_columns.')';
        $this->objstmt = $this->objdb->prepare($sql);
    }

    /**
     * 更新処理設定
     *
     * @access public
     * @return void
     */
    public function setUpdate()
    {
        if ($this->action_flag == RKT_Model::QUERY_UPDATE){
            return ;
        }
        $this->action_flag = RKT_Model::QUERY_UPDATE;

        $sql = 'UPDATE '.DB_PREFIX.$this->tbl_name.
               ' SET '.$this->set_columns.' WHERE ';
        $sql .= implode(' AND ', $this->_condition);

        $this->objstmt = $this->objdb->prepare($sql);
    }

    /**
     * 行の削除設定
     *
     * @access public
     * @return void
     */
    public function setDelete()
    {
        if ($this->action_flag == RKT_Model::QUERY_DELETE){
            return ;
        }
        $this->action_flag = RKT_Model::QUERY_DELETE;

        $sql = 'DELETE FROM '.DB_PREFIX.$this->tbl_name.' WHERE ';
        $sql .= implode(' AND ', $this->_condition);
        $this->objstmt = $this->objdb->prepare($sql);
    }

    /**
     * 初期値データへの加工処理
     *
     * @access protected
     * @retrun void
     */
    protected function initEffect()
    {
    }

    /**
     * 変数の初期化
     *
     * @abstract
     * @access protected
     * @return void
     */
    protected function initValue()
    {
    }

    /**
     * 処理後トリガー
     *
     * @access protected
     */
    protected function triggerAfter()
    {
    }

    /**
     * 処理前トリガー
     *
     * @access protected
     */
    protected function triggerBefore()
    {
    }

    /**
     * 変数の受け渡し
     *
     * @access private
     * @return voie
     */
    private function _bindParams()
    {
        $params = array(
            RKT_Model::QUERY_INSERT=> '_bindParamInsert',
            RKT_Model::QUERY_UPDATE=> '_bindParamUpdate',
            RKT_Model::QUERY_SELECT=> '_bindParamOther',
            RKT_Model::QUERY_DELETE=> '_bindParamOther',
        );

        call_user_func(array(&$this, $params[$this->action_flag]));
    }

    /**
     * 挿入用変数の受け渡し
     *
     * @access private
     * @return voie
     */
    private function _bindParamInsert()
    {
        foreach ($this->column as $key=>$info){
            $this->_setDefaultValue($key);
            if ($info['type'] == 'date'){
                $this->$key = joinDate($this->$key);
            }
            $this->objstmt->bindParam(':'.$key, $this->$key, $info['pdo_type']);
        }
    }

    /**
     * 更新用変数の受け渡し
     *
     * @access private
     * @return voie
     */
    private function _bindParamUpdate()
    {
        /* プライマリキー */
        if (!empty($this->cur_id)){
            $this->objstmt->bindParam(':cur_id', $this->cur_id, PDO::PARAM_INT);
        }
        foreach ($this->column as $key=>$info){
            $this->_setDefaultValue($key);
            if ($info['type'] == 'date'){
                $this->$key = joinDate($this->$key);
            }
            $this->objstmt->bindParam(':'.$key, $this->$key, $info['pdo_type']);
        }
    }

    /**
     * その他変数の受け渡し
     *
     * @access private
     * @return voie
     */
    private function _bindParamOther()
    {
        $column = $this->column;
        $column['cur_id']['type']     = 'number';
        $column['cur_id']['pdo_type'] = PDO::PARAM_INT;

        foreach ($this->_condition as $key=>$tmp){
            if ($column[$key]['type'] == 'date'){
                $this->$key = joinDate($this->$key);
            }
            $this->objstmt->bindParam(':'.$key, $this->$key, $column[$key]['pdo_type']);
        }
    }

    /**
     * デフォルト値の設定
     *
     * @access private
     * @param string $key
     */
    private function _setDefaultValue($key)
    {
        $default = $this->column[$key]['default'];
        if ($key == 'regist_date'){
            $default = date('Y-m-d H:i:s');
        }
        $this->$key = ($this->$key !== '' && $this->$key !== null)? $this->$key:$default;

        if ($this->$key === NULL){
            $this->column[$key]['pdo_type'] = PDO::PARAM_NULL;
        }
    }
} // class RKT_Manip
?>
