Hatena::ブログ(Diary)

hnwの日記 このページをアンテナに追加 RSSフィード

[プロフィール]
 | 

2012年5月1日(火) PHPのロケールに関するまとめ このエントリーを含むブックマーク このエントリーのブックマークコメント

5/3 17:45追記:t_komuraさんに指摘いただいた関数と、さらに僕が調べ直したものを含め、「ロケール設定に従う関数一覧」に25個ほど追加しました。かなり見落としがありましたね…。


PHPロケール*1まわりについて調査したので、これをまとめてみます。


この記事は「ロケールの影響を受ける関数 - Sarabande.jp」を掘り下げたものです。masakielasticさん、ナイスな記事をありがとうございます。


PHP文字列型と文字エンコーディング

他のモダンLL言語と異なり、PHP文字列の文字エンコーディングに関して何も仮定せず、単なるバイト列として管理しています。つまり、文字エンコーディングの取り扱いは各関数の実装に委ねられています。


下記の通り、これはマニュアルにも記述があるのですが、実に残念なことです。


残念ながら、PHP の各関数文字列エンコーディングを判断する方法はまったく統一されていません。


文字列型の詳細 - PHP: 文字列 - Manual


とはいえ、引用元の文章では各関数と文字エンコーディングの関係を下記のように大別しています。



これを見ると、それぞれの関数出自によって挙動が変わっているようです。PHP本体の実装として、OSの機能に頼ったり他所から持ってきたライブラリに頼ったりと実装がバラバラであることが混乱の一因だといえるでしょう。


本稿では、3番目の分類にある「ロケール」について詳しく見ていきます。


ロケールにまつわるトラブル

ロケールとは、OSが持っている多言語対応の仕組みです。たとえばヨーロッパの国々では異なる文字コードを利用していますが、特定の1バイトがアルファベットか記号かをロケール設定に応じて判定する機能(ライブラリ関数)をOSが提供しています。PHPの一部関数でも、こうしたロケール関連のライブラリ関数を利用しているというわけです。


ところで、ロケールOSが提供しているわけですから、環境依存ということになります。実際、ロケール情報の差やパッケージのインストール状況によって挙動が変わってしまうことがあるのです。


特にDebian系のLinuxディストリビューションではロケールが別パッケージになっていることもあり、ロケールなしで動作していることがあります。また、特にShift_JISについてはロケール情報がそもそも提供されていなかったり、誤りを含んでいたりすることがあります。


そんなこんなで、ロケールまわりのトラブルは珍しくありません。複数環境で使うようなプログラムであれば、ロケールを明示的に設定し、設定に成功したかを確認すべきです。もしくは、ロケールに依存する関数を避けることも検討事項になるでしょう。


<?php
if (setlocale(LC_ALL, 'ja_JP.UTF-8') === false) {
    error_log('Locale not found: ja_JP.UTF-8');
    exit(1);
}
//本来の処理

ロケール設定に従う関数一覧

masakielasticさんの指摘の通り、どのPHP関数ロケール設定に従うのかPHPマニュアルでは網羅されていません。今回、これを独自に調べてみました。


下記のリストはPHPのCソースコードgrepするなどして怪しいPHP関数を抜き出したものです。過不足があるかもしれませんので、お気づきの方はお知らせください。


1バイト文字について大文字小文字などの判定・変換や、大小比較をするもの

toupper(3)などctype.hが提供するライブラリ関数を利用しているPHP関数群です。UTF-8文字列に対してCロケールまたはUTF-8ロケールで利用する限り、特にトラブルは無いはずです。一方で、誤ってISO8859系のロケールが設定されていると意味不明なバグに悩まされそうです。



その他の国別の表記をするもの

その他のロケール情報を利用する関数群です。ヨーロッパ圏では数値や日時の表記が国ごとに異なるため、こうした関数が重要になるのでしょう。


下記関数のうち、strftime関数を使って曜日を漢字表記できる環境があります。これを愛用している人がいるかもしれませんが、全ての環境で利用できるものではありませんので注意してください。



1文字が何バイトかの判定をするもの

mblen(3)を利用してマルチバイト文字の範囲を特定している関数/クラスメソッド/ストリームです。セキュリティクラスタの人がワクワクするような項目が並んでいますが、本稿ではこれ以上の深追いはしません。



まとめ


ロケール周りはCプログラマなら鬼門の一つとして印象が強い場所だと思うんですが、Cを知らないPHPプログラマからすると何のこっちゃ的な話題だと思うんですよね。そのくせ説明不足という酷い状況だったわけですから、今回のまとめは有意義なんじゃないかと思っています。


参考リンク


最初のリンク先はドイツ語PHP教科書で、strtoupper関数でöをÖにしています。実際、こういうニーズはあるんでしょうね。とはいえ、ISO8859-1ならこれで動いていたのがUTF-8にしたら動かなくなってしまうわけですから、ヨーロッパの人も大変そうです。


2番目のリンク先は、内部エンコーディングUTF-8にしているのにbasename関数で日本語を含むファイル名の処理に失敗するという指摘です。おそらくデフォルトロケール指定が誤っているだけで、setlocaleすれば期待通りに動く気がします。PHP6で何とかするという返答がありますが、この混乱をなんとかしたいという意味でしょう。

*1ロカールの方が本来の発音に近いらしいと聞きましたが、10年以上もロケールと呼んでいるので僕個人はこのまま押し通す気でいます

t_komurat_komura 2012/05/03 00:11 ロケールの影響を受ける関数は多いので厳密に調べるのは大変ですね。参考までに少し調べてみました。
dirname, array_unique(SORT_LOCALE_STRING を使用した場合), gmstrftime もロケールの影響を受けるようです。
また、mblen を使用する php_basename を内部で使用している関数は多いです。tempnam(第2引数(prefix)に使用), exif_read_data, exif_thumbnail, mail(php.ini で mail.add_x_header を有効にした場合), mb_send_mail(mail と同様), error_log(第2引数(message_type) に 1(メール送信)を設定した場合)などがありました。
あと、mail と mb_send_mail は、第5引数(additional_parameter)または、php.ini に mail.force_extra_parameters を設定した場合、内部で php_escape_shell_cmd(escapeshellcmd) を使用しています。escapeshellcmd は問題があるので、additional_parameter に外部からの入力を使用していると危険な気がします。

hnwhnw 2012/05/03 17:51 t_komuraさん、ありがとうございます。array_uniqueなど色々と見逃しがあったので、追記させて頂きました。

一方で、このリストに何を入れるべきかは難しい問題だと思っています。網羅するということなら少しでも関係するものはリストアップしたい気がするんですが、無害なものを書くと混乱を招きそうで、悩んでいたところでした。

たとえばdirnameについてはWindowsのドライブ名をisalphaで見ているということだと思いますが、トラブルの原因にはなりにくいように思っています。また、toupper,tolowerが使われているところも大量にあるなど、全部書き出すとキリが無いような気がしたので、僕が問題を再現できたものだけリストアップしていました。

とはいえ、mblenについてはまずい応用がありそうな気がするので、少なくともここだけは安全側に倒すことにしました。

この調査には一定の時間がかかったことと思います。詳細なご指摘ありがとうございました。

 | 
ページビュー
1882516