[zend]アプリ側(UTF8)とDB(EUC-JP)の文字コード変換をPDOで行う

( PHP )

システム開発行っていると、なるべく昔の仕様を変更せずに既存リソースを生かしながらも新しい開発行っていかな ければいけない自体が多々ある思われる。

今回の件でいえば、文字コードの問題。

システム側からみたら文字コードなんていっそのこと統一したほうが何も考えないで楽なんだけど、そうも いってられず昔の文字コードをどう利用していくかを考えなければいけないときがある。

理想としてはすべてUTF-8にしたかったがそうはいかない。DBがEUC-JPだからだ。 DB・アプリ間でのインターフェースでそれぞれ変換する必要がある。

対策としては、ZendFrameworkで利用しているPDOのクラスを継承して変換をさせるようにしました。 (この場合、局所的な対処ですがとりあえず目的は果たせたのでこれでいきました)

※ZendでDIコンテナ使って細かいこと実現しているのでそのへんは・・・・。

まずは

abstract class App_Db_Adapter_Pdo_Abstract extends Zend_Db_Adapter_Pdo_Abstract
{
    /**
     * Creates a PDO object and connects to the database.
     *
     * @return void
     * @throws Zend_Db_Adapter_Exception
     */
    protected function _connect()
    {
        // if we already have a PDO object, no need to re-connect.
        if ($this->_connection) {
            return;
        }

        // get the dsn first, because some adapters alter the $_pdoType
        $dsn = $this->_dsn();

        // check for PDO extension
        if (!extension_loaded('pdo')) {
            /**
             * @see Zend_Db_Adapter_Exception
             */
            require_once 'Zend/Db/Adapter/Exception.php';
            throw new Zend_Db_Adapter_Exception('The PDO extension is required for this adapter but the extension is not loaded');
        }

        // check the PDO driver is available
        if (!in_array($this->_pdoType, PDO::getAvailableDrivers())) {
            /**
             * @see Zend_Db_Adapter_Exception
             */
            require_once 'Zend/Db/Adapter/Exception.php';
            throw new Zend_Db_Adapter_Exception('The ' . $this->_pdoType . ' driver is not currently installed');
        }

        // create PDO connection
        $q = $this->_profiler->queryStart('connect', Zend_Db_Profiler::CONNECT);

        // add the persistence flag if we find it in our config array
        if (isset($this->_config['persistent']) && ($this->_config['persistent'] == true)) {
            $this->_config['driver_options'][PDO::ATTR_PERSISTENT] = true;
        }

        try {
            $this->_connection = new PDOCSTM(
                $dsn,
                $this->_config['username'],
                $this->_config['password'],
                $this->_config['driver_options']
            );

            $this->_profiler->queryEnd($q);

            // set the PDO connection to perform case-folding on array keys, or not
            $this->_connection->setAttribute(PDO::ATTR_CASE, $this->_caseFolding);

            // always use exceptions.
            $this->_connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

        } catch (PDOException $e) {
            /**
             * @see Zend_Db_Adapter_Exception
             */
            require_once 'Zend/Db/Adapter/Exception.php';
            throw new Zend_Db_Adapter_Exception($e->getMessage(), $e->getCode(), $e);
        }

    }

}

なんてものを作成します。

実際変更したところといえば、

# その1
$this->_connection = new PDOCSTM();

あとは、PDOを継承して実装したクラスを記述しておしまいです。 これでほぼ取得や更新両方SQLを変換することができます。

class PDOCSTM extends PDO {
    public function __construct($dsn, $username="", $password="", $driver_options=array()) {
        parent::__construct($dsn, $username, $password, $driver_options);
        $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('STMT', array($this)));
    }
}
class STMT extends PDOStatement {
    public $dbh;
    protected function __construct($dbh) {
        $this->dbh = $dbh;
    }
    function fetch($option=PDO::FETCH_ASSOC) {
        $row = parent::fetch($option);
        foreach ($row as $key=>$val) {
            # その1
            $row[$key] = mb_convert_encoding($val, "UTF-8", "EUC-JP");
        }
        return $row;
    }
    function fetchAll($option=PDO::FETCH_ASSOC) {
        $rows = parent::fetchAll($option);
        foreach ($rows as $key => $row) {
            foreach ($row as $crm => $val) {
                # その2
                $rows[$key][$crm] = mb_convert_encoding($val, "UTF-8", "EUC-JP");
            }
        }
        return $rows;
    }
    function execute($params=array()) {
        if (APPLICATION_ENV !== "production") {
            if (!preg_match("/SELECT/", $this->queryString)) {
                error_log("[SQL] " . $this->queryString . " Array::" . print_r($params,true));
            }
            else {
                error_log("[SQL] " . $this->queryString);
            }
            if (!empty($params)) {
                # その3
                $params = App_Array::getEncodedData($params, "EUC-JP", "UTF-8");
            }
        }
        return parent::execute($params);
    }
}

ちなみに「App_Array()」は配列のデータを再帰的に文字コード変換させてます。

                # その3
                $params = App_Array::getEncodedData($params, "EUC-JP", "UTF-8");