<?php
/**
 * -----------------------------------------------------------------------------
 *
 * SyL - Web Application Framework for PHP
 *
 * PHP version 4 (>= 4.3.x) or 5
 *
 * Copyright (C) 2006 k.watanabe
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * -----------------------------------------------------------------------------
 * @package   SyL
 * @author    k.watanabe <k.watanabe@syl.jp>
 * @copyright 2006 k.watanabe
 * @license   http://www.opensource.org/licenses/lgpl-license.php
 * @version   CVS: $Id: SyL_DB.php,v 1.7 2006/12/23 11:13:09 seasonstream Exp $
 * @link      http://www.syl.jp/
 * -----------------------------------------------------------------------------
 */

/**
 * デバックフラグ
 * trueの場合実行SQLをログに書き込み
 */
define( 'DB_DEBUG', false );

/**
 *  DB操作クラス
 *
 * @package   SyL
 * @author    k.watanabe <k.watanabe@syl.jp>
 * @copyright 2006 k.watanabe
 * @license   http://www.opensource.org/licenses/lgpl-license.php
 * @version   CVS: $Id: SyL_DB.php,v 1.7 2006/12/23 11:13:09 seasonstream Exp $
 * @link      http://www.syl.jp/
 */
class SyL_DB
{
    /**
     * インスタンスDB名
     * 
     * @access protected
     * @var string
     */
    var $dbname = '';
    /**
     * コネクションリソース
     * 
     * @access protected
     * @var resource
     */
    var $connection = null;
    /**
     * トランザクションフラグ
     * 
     * @access protected
     * @var boolean
     */
    var $transaction = false;
    /**
     * DBクライアントエンコード
     * 
     * @access protected
     * @var string
     */
    var $client_encode = '';
    /**
     * PHPエンコード
     * 
     * @access protected
     * @var string
     */
    var $php_encode = '';
    /**
     * エンコード変換テーブル
     * 
     * @access protected
     * @var string
     */
    var $encode_table = array();

    /**
     * フィールド配列を参照する場合の
     * フィールド名の大文字、小文字統一参照フラグ
     *
     * ※Mistaleは、CASE_UPPERのみ対応
     * 
     * @access protected
     * @var string 大文字参照: CASE_UPPER
     *             小文字参照: CASE_LOWER
     *             そのまま  : （空文字を設定）
     */
    // var $field_ref = '';
    var $field_ref = CASE_UPPER;

    /**
     * tigger_error発生フラグ
     * 
     * @access protected
     * @var bool
     */
    var $trigger_error_flag = true;
    /**
     * 最終エラーメッセージ
     * 
     * @access protected
     * @var string
     */
    var $last_error = '';
    /**
     * ログ取得コールバックメソッド
     * 
     * @access protected
     * @var mixed
     */
    var $callback_log = null;

    /**
     * コンストラクタ
     *
     * @access public
     * @param string Dbタイプ
     * @param string クライアントエンコード
     * @param string サーバーエンコード
     */
    function SyL_DB($dbtype, $client_encode, $php_encode)
    {
        set_magic_quotes_runtime(0);
        $this->dbname = $dbtype;
        $this->client_encode = $client_encode;
        $this->php_encode    = $php_encode;
    }

    /**
     * SyL_DBクラスのインスタンス取得
     *
     * ※取得方法
     *   $db =& SyL_DB::getInstance();
     *
     * $dsn = '[dbtype]:dbname=[dbname];host=[hostname];port=[port];user=[username];passwd=[password];client_encode=[client_encode];persistent=[persistent]';
     *
     * @access public
     * @param string 接続文字列
     */
    function &getConnection($dsn, $php_encode='')
    {
        static $singleton = array();
        if (!isset($singleton[$dsn])) {
            $tmp = explode( ':', $dsn, 2 );
            $dbtype = ucfirst($tmp[0]);
            $dbname = '';
            $user   = '';
            $passwd = '';
            $host   = '';
            $port   = '';
            $persistent    = '';
            $client_encode = '';
            if (isset($tmp[1])) {
                $tmp = explode(';', $tmp[1]);
                foreach ($tmp as $tmp2) {
                    list($name, $value) = explode('=', $tmp2, 2);
                    $$name = $value;
                }
            }

            $class_name = 'SyL_DB_' . $dbtype;
            // 実インスタンス作成
            include_once dirname(__FILE__) . "/DB/{$class_name}.php";
            $singleton[$dsn] = new $class_name($dbtype, $client_encode, $php_encode);
            $singleton[$dsn]->openConnection($dbname, $user, $passwd, $host, $port, $persistent);
        }
        return $singleton[$dsn];
    }

    /**
     * DB接続実行
     * 
     * @access protected
     * @param string データベース名
     * @param string ユーザー名
     * @param string パスワード
     * @param string ホスト名
     * @param string ポート番号
     * @param string 持続的接続を行うか
     @ @return boolean 接続OK: true, 接続NG: false
     */
    function openConnection($dbname, $user, $passwd, $host, $port, $persistent)
    {
        trigger_error("[SyL error] openConnection method not found ({$dbname})", E_USER_ERROR);
    }

    /**
     * DB接続終了
     * 
     * @access public
     */
    function closeConnection() {}

    /**
     * DBクライアント側文字エンコーティング設定
     * 
     * @access public
     * @param string 文字コード
     */
    function setClientEncoding($client_encode='')
    {
        if ($client_encode) {
            $this->client_encode = $client_encode;
        }
        if ($this->client_encode) {
            if (!$this->php_encode) {
                foreach ($this->encode_table as $encode_table) {
                    if ($encode_table[1] == strtolower($this->client_encode)) {
                        $this->php_encode = $encode_table[0];
                        break;
                    }
                }
            }
        } else if ($this->php_encode) {
            foreach ($this->encode_table as $encode_table) {
                if ($encode_table[0] == strtolower($this->php_encode)) {
                    $this->client_encode = $encode_table[1];
                    break;
                }
            }
        }
    }

    /**
     * パラメータを値によりクォートした文字列を取得する
     * ※サニタイズも実行される
     *
     * @access public
     * @param string クォート前文字列
     * @return string クォート後文字列
     */
    function quote($value)
    {
        if (($value === '') || ($value === null)) {
            return 'NULL';
        } else if (is_int($value) || is_float($value)) {
            return $value;
        } else {
            return "'" . $this->sanitize($value) . "'";
        }
    }

    /**
     * SQL条件文を取得する
     * ※サニタイズも実行される
     *
     * @access public
     * @param string 条件データ
     * @return string SQL条件文
     */
    function condition($value)
    {
        $value = $this->quote($value);
        if ($value == 'NULL') {
            $value = ' IS ' . $value . ' ';
        } else {
            $value = ' = ' . $value . ' ';
        }
        return $value;
    }
  
    /**
     * パラメータサニタイズ（無効化）処理
     * 
     * @access public
     * @param string サニタイズ対象文字列
     * @return string サニタイズ後文字列
     */
    function sanitize($parameter)
    {
        return !is_numeric($parameter) ? addslashes($parameter) : $parameter;
    }

    /**
     * パラメータサニタイズ（無効化）処理のLike文用
     * 
     * @access public
     * @param string サニタイズ対象文字列
     * @return string サニタイズ後文字列
     */
    function sanitizeLike($parameter)
    {
        return preg_replace('/(\_|\%)/', '\\\\$1', $this->sanitize($parameter));
    }

    /**
     * トランザクション開始
     *
     * @access public
     */
    function begin()
    {
        // SQLログ保存
        $this->log('begin');
  
        if (class_exists('SyL_Error')) {
            $e =& SyL_Error::getInstance();
            $e->resetErrorHandler();
        }
        $this->transaction = true;
    }

    /**
     * トランザクション破棄
     *
     * @access public
     */
    function rollback()
    {
        // SQLログ保存
        $this->log('rollback');

        if (class_exists('SyL_Error')) {
            $e =& SyL_Error::getInstance();
            $e->setErrorHandler();
        }
        $this->transaction = false;
    }

    /**
     * トランザクション確定
     *
     * @access public
     */
    function commit()
    {
      // SQLログ保存
      $this->log('commit');

      if (class_exists('SyL_Error')) {
          $e =& SyL_Error::getInstance();
          $e->setErrorHandler();
      }
      $this->transaction = false;
  }

    /**
     * SQLを実行し、結果取得
     * 
     * ・SQL文が、select句の場合
     *   実行結果をリソースとして取得
     * ・SQL文が、insert, update, delete句の場合
     *   実行結果影響件数を取得
     * ・SQL文が、上記以外の場合
     *   実行後、true or falseを返却
     *
     * @access public
     * @param string SQL文
     * @param mixed 実行結果件数、またはDBリソース
     * @return boolean 実行OK: true, 実行NG: false
     */
    function execute($sql, &$result)
    {
        // SQLログ保存
        $this->log($sql);

        // メソッド名抽出
        list($method) = explode( ' ', $sql, 2 );

        // SQL実行
        $result = false;
        switch (strtolower($method)) {
        case 'select':
            $result = $this->executeSelect($sql);
            break;
        case 'insert':
        case 'update':
        case 'delete':
            $result = $this->executeUpdate($sql);
            break;
        default:
            $result = $this->executeNoReturn($sql);
            break;
        }
        return ($result === false) ? false : true;
    }

    /**
     * SQLを実行し、DBリソースを取得
     *
     * @access protected
     * @param string SQL文
     * @return mixed 実行OK: DBリソース, 実行NG: false
     */
    function &executeSelect($sql)
    {
        trigger_error("[SyL error] executeSelect method not found ({$dbname})", E_USER_ERROR);
    }

    /**
     * SQLを実行し、実行結果影響件数を取得
     *
     * @access protected
     * @param string SQL文
     * @return mixed 実行OK: 実行結果影響件数, 実行NG: false
     */
    function executeUpdate($sql)
    {
        trigger_error("[SyL error] executeUpdate method not found ({$dbname})", E_USER_ERROR);
    }

    /**
     * SQL実行のみ
     *
     * @access protected
     * @param string SQL文
     * @return boolean 実行OK: true, 実行NG: false
     */
    function executeNoReturn($sql)
    {
        trigger_error("[SyL error] executeNoReturn method is not found ({$dbname})", E_USER_ERROR);
    }

    /**
     * SQL実行し、結果データを取得
     *
     * ・第3パラメータが、'one'の場合
     *    最初の1カラムを文字列として取得
     * ・第3パラメータが、'record'の場合
     *    最初の1レコードを配列として取得
     * ・第3パラメータが、'all'の場合
     *    全レコードを配列として取得
     *
     * @access public
     *
     * @param string SQL文
     * @param mixed  実行結果を取得
     * @param string 結果取得フラグ
     * @return boolean 実行OK: true, 実行NG: false
     */
    function executeGet($sql, &$data, $get='all') {}

    /**
     * SQLを実行し、ページ毎にデータを取得
     * ※実行前に「initializePage」メソッド実行必須
     * ※SQL文の「FROM」句の直前にタブ文字必須。（それ以外のタブ文字不可）
     *
     * @access public
     * @param string SQL文
     * @param array 実行結果格納配列
     * @param object ページオブジェクト
     * @param int 1ページの表示件数
     * @param int 表示対象ページ数 
     * @return bool 実行結果
     */
    function executePage($sql, &$result, &$page_control, $limit, $page=1) {}

    /**
     * 指定形式のパラメータを引数に、SQL（DML）を組み立て実行する
     * ※サニタイズも自動で行われる
     * ※変数は配列で指定する以外は、型で判定される。
     *
     * array(
     *  'name'     => $name,
     *  'address'  => $adress,
     *  'datetime' => array('function' => 'current_timestamp'),
     *  'id'       => array('number' => 1),
     *  'IS_NULL'  => array('null' => ''),
     *  ...
     * );
     *
     * @access public
     * @param string テーブル名
     * @param array カラムとそのデータ配列
     * @param string insert or update or delete
     * @param string 条件パラメータ（update, delete時のみ）
     * @return bool 実行結果 true: OK、false: エラー
     */
    function executePerform($table, $columns, $action='insert', $where='')
    {
        switch (strtolower($action)) {
        case 'insert':
            $field = array();
            $data  = array();
            foreach ($columns as $column => $value) {
                $field[] = $column;
                if (is_array($value)) {
                    list($attribute, $value) = each($value);
                    if ((strtolower($attribute) === 'null') || ($value === null) || ($value === '')) {
                        $data[] = "NULL";
                    } else {
                        switch (strtolower($attribute)) {
                        case 'function':
                        case 'number':
                            $data[] = $value;
                            break;
                        default:
                            $data[] = $this->quote($value);
                            break;
                        }
                    }
                } else {
                    $data[] = $this->quote($value);
                }
            }
            // SQL INSERT
            $sql = "INSERT INTO " . $table . " (" . implode(',', $field) . ") VALUES (" . implode(',', $data) . ")";
            break;

        case 'update':
            $update = array();
            foreach ($columns as $column => $value) {
                if (is_array($value)) {
                    list($attribute, $value) = each($value);
                    if ((strtolower($attribute) === 'null') || ($value === null) || ($value === '')) {
                        $update[] = $column . " = NULL";
                    } else {
                        switch (strtolower($attribute)) {
                        case 'function':
                        case 'number':
                            $update[] = $column . " = " . $value;
                            break;
                        default:
                            $update[] = $column . " = " . $this->quote($value);
                            break;
                        }
                    }
                } else {
                    $update[] = $column . " = " . $this->quote($value);
                }
            }
      
            if ($where != '') {
                $where = "WHERE " . $where;
            }
            // SQL UPDATE
            $sql = "UPDATE " . $table . " SET " . implode(',', $update) . " " . $where;
            break;

        case 'delete':
            if ($where != '') {
                $where = "WHERE " . $where;
            }
            // SQL DELETE
            $sql = "DELETE FROM " . $table . " " . $where;

        default:
          trigger_error("[SyL error] executePerform method is insert or update only", E_USER_ERROR);
        }

        return $this->execute($sql, $num);
    }


    /**
     * SyLストアドプロシージャオブジェクトを取得
     *
     * @access public
     * @param string ストアドプロシージャ名
     * @return object SyLストアドプロシージャオブジェクト
     */
    function &getProcedureObject( $procedure_name )
    {
        $classname = 'SyL_DB_' . $this->dbname . '_Procedure';
        include_once( $classname . '.php' );
        return (new $classname( $this->connection, $procedure_name, $this->transaction ));
    }
  
    /**
     * トリガエラーを自動で起動しないように設定
     *
     * @access public
     * @param boolean true: トリガエラーON、false: トリガエラーOFF
     */
    function setTriggerError($trigger_error_flag)
    {
        $this->trigger_error_flag = ($trigger_error_flag === true);
    }

    /**
     * trigger_error発動
     * ただし、トランザクション中は発動しない
     *
     * @access public
     * @param string エラーメッセージ
     */
    function executeTriggerError($error_message)
    {
        if (($this->transaction) || (!$this->trigger_error_flag)) {
            $this->last_error = $error_message;
        } else {
            trigger_error($error_message, E_USER_ERROR);
        }
    }

    /**
     * 最後に起こったエラーメッセージを取得
     *
     * @access public
     * @return string 最後に起こったエラーメッセージ
     */
    function getLastErrorMessage()
    {
        return $this->last_error;
    }

    /**
     * 結果セット配列のフィールド名を大文字または小文字参照にする
     *
     * @access public
     * @return array 変換前配列
     * @return array 変換後配列
     */
    function changeFieldName($record)
    {
        if (is_array($record) && $this->field_ref) {
            return array_change_key_case($record, $this->field_ref);
        } else {
            return $record;
        }
    }

    /**
     * ログ取得コールバックメソッドをセット
     *
     * @access public
     * @param mixed ログ取得コールバックメソッド
     */
    function setCallbackLog($callback_log)
    {
        if (is_callable($callback_log)) {
            $this->callback_log = $callback_log;
        } else {
            trigger_error("[SyL error] setCallbackLog argument invalid function (" . print_r($callback_log, true) . ")");
        }
    }

    /**
     * SQLログを保存
     *
     * @access public
     * @param string SQL文
     */
    function log($sql)
    {
        if ($this->callback_log != null) {
            call_user_func($this->callback_log, $sql);
        }
    }

    /**
     * カレントの接続リソースを取得する
     *
     * @access public
     * @return resource DB接続リソース
     */
    function &getResourceConnection()
    {
        return $this->connection;
    }

    /**
     * DBクラス名を取得
     *
     * @access public
     * @return string DBクラス名
     */
    function getDBName()
    {
        return $this->dbname;
    }

    /**
     * システム日付取得関数名を取得
     *
     * @access public
     * @return string システム日付取得関数名
     */
    function getSqlSystemDate()
    {
        return 'CURRENT_TIMESTAMP';
    }

    /**
     * 日付フォーマット関数名を取得
     *
     * @access public
     *
     * @param string カラム名
     * @param string 日付型フォーマット
     * @return string 日付フォーマット関数名
     */
    function getSqlDateFormat($column_name, $format) {}

    /**
     * テーブル一覧を取得する
     *
     * @access public
     *
     * @return array テーブルリスト
     */
    function getTableList()
    {
        return array();
    }

    /**
     * カラム一覧を取得する
     *
     * @access public
     *
     * @param string テーブル名
     * @return array カラムリスト
     */
    function getColumnList( $table_name )
    {
        return array();
    }
}

?>
