EthnaのAppObjectをADOdbでも使いたい!

それ程こだわりも無いんだけど、長いこと放置されてきたような気もするし、ADOdb使おうかなと思ったのでやってみた。
結果的にいうと「自分にしては結構大変だわ」でした。

以下、オーバーライドして使うHoge_DB_ADOdbクラスのソースですよ。
require_once 'Ethna/class/DB/Ethna_DB_ADOdb.php';

// PEAR:DBに合わせる為(これがないとnotice出します)
define('DB_FETCHMODE_DEFAULT', null);
define('DB_FETCHMODE_ASSOC',   null);

/**
 *  Hoge_DB_ADOdb
 *
 *  EthnaのフレームワークでADOdbオブジェクトを扱うための抽象クラス
 *
 *  @package    Ethna
 *  @author     longkey1
 *  @access     public
 */
class Hoge_DB_ADOdb extends Ethna_DB_ADOdb
{
    /**#@+
     *  @access private
     */

    /** @var    object  Ethna_Logger    ログオブジェクト */
    var $logger;

    /** @var    object  Ethna_AppSQL    SQLオブジェクト */
    var $sql;

    /** @var    string  DBタイプ(mysql, pgsql...) */
    var $type;

    /** @var    array   DSN (DB::parseDSN()の返り値) */
    var $dsninfo;

    /**#@-*/

    /**
     *  コンストラクタ
     *
     *  @access public
     *  @param  object  Ethna_Controller    &$controller    コントローラオブジェクト
     *  @param  string  $dsn                                DSN
     *  @param  bool    $persistent                         持続接続設定
     */
    function Hoge_DB_ADOdb(&$controller, $dsn, $persistent)
    {
        parent::Ethna_DB_ADOdb($controller, $dsn, $persistent);

        $this->db = null;
        $this->logger =& $controller->getLogger();
        $this->sql =& $controller->getSQL();

        $this->dsninfo = $this->parseDSN($dsn);
        $this->dsninfo['new_link'] = true;
        $this->type = $this->dsninfo['phptype'];
    }

    // {{{ getType
    /**
     *  DBタイプを返す
     *
     *  @access public
     *  @return string  DBタイプ
     */
    function getType()
    {
        return $this->type;
    }
    // }}}


    // {{{ getMetaData
    /**
     *  テーブル定義情報を取得する
     *
     *  @access public
     *  @param  string  $table  テーブル名
     *  @return mixed   array: PEAR::DBに準じたメタデータ Ethna_Error::エラー
     */
    function &getMetaData($table)
    {
        $def =& $this->db->MetaColumns($table);
        $deforg = $def;// originalとして退避

        if (is_array($def) === false) {
            return $def;
        }

        foreach (array_keys($def) as $k) {
            // object型で返してくるので
            $def[$k] = array_map('strtolower', (array)$def[$k]);

            // type
            $type_map = array(
                'int'       => array(
                    'int', 'integer', '^int\(?[0-9]\+', '^serial', '[a-z]+int$',
                ),
                'boolean'   => array(
                    'bit', 'bool', 'boolean',
                ),
                'datetime'  => array(
                    'timestamp', 'datetime',
                ),
            );
            foreach ($type_map as $convert_to => $regex) {
                foreach ($regex as $r) {
                    if (preg_match('/'.$r.'/', $def[$k]['type'])) {
                        $def[$k]['type'] = $convert_to;
                        break 2;
                    }
                }
            }

            // len
            if($def[$k]['max_length'] > 0){
                $def[$k]['len'] = $def[$k]['max_length'];
            }

            // flags
            if($def[$k]['primary_key'] == true){
                $def[$k]['flags'][] = 'primary_key';
            }
            if($def[$k]['not_null'] == true){
                $def[$k]['flags'][] = 'not_null';
            }
            switch ($this->type) {
                case 'mysql':
                case 'pgsql':
                    // カウンタあるいは自動インクリメントフィールドであれば sequence
                    if ($this->db->MetaType($deforg[$k], null, true) == 'R') {
                        $def[$k]['flags'][] = 'sequence';
                        break;
                    }
                    break;
                // ADOdbの対応次第かなあ、今のところちと厳しい
//              case 'sqlite':
//                  break;
            }

        }

        return $def;
    }
    // }}}

    // {{{ qstr
    /**
     *  文字列をクオートする
     *  
     *  @access public
     *  @param  string  $s    クオートする文字列
     *  @return string        クオートされた文字列
     */
    function qstr($s, $magic_quotes=false)
    {
        return $this->db->qstr($s,$magic_quotes);
    }
    // }}}

    // {{{ Ethna_AppObject連携のための実装
    // {{{ getNextId
    /**
     *  直後のINSERTに使うIDを取得する
     *  (pgsqlのみ対応)
     *  ※adodbでは特に対応していないのでnullを返す、代わりにgetInsertIDを対応させた
     *
     *  @access public
     *  @return mixed   int
     */
    function getNextId($table_name, $field_name)
    {
        return null;
    }
    // }}}

    // {{{ getInsertId
    /**
     *  直前のINSERTによるIDを取得する
     *  (mysql, pgsql, sqlite対応)
     * ※pgsql用に引数を追加
     *  @access public
     *  @return mixed   int:直近のINSERTにより生成されたID null:未サポート
     */
    function getInsertId($table_name = null, $field_name = null)
    {
        if ($this->isValid() == false) {
            return null;
        } else if ($this->db->Insert_ID($table_name, $field_name) == false) {
            return null;
        }
        return $this->db->Insert_ID($table_name, $field_name);
    }
    // }}}

    // {{{ fetchRow
    /**
     *  DB_Result::fetchRow()の結果を整形して返す
     *
     *  @access public
     *  @return int     更新行数
     */
    function &fetchRow(&$res, $fetchmode = DB_FETCHMODE_DEFAULT, $rownum = null)
    {
        $row =& $res->FetchRow();
        if (is_array($row) === false) {
            return $row;
        }

        if ($this->type === 'sqlite') {
            // "table"."column" -> column
            foreach ($row as $k => $v) {
                unset($row[$k]);
                if (($f = strstr($k, '.')) !== false) {
                    $k = substr($f, 1);
                }
                if ($k{0} === '"' && $k{strlen($k)-1} === '"') {
                    $k = substr($k, 1, -1);
                }
                $row[$k] = $v;
            }
        }

        return $row;
    }
    // }}}

    // {{{ affectedRows
    /**
     *  直近のクエリによる更新行数を取得する
     *
     *  @access public
     *  @return int     更新行数
     */
    function affectedRows()
    {
        return $this->db->Affected_Rows();
    }
    // }}}

    // {{{ quoteIdentifier
    /**
     *  dbのtypeに応じて識別子をquoteする
     *  (配列の場合は各要素をquote)
     *
     *  @access protected
     *  @param  mixed   $identifier array or string
     */
    function quoteIdentifier($identifier)
    {
        if (is_array($identifier)) {
            foreach (array_keys($identifier) as $key) {
                $identifier[$key] = $this->quoteIdentifier($identifier[$key]);
            }
            return $identifier;
        }

        $ret = $this->db->nameQuote . $identifier . $this->db->nameQuote;
        return $ret;
    }
    // }}}

    // {{{ sqlquery
    /**
     *  SQL文指定クエリを発行する
     *
     *  @access public
     *  @param  string  $sqlid      SQL-ID(+引数)
     *  @return mixed   DB_Result:結果オブジェクト Ethna_Error:エラー
     */
    function &sqlquery($sqlid)
    {
        $args = func_get_args();
        array_shift($args);
        $query = $this->sql->get($sqlid, $args);

        return $this->_query($query);
    }
    // }}}

    // {{{ sql
    /**
     *  SQL文を取得する
     *  
     *  @access public
     *  @param  string  $sqlid      SQL-ID
     *  @return string  SQL文
     */
    function sql($sqlid)
    {
        $args = func_get_args();
        array_shift($args);
        $query = $this->sql->get($sqlid, $args);

        return $query;
    }
    // }}}
    // }}}
}


まあ、こんな感じ?
今のところちゃんと動いていたEthna_DB_PEARクラスを超参考にしてあるので、自分では使わないメソッドてんこ盛り。
そこはテストもしてないので、ようわかりません。
ポイントとしては、
・getNextId()はADOdbではサポートしてないので、実質放棄
・getInsertId()にpgsqlを対応させるため、引数を変更
・getMetaData()のsqliteはADOdbではサポートしてないので放棄
sqliteに関しては、ちょっと絶望的かもなあ。


あと、Ethna_AppObjectクラスもオーバーライドするのでした。

// {{{ Hoge_AppObject
/**
 *  アプリケーションオブジェクトのベースクラス
 *
 *  @package    Hoge
 *  @author     longkey1
 *  @access     public 
 */
class Hoge_AppObject extends Ethna_AppObject
{
    // {{{ add
    /**
     *  オブジェクトを追加する
     *
     *  @access public
     *  @return mixed   0:正常終了 Ethna_Error:エラー
     */
    function add()
    {
        // next idの取得: (pgsqlの場合のみ)
        // 取得できた場合はこのidを使う
        // ※ADOdb対応でnext id取得できなくなったのでいらないかもだけど、
        //   とりあえず残しておく
        foreach (to_array($this->id_def) as $id_def) {
            if (isset($this->prop_def[$id_def]['seq'])
                && $this->prop_def[$id_def]['seq']) {
                // NOTE: このapp object以外からinsertがないことが前提
                $next_id = $this->my_db_rw->getNextId(
                    $this->prop_def[$id_def]['table'], $id_def);
                if ($next_id !== null && $next_id >= 0) {
                    $this->prop[$id_def] = $next_id;
                }
                break;
            }
        }

        $sql = $this->_getSQL_Add();
        for ($i = 0; $i < 4; $i++) {
            $r =& $this->my_db_rw->query($sql);
            if (Ethna::isError($r)) {
                if ($r->getCode() == E_DB_DUPENT) {
                    // 重複エラーキーの判別
                    $duplicate_key_list = $this->_getDuplicateKeyList();
                    if (Ethna::isError($duplicate_key_list)) {
                        return $duplicate_key_list;
                    }
                    if (is_array($duplicate_key_list)
                        && count($duplicate_key_list) > 0) {
                        foreach ($duplicate_key_list as $k) {
                            return Ethna::raiseNotice('Duplicate Key Error [%s]',
                                                      E_APP_DUPENT, $k);
                        }
                    }
                } else {
                    return $r;
                }
            } else {
                break;
            }
        }
        if ($i == 4) {
            // cannot be reached
            return Ethna::raiseError('Cannot detect Duplicate key Error', E_GENERAL);
        }

        // last insert idの取得: (mysql, postgres, sqlite)
        // primary key の 'seq' フラグがある(最初の)プロパティに入れる
        // ※ pgsql用にテーブルとカラム名をセットするように変更
        foreach (to_array($this->id_def) as $id_def) {
            if (
                (isset($this->prop_def[$id_def]['seq'])) &&
                ($this->prop_def[$id_def]['seq'])
            ) {
                $insert_id = $this->my_db_rw->getInsertId(
                    $this->prop_def[$id_def]['table'],
                     $id_def
                );
                if ($insert_id !== null && $insert_id >= 0) {
                    $this->prop[$id_def] = $insert_id;
                }
                break;
            }
        }

        // IDの設定
        if (is_array($this->id_def)) {
            $this->id = array();
            foreach ($this->id_def as $k) {
                $this->id[] = $this->prop[$k];
            }
        } else if (isset($this->prop[$this->id_def])) {
            $this->id = $this->prop[$this->id_def];
        } else {
            trigger_error("primary key is missing", E_USER_ERROR);
        }

        // バックアップ/キャッシュ更新
        $this->prop_backup = $this->prop;
        $this->_clearPropCache();

        return 0;
    }
    // }}}

}
// }}}</pre>

これで最低限動くかなって感じだ。
next idが絡むところは、全部削除しても良いかも。
無理してsql直書きでってやり方もあるだろうけど、それはADOdbとは完全に違うものになってしまうしなあ。
どうなんだろうね。

とりあえず、ネット上にこういう情報がほぼ皆無だったので、晒しておきます。

※ 2008/10/08 追記
以下を最初の方に追記しました。
<
efine('DB_FETCHMODE_DEFAULT', null);
define('DB_FETCHMODE_ASSOC',   null);</pre>
実働には支障が無い(使ってない)のですが、Noticeだしてsimpletest使う時とかにやたらexceptionsを出すので。

コメント

アーカイブ

2010

2009

2008

2007

コンタクト

longkey1[at]gmail[dot]com