migration

バージョンの移行による文字化けの原因に関して

以下は、バージョン間の差異による文字化けへの原因です。

PHPのコードを読める方は、この文書の終わりにMySQLまわりのコードをNucleus CMSの3.41、3.51、3.64から引用しておきましたので、違いを把握するための助けとして下さい。

加えて、HTTP、Apache2、PHP、MySQLの各モジュールの連関に関して、フォーラムの投稿「マルチバイト処理の概念図」も参照して下さい。PHPからMySQLの部分を以下に掲載します。

文字化けの概要

文字化けの原因は、保存されている日本語文字列の文字符号化方式と、それを読みだして表示する側の文字符号化方式の食い違いから発生します。

文字符号化方式とは一般的に「エンコード」とか「キャラクターセット」と呼ばれているものです。この文書では、日本工業規格(JIS)で用いられている用語「文字符号化方式」で統一します。

保存されていなくても、例えばHTTPクライアント(ウェブブラウザ)からHTTPサーバへ送信されるデータの符号化文字方式と、それを処理するPHP側の符号化文字方式が異なっていても、あるいはその逆でも、文字化けは発生します。

しかしNucleus CMSの場合、文字化けの原因は、MySQLサーバによる「クエリの符号化文字列の自動変換」が原因でした。これは、以下の黒矢印部分で発生します。

Nucleus CMSのケース(概要)

Nucleus CMSは3.41以前と3.51以降で、文字符号化方式の内部的な扱いを変更しました。これは2005年から2010年の間に、一般的な「レンタルサーバ」のMySQLやPHPの環境が変化し、日本語の文字符号化方式をより厳密に扱うことが可能となったからです。この変化により、例えばNucleus CMSの日本語文字検索機能がより効率よく動いたり、文字符号化方式の変換にかかっていた時間ロスが軽減されたり、文字符号化方式の検出時のミス判定が軽減されるといったメリットが生まれました。

しかしその結果、3.41から3.51以降へアップグレードする際に、文字化けを起こしてしまうという可能性が残りました。

Nucleus CMSのケース(詳細)

以下は詳細な情報です。おそらく開発者じゃないと読むのにしんどいと思います。先に次の節の「MySQLの文字符号化方式と照合順序について」を読んでおくのもいいかもしれません。

3.41以前の特徴

  • MySQLサーバ内のデータベースに対してcharacter setやcollationなどを指定せずに使っていた
  • それによって、MySQLサーバ周りのcharacter setやcollationの設定値は、標準の「latin1」と[latin1_swedesh_ci]が使われた
  • PHPから発行したクエリは、例えそれが「UTF8」に従って符号化された文字列をされていても、MySQLは「Latin1」に従って符号化されたされた文字列として扱っていた
  • なお、扱いが変わっただけなので、符号化方式の変換は発生していない
  • データベースの照合順序が「latin1」であっても、データを「そのまま」引っ張りだして、「UTF-8」で符号化した文字列として利用することができた
  • ただの「データの置き場」となっていたと考えて差し支えない。そのため、文字化けは発生しなかった
  • その対価として、3.41系は検索機能がちゃんと働かない場合もあるという問題を抱えることとなった。設定されている照合順序に対し、入力されている文字列の符号化文字方式が対応したものではないのがその理由

3.51以降の特徴

  • MySQLサーバ内のデータベースに対して日本語向けに文字符号化方式や照合順序を設定するようにした
  • 加えてデータベースへの接続時に、関数「mysql_set_charset」やクエリ「SET CHARACTER SET x;」を発行することで、接続に対しても文字符号化方式を設定するようにした
  • データベースやテーブル・接続に対して日本語に適した設定を与えることで、検索機能がより正確に動くようにした

3.41以前を3.51以降で使う場合に起こりうるトラブル

  • 3.41以前をインストールしたデータベースで照合順序を「latin1_swedesh_ci」などとして利用している場合、3.51以降で読み取ろうとする際に、データベースの照合順序とそれへの接続の文字符号化方式が異なるためにそこで文字符号化方式の自動変換が発生し、結果文字化けが発生することもあります

詳しく知りたい方は、以下の節を参照して下さい。

参考資料

開発者用の各種ドキュメント

キャラクターセットと文字符号化方式について

キャラクターセットは、文字の集まりのことです。文字種とか言ったりします。例えば英数字であればASCII、日本語であればJIS X 0208といった規格に定められた(収集された)文字のことです。

文字符号化方式は、それらキャラクターセットをコンピュータで扱うことが出来るように、文字とビット列をマッピングする方法です。例えばISO-8859-1(Latin-1)やEUC-JP、ISO-2022-JP、UTF-8などです。

MySQLのクエリ内の命令は全てASCIIキャラクターセットを用いています。ISO-8859-1〜16でもEUC-JPでもUTF-8でも、ASCIIで知られるキャラクターセットに関しては、同じビット列を割り当てています。そのため、文字符号化方式の相互変換で日本語が文字化けしたとしても、MySQLの命令は実行されることができます。

MySQLの文字符号化方式と照合順序について

MySQLには、文字符号化方式に関する2つの概念があります。ひとつは文字符号化方式(character set)で、もうひとつは照合順序(collation)です。照合順序は、mySQLサーバ内部でデータのソートをする際に利用されるもので、例えば英数字であれば「大文字・小文字を区別しない」とか「大文字・小文字を区別する」とか「アルファベットが数字より前に来る」とか「数字よりアルファベットが前に来る」といったように、同一の符号化方式に複数の照合順序が定義できます。

そのため、照合順序が与えられれば、符号化文字方式も定まります。文字符号化方式が照合順序のスーパーセットとなっているためです。なので以下は簡単のために、照合順序にターゲットを絞って説明します。

なお、これはMySQL5に関する情報です。MySQL4はわかりません。

テーブル、データベース、サーバの照合順序について

MySQLサーバではテーブル作成時に照合順序を指定することもできますが、指定しなくても問題ないです。

データベース作成時も同様に、照合順序を指定してもしなくても問題ありません。

作成したデータベースに接続する際、データベースの照合順序は以下のように決定されます。

  1. 現在のデータベースに対して文字符号化方式が設定されていたら、それをセッション変数「collation_database」とする。
  2. 上記じゃなかったら、MySQLサーバのデフォルト値であるグローバル変数「collation_server」と同じ値を、セッション変数「collation_database」に設定する。

データベースに接続した後、テーブルを扱う場合の照合順序は

  1. テーブル毎に照合順序を指定されたなら、それを用いる
  2. 1.じゃなかったら、上記で定義されたセッション変数「collation_database」を用いる

なお、決定した照合順序に関するセッション変数「collation_database」に合わせて自動的に、文字符号化方式に関するセッション変数「character_set_database」が設定されます。

接続自体の照合順序について

接続自体も照合順序に関する設定を持ちます。セッション変数「collation_connection」です。

PHP関数「mysql_set_charset」やクエリ「SET CHARACTER SET x;」を実行することで、セッション変数「collation_database」の値が、このセッション変数「collation_collection」に設定されます。

同時に、セッション変数「charset_database」の値が、セッション変数「charset_connection」に設定されます。

文字符号化方式の自動変換について

接続自体は「collation_connection」の他に、文字符号化方式に関する設定値である「character_set_client」「character_set_connection」「character_set_result」を持ちます。

  1. 「character_set_client」はPHPのMySQL拡張あるいはPDO拡張から入力されるクエリの文字符号化方式です。
  2. 「character_set_connection」はMySQLの処理系で扱われる文字符号化方式です。
  3. 「character_set_result」はMySQLの処理系がクエリを実行した結果として出力する文字列の文字符号化方式です。

MySQLサーバは、「character_set_client」から「character_set_connection」へ、「character_set_result」から「character_set_client」へと文字符号化方式を自動変換します。こうすることで、MySQLサーバ内とPHP側で別々な文字符号化方式を使いたいという要求に応えています。

なお、PHP関数「mysql_set_charset」やクエリ「SET CHARACTER SET x;」を実行することで、この3者を適切な文字符号化方式に設定することができます。

なお、クエリ「SET NAMES x;」はこの3者に同じ文字符号化方式を設定するため、場合によっては文字化けする可能性があります。なぜかというと、データベースあるいはテーブルの照合順序や文字符号化方式を考慮しないからです。

MySQLへの接続を行っている関数「sql_connect」

Nucleus CMS 3.41の場合

/nucleus/libs/globalfunctions.phpの512行目から (<add for garble measure>はコメントアウトされ実行されません)

/**

  * Connects to mysql server

  */

function sql_connect() {

	global $MYSQL_HOST, $MYSQL_USER, $MYSQL_PASSWORD, $MYSQL_DATABASE, $MYSQL_CONN;



	$MYSQL_CONN = @mysql_connect($MYSQL_HOST, $MYSQL_USER, $MYSQL_PASSWORD) or startUpError('<p>Could not connect to MySQL database.</p>', 'Connect Error');

	mysql_select_db($MYSQL_DATABASE) or startUpError('<p>Could not select database: ' . mysql_error() . '</p>', 'Connect Error');



/* <add for garble measure>

	$resource = sql_query("show variables LIKE 'character_set_database'");

	$fetchDat = mysql_fetch_assoc($resource);

	$charset  = $fetchDat['Value'];

	$mySqlVer = implode('.', array_map('intval', explode('.', mysql_get_server_info($MYSQL_CONN))));

	if ($mySqlVer >= '5.0.7' && phpversion() >= '5.2.3') {

		mysql_set_charset($charset);

	} else {

		sql_query("SET NAMES " . $charset);

	}

</add for garble measure>*/



	return $MYSQL_CONN;

}

Nucleus CMS 3.51の場合

/nucleus/libs/mysql/mysql.phpの57行目から

    /**
      * Connects to mysql server
      */
    function sql_connect() {
        global $MYSQL_HOST, $MYSQL_USER, $MYSQL_PASSWORD, $MYSQL_DATABASE, $MYSQL_CONN;

        $MYSQL_CONN = @mysql_connect($MYSQL_HOST, $MYSQL_USER, $MYSQL_PASSWORD) or startUpError('<p>Could not connect to MySQL database.</p>', 'Connect Error');
        mysql_select_db($MYSQL_DATABASE) or startUpError('<p>Could not select database: ' . mysql_error() . '</p>', 'Connect Error');

// <add for garble measure>
        $resource = sql_query("show variables LIKE 'character_set_database'");
        $fetchDat = sql_fetch_assoc($resource);
        $charset  = $fetchDat['Value'];
        $mySqlVer = implode('.', array_map('intval', explode('.', sql_get_server_info($MYSQL_CONN))));
        if ($mySqlVer >= '5.0.7' && phpversion() >= '5.2.3') {
            mysql_set_charset($charset);
        } elseif ($mySqlVer >= '4.1.0') {
            sql_query("SET CHARACTER SET " . $charset);
        }
// </add for garble measure>

        return $MYSQL_CONN;
    }

Nucleus CMS 3.64の場合

/nucleus/libs/mysql/mysql.phpの65行目から

	/**
	  * Connects to mysql server
	  */
	function sql_connect() {
		global $MYSQL_HOST, $MYSQL_USER, $MYSQL_PASSWORD, $MYSQL_DATABASE, $MYSQL_CONN;

		$MYSQL_CONN = @mysql_connect($MYSQL_HOST, $MYSQL_USER, $MYSQL_PASSWORD) or startUpError('<p>Could not connect to MySQL database.</p>', 'Connect Error');
		mysql_select_db($MYSQL_DATABASE) or startUpError('<p>Could not select database: ' . mysql_error() . '</p>', 'Connect Error');

// <add for garble measure>
		if (defined(_CHARSET)){
			$charset  = _CHARSET;
		}else{
			$resource = sql_query("show variables LIKE 'character_set_database'");
			$fetchDat = sql_fetch_assoc($resource);
			$charset  = $fetchDat['Value'];
			// in trouble of encoding,uncomment the following line.
			// $charset = "ujis";
			// $charset = "utf8";
		}
		sql_set_charset_jp($charset);
// </add for garble measure>

		return $MYSQL_CONN;
	}

/nucleus/libs/mysql/mysql.phpの348行目から

*******************************************************************/

	/*
	 * for preventing I/O strings from auto-detecting the charactor encodings by MySQL
	 * since 3.62_beta-jp
	 * Jan.20, 2011 by kotorisan and cacher
	 * refering to their conversation below,
	 * http://japan.nucleuscms.org/bb/viewtopic.php?p=26581
	 * 
	 * NOTE: 	shift_jis is only supported for output. Using shift_jis in DB is prohibited.
	 * NOTE:	iso-8859-x,windows-125x if _CHARSET is unset.
	 */
	function sql_set_charset_jp($charset) {
		switch(strtolower($charset)){

			case 'utf-8':
			case 'utf8':

				$charset = 'utf8';

				break;

			case 'euc-jp':
			case 'ujis':

				$charset = 'ujis';

				break;

			case 'gb2312':

				$charset = 'gb2312';

				break;
			/*

			case 'shift_jis':
			case 'sjis':

				$charset = 'sjis';

				break;
			*/

			default:
				$charset = 'latin1';

				break;

		}
		$mySqlVer = implode('.', array_map('intval', explode('.', sql_get_server_info())));
		if (version_compare($mySqlVer, '5.0.7', '>=') && function_exists('mysql_set_charset')) {
			$res = mysql_set_charset($charset);
		} elseif (version_compare($mySqlVer, '4.1.0', '>=')) {
			$res = sql_query("SET CHARACTER SET " . $charset);
		}
		return $res;
	}

インストールスクリプトのMySQL接続確立部分

Nucleus CMS 3.41の場合

/install.phpの565行目から (2-2. はまるまる、3.はcharacter setとcollation設定がコメントアウトで実行されない)

// 2-1. try to log in to mySQL

global $MYSQL_CONN;

$MYSQL_CONN = @mysql_connect($mysql_host, $mysql_user, $mysql_password);



if ($MYSQL_CONN == false) {

	_doError(_ERROR15 . ': ' . mysql_error() );

}



/* <add for garble measure>

// 2-2. set DEFAULT CHARSET and COLLATE

$mySqlVer = implode('.', array_map('intval', explode('.', mysql_get_server_info($MYSQL_CONN))));

if ($mySqlVer >= '5.0.7' && phpversion() >= '5.2.3') {

	mysql_set_charset($charset);

} else {

	mysql_query("SET NAMES " . $charset);

}

$collation = ($charset == 'utf8') ? 'utf8_unicode_ci' : 'ujis_japanese_ci';

</add for garble measure>*/



// 3. try to create database (if needed)

if ($mysql_create == 1) {

	$sql = 'CREATE DATABASE '

		 .     $mysql_database

/* <add for garble measure>

		 . ' DEFAULT CHARACTER SET '

		 .     $charset

		 . ' COLLATE '

		 .     $collation

</add for garble measure>*/

		 . '';

	mysql_query($sql) or _doError(_ERROR16 . ': ' . mysql_error());

}



// 4. try to select database

mysql_select_db($mysql_database) or _doError(_ERROR17);

Nucleus CMS 3.51の場合

/install/index.phpの589行目から (コメントアウトで実行されない箇所はない)

// 2-1. try to log in to mySQL



global $MYSQL_CONN;

// this will need to be changed if we ever allow

$MYSQL_CONN = @sql_connect_args($mysql_host, $mysql_user, $mysql_password);



if ($MYSQL_CONN == false) {

	_doError(_ERROR15 . ': ' . sql_error() );

}



// <add for garble measure>

// 2-2. set DEFAULT CHARSET and COLLATE

$mySqlVer = implode('.', array_map('intval', explode('.', sql_get_server_info($MYSQL_CONN))));

if ($mySqlVer >= '5.0.7' && phpversion() >= '5.2.3') {

	mysql_set_charset($charset);

} elseif ($mySqlVer >= '4.1.0') {

	sql_query("SET NAMES " . $charset);

}

$collation = ($charset == 'utf8') ? 'utf8_unicode_ci' : 'ujis_japanese_ci';

// </add for garble measure>



// 3. try to create database (if needed)

if ($mysql_create == 1) {

	$sql = 'CREATE DATABASE '

		 .	 $mysql_database;

// <add for garble measure>

if ($mySqlVer >= '4.1.0') {

	$sql .= ' DEFAULT CHARACTER SET '

		  .	 $charset

		  . ' COLLATE '

		  .	 $collation;

}

//</add for garble measure>

	sql_query($sql,$MYSQL_CONN) or _doError(_ERROR16 . ': ' . sql_error($MYSQL_CONN));

}

Nucleus CMS 3.64の場合

/install/index.phpの616行目から (コメントアウトで実行されない箇所はない)

// 2. try to log in to mySQL



global $MYSQL_CONN;

// this will need to be changed if we ever allow

$MYSQL_CONN = @sql_connect_args($mysql_host, $mysql_user, $mysql_password);



if ($MYSQL_CONN == false) {

	_doError(_ERROR15 . ': ' . sql_error() );

}



// 3. try to create database (if needed)

$mySqlVer = implode('.', array_map('intval', explode('.', sql_get_server_info())));

$collation = ($charset == 'utf8') ? 'utf8_general_ci' : 'ujis_japanese_ci';

if ($mysql_create == 1) {

	$sql = 'CREATE DATABASE '

		 .	 $mysql_database;

// <add for garble measure>

if (version_compare($mySqlVer, '4.1.0', '>=')) {

	$sql .= ' DEFAULT CHARACTER SET '

		  .	 $charset

		  . ' COLLATE '

		  .	 $collation;

}

// </add for garble measure>

	sql_query($sql,$MYSQL_CONN) or _doError(_ERROR16 . ': ' . sql_error($MYSQL_CONN));

}



// 4. try to select database

sql_select_db($mysql_database,$MYSQL_CONN) or _doError(_ERROR17);



/*

 * 4.5. set character set to this database in MySQL server

 * This processing is added by Nucleus CMS Japanese Package Release Team as of Mar.30, 2011

*/

sql_set_charset_jp($charset);
 
migration.txt · 最終更新: 2011/04/27 13:48 (外部編集)