hogehoge foobar Blog Style Beta

Web,Mac,Linux,JavaScript,Perl,PHP,RegExp,Git,Vim,Redmineなど技術的なことのメモや、ちょっと便利そうなものの紹介をしています。

PHPから登録したCLOB型の文字化けについて

以前に、
PHPセッションをDBで管理 - OracleでのAutoIncrement用テーブルを作成編
PHPセッションをDBで管理 - PHPセッション管理のコールバック関数を設定編
というエントリを書きましたが、その時に使用したPHPから「OracleのCLOB型」にデータ登録をした場合に、「文字化け」が発生する現象があったので、補足として書いておきます。

PHPソース=SJIS + NLS_CHARACTERSET=JA16SJIS

今回の場合、「PHPソース=SJIS + NLS_CHARACTERSET=JA16SJIS」の環境で文字化けが発生しました。
Oracleの「NLS_CHARACTERSET」の設定値は以下のSQLで確認できます。

NLS_CHARACTERSETの確認(SQL)
SELECT * FROM NLS_DATABASE_PARAMETERS WHERE PARAMETER='NLS_CHARACTERSET'
NLS_CHARACTERSETの確認(結果の例)
PARAMETER VALUE
NLS_CHARACTERSET JA16SJIS

PHPの内部の文字コードphp.iniで確認できます。
これにプラスして、実際のソースコード文字コードも合わせる必要があります。

internal_encodingの設定 SJISの場合 (php.ini)
mbstring.internal_encoding = SJIS

今回の原因

今回、文字化けが発生した原因ですが、Oracleのサポートガイドに以下のような記述がありました。

注意:
データベース・キャラクタ・セットがマルチバイトの場合、CLOBデータはUCS-2との互換形式で格納されます。データベース・キャラクタ・セットがシングルバイトの場合、CLOBデータはデータベース・キャラクタ・セットで格納されます。

Oracle Database グローバリゼーション・サポート・ガイド キャラクタ・セットの選択

つまりは、CLOB型に全角等のマルチバイト文字を登録しようとした場合は、暗黙的に文字コードが「UCS-2」に変換されるみたいです。
今回はここの「SJISUCS-2」の変換で文字化けが発生しているようでした。

対処方法

対処方法としては、2つのパターンがあります。

 →PHPソースの文字コードUTF-8であれば文字化けは発生しないみたいです。

  • CLOBでは無く、BLOBを使用する。

 →CLOBではマルチバイト文字が暗黙的にUSC-2に変換されるが、BLOBは文字コード変換は行わない。

前回のサンプルコードの改良版(BLOB対応版)

PHPセッションをDBで管理 - PHPセッション管理のコールバック関数を設定編でCLOBを使用したサンプルソースを掲載しましたが、
「CLOBでは無く、BLOBを使用する」とした場合は、「セッションの書き込み」のソースの一部を変更する必要があります。
変更点は下記の2点です。

  • empty_blob()empty_blob() に変更
  • oci_bind_by_name($stmt, ":lob", &$clob, -1, OCI_B_CLOB); → oci_bind_by_name($stmt, ":lob", &$blob, -1, OCI_B_BLOB); に変更
<?php
//セッションの書き込み時(BLOB版)
function sess_write($id, $data)
{
    // セッションを一度削除
    $conn = oci_connect("username", "password", "oracle_sid");
    $sql = "DELETE FROM sessions WHERE sess_id = '" . $id . "'";
    $stmt = oci_parse($conn, $sql);
    oci_execute($stmt, OCI_DEFAULT);
    
    // 新たなシーケンスでセッションを再度書き込み
    // シーケンスはOracleトリガーで自動インクリメントされる
    $sql = "INSERT INTO sessions (sess_id, data, update_time) VALUES ('$id', empty_blob(), sysdate) RETURNING data INTO :lob";
    $stmt = oci_parse($conn, $sql);
    $blob = oci_new_descriptor($conn, OCI_D_LOB);
    oci_bind_by_name($stmt, ":lob", &$blob, -1, OCI_B_BLOB);
    oci_execute($stmt, OCI_DEFAULT);
    $blob->save($data);
        
    oci_commit($conn);
    oci_free_statement($stmt);
    oci_close($conn);
    
    $rtn = true;
    return $rtn;
}
?>

今回参考にしたページ

Oracle Database グローバリゼーション・サポート・ガイド - キャラクタ・セットの選択
http://otndnld.oracle.co.jp/document/products/oracle10g/102/doc_cd/server.102/B19218-02/ch2charset.htm