現役 PHP プログラマーのための Unicode

人気の Web スクリプト言語を使って人類すべての言語を話す

PHP に関する一般的なチュートリアルや参考資料に見られる、Hello World などほとんどすべての例は、限定された形式の英語を「自然言語」での通信に使うことを想定しています。しかし PHP は、それ以上のことができます。適切な方法を使えば、PHP は英語での名前や外来語にときどき現れるアクセント記号付きの文字を処理できるだけではなく、ドイツ語やロシア語、中国語、日本語など、世界中の一般的な言語の大部分を効果的に処理することができます。

Cameron Laird (claird@phaseit.net), Vice President, Phaseit, Inc.

Photo of Cameron LairdCameron Laird は長年 developerWorks への寄稿者であり、以前はコラムニストでした。彼は、彼の雇用者のアプリケーション開発を加速するプロジェクトに関して、信頼性とセキュリティーに焦点を当てた執筆を行っています。彼は 20 年前に、まだ実験的な製品であった AIX を初めて使いました。それ以来、さまざまなメモリー・デバッグ・ツールの熱心な利用者であり、貢献者でもあります。連絡先は claird@phaseit.net です。



2007年 9月 25日

次の小さな PHP プログラムを実行してみてください。

リスト 1. ロシア語の出力をコーディングする
               $q = "Здрав".
                    "ствуй".
                    "те";
               print html_entity_decode($q, ENT_NOQUOTES, 
                    'UTF-8')."\n";

運が良ければ、出力には Здравствуйте (ロシア語で「こんにちは」を意味します) が表示されるはずです。

標準的な英語のアルファベット以外の文字を PHP で処理する作業は、運次第であり、さらには「ミステリー」であることが非常に多いものです。文字エンコーディングや国際化などの話題に関してさまざまなことが書かれていますが、その大部分は間違っているか、あるいは少なくとも古くなっており、それ以外のものも PHP の特定の構成に結びつけられていることが多いものです。この記事の目的は、PHP における Unicode 処理の基本のみを説明することですが、十分に注意しながらひととおり説明することで、皆さんが必要とするすべての「国際化プログラミング」の確固たる基礎を提供したいと思います。

裏ではさまざまなことが行われています

この、一見単純そうな 2 行のプログラムには、大量のコンテキストが関係しています。第 1 に、私は PHP V5 を想定しています。PHP V4 でも英語以外の文字を処理できますが、一般的には非標準の拡張機能が必要であり、2007年の今となってはほぼ確実に無駄な作業となります。一方 PHP V6 は文字エンコーディングに関する多くの問題を解決することが予定されており、この記事で示す方法の大部分は、PHP V6 に取って代わられるはずです。PHP V6 では、Unicode ストリングがそのまま動作することが期待されています。

たとえ最近の標準的な PHP V5 のインストールであっても、ここで示す出力と同じ出力を得られるという保証はありません。私は開発を行っている間に、キリル文字 (Cyrillic) のフォントを利用できないと思われる、ブラウザーがいくつか見つかりました。それらのブラウザーは出力を Здравствуйте と表示せず、代わりにラテン語で Zdravstvujte として表示したのです。


コード・フォーマット

この記事の PHP のソース・コードは、大多数の開発者の環境で動作するように設計されており、標準的な PHP V5 のシステムであれば、できるだけどれにでも適用できるようにしてあります。

本質的なことに焦点を絞るため、ソース・コードからは決まり切った <?php タグと ?> タグを省略して示してあります。また、出力はほとんどの場合、text/plain を対象としています。したがって、リスト 1 はリスト 2 の省略と考えることもできます。

リスト 2. より完全なタグ付けをしてロシア語の出力をコーディングする
             <?php
                   // The next two lines are necessary only for
                   // unusual configurations, but can only help.
               mb_language('uni');
               mb_internal_encoding('UTF-8');

               $q = "&#1047;&#1076;&#1088;&#1072;&#1074;".
                    "&#1089;&#1090;&#1074;&#1091;&#1081;".
                    "&#1090;&#1077;";
               print "<html>".
                     html_entity_decode($q, ENT_NOQUOTES, 'UTF-8').
                     "</html>";
             ?>

また、PHP V5 と最新の標準的なブラウザーに焦点を絞ることで、商用化されている大部分の状況を網羅することができます。ここで説明する方法はほとんどすべて、どのような構成の php.inilocale、フォントの集合などにも適用することができます。

もし、この実験用に信頼できるプラットフォームがあったとすると、それを使って何をやりましょうか? 最も基本的な例としては、次のようなものが含まれます。

  • 英語以外の言語でメッセージ (プロンプトなど) を表示する
  • TEXTAREATEXT INPUT からのユーザー入力を受け付ける
  • 文字データをファイルやデータベースに保存し、またそのデータを取得する
  • 単純なストリング操作

では、これらに関連することを調べてみましょう。


2 つの難題

すぐに問題になることが、いくつかあります。標準的な英語のアルファベットの制限を超えるための適切なソリューション、さらには適切にフォーマットされた英語に時として現れるアクセント記号付きの文字 (「Ramón」や「Gödel」、「apéritif」など) を維持するための適切なソリューションは、UTF-8 としてエンコードされた Unicode です。しかしこのソリューションは、「グリフ」、「コード・ポイント」、「抽象文字」をはじめとし、その他多くの複雑で特殊な定義があるため、Unicode への入門 (「参考文献」を参照) をすませた人にとってさえ、非常に骨の折れる課題です。Unicode を使った開発の大変さは、ネットワーク・プログラミングで一般的な「すべてを自力で構築しなければならない」大変さと同様のものがあります。しかし Unicode を使った開発にはさらなる難題があり、(ネットワーク・プログラミングでは) 実用にかなった結果を得るためにはサーバーとクライアントを動作させる必要がありますが、Unicode に関する効果的なプログラミングを行うためには以下のものが必要になってきます。

  • 日常的に使用するキーボードで入力できる文字範囲をほぼ確実に超えると思われる文字の「入力方法」
  • Unicode データを適切に処理するアプリケーション言語または計算言語
  • 計算された文字を人間が読み取れる形式で表示できる、適切にインストールされたフォントやその他の機能

国際的な作業を大量に行う人であれば、自分が行っている作業を表示できるようにするだけのために、特別なキーボードやエディター、フォントなどに投資しているかもしれません。

Unicode のプログラミングを行う際の 2 番目の難題は、PHP に欠陥があることです。もっと正確に言えば、PHP に欠陥があったことです。PHP は、元々は ASCII 以外のデータを処理するように設計されていなかったのです。PHP V6 はこうした欠陥を修復し、Unicode データをストリングにそのまま埋め込める、Python などの言語のレベルに PHP を高めるはずです。

しかし当面は、PHP での Unicode プログラミングには注意が必要です。Unicode を取り上げている多くのオンライン・フォーラムや Unicode に触れた何冊かの PHP の本は、一般的ではない拡張機能を使った場合のみに有効な助言をしているか、あるいは一部の構成のみで動作するコードを提供しているにすぎません。それが、この記事の最初にリスト 1 を示した理由の 1 つです。html_entity_decode は広くインストールされており、正しくインストールされていますが、頻繁には使われていません。Unicode データを HTML の数値表現として表すと、ソース・コードは不格好になりますが、信頼性が高く、標準の Unicode 表から合成しやすくなります。

同じ出力を、さらにコンパクトに次のようにコーディングすることもできます。

         $r = "Здравствуйте";
         print "$r\n";

しかしこの形式では、ソース・コード自体は 7 ビットどころか 8 ビット「のみ」の構成ですらありません。そのためソース・コードは、多くのエディターや構成管理システム、その他の開発ツールでは、適切に処理されない可能性があります。その結果が、先ほど触れたミステリーの 1 つです。つまり、動作したりしなかったりするように見える、気まぐれなプログラムというわけです。

もう 1 つ、少し考慮する価値のあるバリエーションは、次のようなものです。

          $q = "&#x417;&#x434;&#x440;&#x430;&#x432;".
               "&#x441;&#x442;&#x432;&#x443;&#x439;".
               "&#x442;&#x435;";
          print html_entity_decode($q, ENT_NOQUOTES,
                       'UTF-8')."\n";

この方法は、10 進の整数ではなく 16 進で表現された Unicode の文字テーブルで作業している場合には、リスト 1 の代わりとして貴重な方法です。


PHP の機能

非常に単純な Unicode 操作以上のことをする場合には、私はいくつかのコンビニエンス関数 (リスト 3) に頼ることにしています。リスト 4 はその結果です。

リスト 3. 表示可能な UTF-8 とデバッグ可能な Unicode コードとの間の変換
          function utf8_to_unicode_code($utf8_string)
          {
              $expanded = iconv("UTF-8", "UTF-32", $utf8_string);
              return unpack("L*", $expanded);
          }
          function unicode_code_to_utf8($unicode_list)
          { 
              $result = "";
              foreach($unicode_list as $key => $value) {
                  $one_character = pack("L", $value);
                  $result .= iconv("UTF-32", "UTF-8", $one_character);
              }
              return $result;
          }
      
          $q = "&#1047;&#1076;&#1088;&#1072;&#1074;&#1089;".
               "&#1089;&#1090;&#1074;&#1091;&#1081;".
               "&#1090;&#1077;";
      
          $r = html_entity_decode($q, ENT_NOQUOTES, 'UTF-8');
          $s = utf8_to_unicode_code($r);
          $t = unicode_code_to_utf8($s);
          print "$r\n";
          print_r($s);
          print "$t\n";
リスト 4. リスト 3 を実行した結果
        Здравсствуйте
        Array
        (
            [1] => 65279
            [2] => 1047
            [3] => 1076
            [4] => 1088
            [5] => 1072
            [6] => 1074
            [7] => 1089
            [8] => 1089
            [9] => 1090
            [10] => 1074
            [11] => 1091
            [12] => 1081
            [13] => 1090
            [14] => 1077
        )
        Здравсствуйте

すべてのソース・コードと、(ロシア語のストリングを除いて) 出力されるすべてのものが通常の方法で表示可能であり、実際に 7 ビットの ASCII であることに注目してください。そのため、コピーや E メールでの送信、また通常の開発ツールでの処理も容易に行うことができます。

同じロシア語の単語を出力するための、もう 1 つの方法は下記です。

           $l = array(1047, 1076, 1088, 1072, 1074, 1089, 1089,
                 1090, 1074, 1091, 1081, 1090, 1077);
                 print unicode_code_to_utf8($l)."\n";

データが 1 台のマシン上にある限り、BOM (byte order marker) である最初の整数値 65279 をスキップしても構わないことに注意してください。BOM については Unicode の 1 つの側面として「参考文献」の資料に説明されており、PHP 特有のものではないため、ここではこれ以上の説明は省略します。

これらは基本的な操作であり、経験を積んだ PHP プログラマーにとっては、わかりきった操作です。ただし、これらの操作を明確にしておくことが重要です。これまで PHP に関して書かれていることの大部分は、わかりにくく、移植性がないからです。

これ以外の、PHP での Unicode の扱い方に関して私が調べたところ、どの場合も文字を 1 つの場所から別の場所に移動させるためのエンジンとして適切に PHP を扱っています。キーボードからデータベースや画面に Unicode を渡すための方法が重要なので、PHP 自体の内部でストリングがどう見えるかを検証する必要はありません。

それによってコードは確かに整理され、そして実動アプリケーションの最終形式では HTML そのものあるいは UTF-32 変換は必要ないかもしれません。しかし私は、プログラミングが順調に進まない場合には必ず、こうした低レベルの方法が非常に役に立つことに気付きました。例えば、データベースと XML エディターとがエンコーディングに関して一致しておらず、明白な証拠が「????????」と出力されるエントリーのみ、ということがあるかもしれません。そうした場合には、個々の文字を人間に読み取れるさまざまな表示形式で扱えることは非常に便利です。


プログラミングに関する考慮事項

先ほど触れたように、PHP で Unicode を扱う方法はいくつかあります (PHP の拡張や、さまざまなエンコーディング方法など)。しかし皆さんがエキスパートでない限り、そうした多くの可能性の中のどれかに決めようとすることは避けるようにお勧めします。そうした方法を使わなくても、次に示す 1 つの一貫した目標に集中することで、まず間違いなく最善の結果を実現できるはずです。

  • 明示的に UTF-8 を使い、次のようにします。
    • スクリプトの最上部に "mb_language('uni'); mb_internal_encoding('UTF-8');" を付加します。
    • .htaccess, header() あるいは Web サーバーの構成によって、HTTP ヘッダーに Content-type: text/html; charset=utf-8 を付加します。
    • HTML のマークアップで <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><orm accept-charset = "utf-8"> を付加します。
    • CREATE DATABASE ... DEFAULT CHARACTER SET utf8 COLLATE utf8 ... ENGINE ... CHARSET=utf8 COLLATE=utf8_unicode_ci は MySQL インスタンス用の典型的なシーケンスであり、他のデータベースでも表現は似ています。
    • SET NAMES 'utf8' COLLATE 'utf8_unicode_ci' は PHP にとって貴重なディレクティブであり、接続できれば即座に MySQL に送信することができます。
    • php.inidefault_charset = UTF-8 を割り当てます。
  • strlenstrtlower などのストリング関数を mb_strlenmb_convert_case で置き換えます。
  • mail やその仲間を mb_send_mail などで置き換えます。Unicode 対応の E メールはこの記事の範囲を超えた高度な話題ですが、mb_send_mail を使うことは出発点として適切です。
  • マルチバイトの正規表現関数を使います (「参考文献」を参照)。

私が頻繁に使用する省略関数は、マルチバイトのストリング関数の扱い方を示す、ちょっとした例です。この関数は、元々は次のようなものです。

リスト 5. 従来どおりの切り捨て
    function ell_truncate($string, $permitted_length) {
        if (strlen($string) <= $permitted_length)
            return $string;
        $ellipsis = "...";
        return substr_replace($string, $ellipsis,
                        $permitted_length - strlen($ellipsis));
    }

これを a long explanation に対して長さを 10 として適用すると、結果は a long ... です。しかし長さを 30 に増やすとオリジナルのストリングが返されます。これは、例えば標題を手早く省略したい場合に便利です。

下記は、もっと Unicode らしいソリューションを示しています。

リスト 6. より適切な省略
          function mb_ell_truncate($string, $permitted_length) {
              if (strlen($string) <= $permitted_length)
                  return $string;
              $ellipsis = html_entity_decode("&#x2026;",
                                   ENT_NOQUOTES, 'UTF-8');
              return mb_substr($string, 0,
                              $permitted_length -
                                       mb_strlen($ellipsis)).
                     $ellipsis;
          } 
      
          $q = "&#1047;&#1076;&#1088;&#1072;&#1074;".
               "&#1089;&#1090;&#1074;&#1091;&#1081;".
               "&#1090;&#1077;";
          $q = html_entity_decode($q, ENT_NOQUOTES,
                       'UTF-8');
          print mb_ell_truncate($q, 8)."\n";

これは省略に関する標準的な規則を使っています。そしてストリングの文字を正確にカウントし、PHP の構成をどのように組み合わせた場合にも省略を行います。

これらの事項はどれも、Unicode プログラミングの出発点にすぎません。その他にも、もっと大きな課題が山のように残っています。例えば次のようなものが挙げられます。

  • すべての言語が大文字と小文字の区別をするわけではありません。
  • 多くの言語では「アルファベット順」が無意味です。そのため、ソート順の解釈が英語の場合と異なります。
  • どの言語で作成するかによって、同じ 2 つの文字でもソート順が異なるかもしれません。
  • 複雑になるほどセキュリティーの考慮事項も増大します。「abc」として見えるものが、通常の英語の文字とまったく異なる値でありながら、たまたま同じように出力されているのかもしれません。

Unicode 対応の大部分の計算言語では、こうした問題を共通に持っています。この記事のポイントは、基礎を十分深く理解し、より高度な題目に自信を持って挑戦できるようにすることです。そして、次のことを忘れないでください。もし皆さんが Unicode を処理するために大変な思いをしなければならない、あるいは巧妙なコーディングをしなければならないとしたら、おそらく何かを間違えているのです。PHP V5 と上記のヒントは、皆さんの Unicode プログラミングを簡単にするように考えられているからです。


まとめ

この入門記事を読んだ開発者全員にとって、基本を理解できれば PHP V5 による Unicode プログラミングは手の届くところにあり、ミステリーではなくなるはずです。

参考文献

学ぶために

  • developerWorks には Unicode に関する貴重な記事が何本か公開されていますが、大部分は Java™ プログラミングなどの観点から書かれたものです。「Unicode encodings」は PHP プログラマーにとって貴重な入門記事です。
  • Unicode.org には、Unicode 用語の解説集や、言語体系ごとの Unicode 文字コード表など、数多くの貴重なリソースがあります。
  • A tutorial on character code issues」は、インターネットでの文字の問題に関する貴重で本格的な入門資料です。私はこの資料の著者が執筆した本を推薦しています。
  • Unicode.org には BOM に焦点を当てた FAQ があります。
  • Using Regular Expressions with PHP」は、PHP のさまざまな正規表現ライブラリーと、特にマルチバイト文字を処理できるライブラリーを正しく記述しています。
  • Character Sets / Character Encoding Issues」には、特に Unicode プログラミングがなぜ不適切なものになりがちなのかの説明を含めて、貴重な情報が集められています。ここは PHP プログラマーにとっての Unicode に関するリソースを 1 ヵ所に集めた、最も有用なサイトです。残念なことに、いくつかの部分は誤ったページや古いページ、間違いを含んだコードを示したページなどにリンクされています。
  • PHP マニュアルのPHP マルチバイト・ストリング関数は基本的な参考資料です。
  • PHP.net は PHP 開発者にとっての中心リソースです。
  • Recommended PHP reading list」を調べてみてください。
  • developerWorks には他にも PHP に関する資料が豊富に用意されています。
  • IBM developerWorks の PHP project resources を利用して PHP のスキルを磨いてください。
  • developerWorks podcasts では、ソフトウェア開発者のための興味深いインタビューや議論を聞くことができます。
  • PHP でデータベースを使うのであれば、Zend Core for IBM を調べてみてください。これはシームレスでそのまま使用でき、インストールも容易な、IBM DB2 9 をサポートする PHP の開発環境であり実動環境です。
  • developerWorks Technical events and webcasts で最新情報を入手してください。
  • IBM オープンソース開発者にとって関心のある、世界中で今後開催される会議や業界展示会、ウェブキャスト、その他のイベントについて調べてみてください。
  • developerWorks の Open source ゾーンをご覧ください。オープンソース技術を使った開発や、IBM 製品でオープンソース技術を使用するためのハウ・ツー情報やツール、プロジェクトの更新情報など、豊富な情報が用意されています。
  • IBM のオープンソース技術や製品機能を調べ、学ぶために、無料の developerWorks On demand demos をご覧ください。

製品や技術を入手するために

  • 皆さんの次期オープンソース開発プロジェクトを IBM trial software を使って革新してください。ダウンロード、あるいは DVD で入手することができます。
  • IBM 製品の評価版をダウンロードし、DB2® や Lotus®、Rational®、Tivoli®、WebSphere® などのアプリケーション開発ツールやミドルウェア製品を試してみてください。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source
ArticleID=264310
ArticleTitle=現役 PHP プログラマーのための Unicode
publish-date=09252007