PR

 今回は,不具合修正のために緊急リリースされた PHP 4.3系の最新版PHP 4.3.6およびPHP5のリリース候補版PHP5.0.0RC2,そして,その他のPHP関連の情報に関して解説する。

PHP 4.3.5の不具合を修正したPHP 4.3.6がリリース

 3月26日にPHP 4.3.5がリリースされた後,リリース直後に重大な不具合がみつかり,これを修正したPHP 4.3.6が4月15日に緊急リリースされた。具体的な修正点には,Win32環境などで用いられるマルチスレッド対応モード(ZTSモード)においてPHPがクラッシュするというバグ,mktime() およびstrtotime()関数における夏時間対応のバグなど,比較的重要な修正が含まれている。その他のものも含めて合計25件のバグが修正されている。

また,PHPにバンドルされている以下のライブラリが更新されている。

  • イメージライブラリ GD (バージョン2.0.22)
  • PDF生成ライブラリPDFlib (バージョン5.0.3p1に更新,Win32版のみ)

     特にWin32環境などでZTSモードを使用しているPHP 4.3系のユーザーは早期にPHP 4.3.6へバージョンアップすることが推奨される。なお,変更点に関する詳細は,「PHP 4.3.6 Release Announcement」(http://www.php.net/release_4_3_6.php)を参考にしてほしい。
    開発の中心は既にPHP5に移行しており,PHP 4.3系での新たな機能追加は行われないこととなっているが,今後もバグ修正作業は行われる予定である。

    PHP 5.0.0RC2がリリース

     1~2カ月以内に予定されるPHP 5の正式リリースに向けて開発が進んでいる。4月25日にPHP 5.0..0RC2(リリース候補2版)がリリースされた。このバージョンでは,23件のバグ修正が行われ,PHP4互換モードの整備なども進んでいる。RC2における変更点を以下に紹介する。なお,PHP 5.0.0RC1からの変更点の詳細については,
    ChangeLog(http://www.php.net/ChangeLog-5.php#5.0.0RC2)を参照されたい。

    Zend Engine 1 互換モードの整備と課題

     PHP4からPHP5への移行を検討するにあたり,最も気になるのが互換性,すなわち,従来使用していたアプリケーションがPHP5でも動作するかという問題である。

     PHP5に搭載されるスクリプトエンジンZend Engine 2(ZE2)ではオブジェクト機能の大幅拡張に伴い,PHP4に搭載されているZend Engine 1(ZE1)で動作していた従来のコードとの互換性が一部失われる可能性がある。PHP4からPHP5への移行を容易にするために容易されているのが,前回も紹介したZE1互換モードである。このモードは,php.iniで以下のオプション設定を行うことで有効となる。

    zend.ze1_compatibility_mode = On

    RC2ではこのZE1互換モードの実装が見直され,ZE1との互換性がより高くなった。ZE2とZE1の間では,オブジェクト機能に関して以下の3項目の動作が異なっている(表1)。

    表1●Zend Engine 1とZend Engine 2のオブジェクト機能に関する動作の違い

      PHP4 PHP5
    1.オブジェクトのコピー ディープ・コピー(クローンを自動生成)がデフォルト。シャロー・コピーを行う場合は,'&'演算子を使用 シャロー・コピー(リファレンスをコピー)がデフォルト。ディープ・コピーを行う場合は,__clone()メソッドを使用する
    2.オブジェクトの整数(int)へのキャスト プロパティの有無によりそれぞれ1,0となる 通知(NOTICE)を発生し,常に1となる
    3.オブジェクトの比較 同一プロパティを有する同じクラスのインスタンスは等しい 同じオブジェクトIDを有するオブジェクトのみ等しい(ただし,まだ,バグがある。)

     上記1について,具体例を示すために以下の簡単なコードを見てみよう。

      class Foo {
      var $v = 1;
      }
     $a = new Foo();
     $b = $a; // オブジェクトのコピー
     $b->v = 2; // コピー先のプロパティの値を変更
     echo $a->v; // コピー元のプロパティの値を表示
     ?>

    出力は以下のようになる。

     PHP4.3.6およびPHP5RC2(ZE1互換モード有効)の場合:1
     PHP5RC2(ZE1互換モード無効)の場合:2

     PHP5ではコピー先とコピー元のオブジェクトの実体は同じであるため,コピー先のオブジェクトのプロパティを変更すると,コピー元のプロパティも変更される。ZE1互換モードが有効な場合は,PHP4の動作と同じなる。

    上記2のオブジェクトのキャストに関して,文字列(string)へのキャストについてはObject(PHP4の場合),Object id #id(PHP5の場合)となるためほぼ同一だが,整数(int)へのキャストの場合は動作が異なる。以下のコードを見てみよう。

    class Foo {
     function setName($name) {
      $this->name = $name;
     }
    }
    $a = new Foo;
    $a->setName('Taro');
    $b = new Foo;
    echo (int)$a,",";
    echo (int)$b;
    ?>

     出力は以下のようになる。

     PHP4.3.6およびPHP5RC2(ZE1互換モード有効)の場合:1,0
     PHP5RC2(ZE1互換モード無効)の場合:1,1

     なお,PHP 5.0.0RC2では,上記のように整数のキャストを行うと常に1が返されるが,最新の開発版ではオブジェクトIDが整数値として返されるようである。この辺りの仕様はリリースまでにさらに変更される可能性がある。

     オブジェクトのキャストをアプリケーションで行う例はあまりないかと思われるが,既存のPHP4用スクリプトにおいて,プロパティの有無を調べるために整数へのキャストを用いていた場合には,コードの変更が必要となる可能性がある。

     次に3番目のオブジェクトの比較について説明する。オブジェクトの比較において,PHP4ではオブジェクトの型とプロパティが比較されていたが,PHP5ではオブジェクトのハンドルが比較されるようになる。以下のコードを見てみよう。

    class Foo {
     var $v = 1;
    }
    class Boo {
     var $v = 1;
    }
    $a = new Foo;
    $b = new Foo;
    $c = new Boo;
    $d = new Foo;
    $d->v = 2;
    $e = $a;
    echo $a == $b ? "same," : "not same,";
    echo $a == $c ? "same," : "not same,";
    echo $a == $d ? "same," : "not same,";
    echo $a == $e ? "same" : "not same";
    echo "\n";
    echo $a === $b ? "same," : "not same,";
    echo $a === $c ? "same," : "not same,";
    echo $a === $d ? "same," : "not same,";
    echo $a === $e ? "same" : "not same";
    ?>

     上のコードにおいて,'=='は値を比較する比較演算子であり,必要に応じて自動的にキャストが行われる。一方,'==='は型と値が等しい場合にみ等しいと判定される比較演算子である。出力は以下のようになる。

    PHP4.3.6の場合:
      same,not same,not same,same
      same,not same,not same,same 

    PHP5(ZE1互換モード無効)の場合:
      same,not same,not same,same
      not same,not same,not same,same

    PHP5(ZE1互換モード有効)の場合:
      same,same,not same,same
      not same,not same,not same,not same

     この場合は,ZE1互換モードとPHP4の動作は異なっている。ZE1互換モードにおいて異なるクラスのオブジェクト(FooとBoo)を等しいと判定しているのは,バグであると思われる。また, '==='演算子により型と値を比較したケースでは,ZE1互換モード無効の場合はPHP5本来の仕様に合致しているものの,ZE1互換モード有効の場合の動作はPHP4の動作ともPHP5の動作とも異なっている。

     なお,PHP5.0.0RC2における以上の問題についてはすでに開発者に報告済みであり,5月11日現在の最新の開発版(CVSから入手可能)では修正済みである。

     この他にも若干の動作の違いが存在するが,以上の3点はオブジェクトの代入・キャスト・比較といった基本操作に関連するため,特に互換性の問題を発生しやすいと言えるだろう。また,ZE1互換モードは,まだ,PHP4の動作と非互換の部分があるが,まだ実装中であるため,正式リリースまでにはPHP4の動作と同じとなるように互換性が向上すると思われる。

    新しいエラー・レベルE_STRICTによる互換性の確認

     既存のスクリプトがZE2対応のコードであるかどうかを確認するためには,PHP5から新たに追加された新しいエラー・レベルE_STRICTを使うと良いだろう。具体的には,設定ファイルphp.iniのerror_reporting命令に以下のようにE_STRICTを追加することでE_STRICT警告が有効となる。

    error_reporting = E_ALL | E_STRICT

     E_STRICTを有効にすると,PHP4の古い記法に基づくコードに対してパース時に警告が発生される。例えば,プロパティ変数の宣言においてpublicの代わりにPHP4の古い記法であるvarを使用した場合には,以下のようなE_STRICT警告が発生する。

    Strict Standards: var: Deprecated. Please use the public/private/protected modifiers in test.php on line 6

     なお,従来はE_ALLを指定するとE_NOTICEを含むすべての警告・エラーが有効となっていたが,E_STRICTはE_ALLには含まれておらず上記のようにerror_reportingにE_ALLとは別に指定する必要があるので注意してほしい。

    オブジェクトAPIの命名規則が変更に

     その他の変更点として,オブジェクトAPIの命名規則に関するものがある。

     PHPにおける関数名およびオブジェクトのプロパティ・メソッドの命名の流儀としては,studlyCapsまたはアンダースコア法が用いられている。studlyCapsというのは,1文字目をのぞき関数・変数名を構成する各単語の先頭文字を大文字とする記法である。(ここでStudlyCapsと書いた場合は,一文字目も大文字となる。)

     一方,アンダースコア法は,各単語の間をアンダースコアで区切る記法である。以下に例を示す。

    $obj->showName();  // studlyCaps
    $obj->show_name();  // アンダースコア法

     以前からどちらの命名記法に統一するべく議論が行われてきたが,これまで明確な結論はでていなかった。PHP5では,オブジェクトAPIについては,studlyCapsが標準的に採用されることになった。ただし,拡張モジュールの作者の判断でアンダースコア法を用いることも可能とされている。

     この原則に基づいて,SimpleXML,SQLite,SOAP,Reflection API,Mingなどの各拡張モジュールにおけるオブジェクトAPIは,studlyCapsによる実装により作成または変更されている。ただし,MySQLiモジュールについては,作者の判断でアンダースコア法のままとなっている。どちらかの命名規則に明確な優位性があるとは考えられないが,命名規則に関しては統一される方がユーザーにとって望ましいと言える。

     また,オブジェクトAPIではない通常の関数APIについては,PHP4の関数APIの標準的な命名記法であるアンダースコア法が引き続き標準的に用いられている。

    オブジェクトと関数,デュアルAPIのサポート

     PHP5で新たに追加された拡張モジュールの多くは,ユーザーの利便性をはかるためオブジェクトAPIと関数APIの2種類のAPI(デュアルAPI)がサポートされている。この2種類のAPIはユーザーの好みや目的に応じて使い分けることができ,ほとんどの場合,同じことを両方のAPIで行うことができる。例えば,HTMLの構文検証を行うtidyモジュールのコードを見てみよう。

    関数APIで書く場合:
    $tidy = tidy_parse_file('example.html');
    echo tidy_get_contents($tidy);
    ?>

    オブジェクトAPIで書く場合:
    $tidy = new tidy();
    $tidy->parseFile('example.html');
    echo $tidy->value;
    ?>

     関数やメソッドの命名記法については,前記のようにオブジェクトAPIはstudlyCaps,関数APIはアンダースコア法に基づいている。

     PHPでは,関数を用いる手続き型とクラスを用いるオブジェクト指向型の2種類の開発形態を用いることができる。PHP4では,オブジェクトAPIの実装が困難で,ほとんどの拡張モジュールではサポートされていなかった。PHP5ではオブジェクトAPIが強化され,拡張モジュールにおける実装も容易となったため,上記のようなデュアルAPIをサポートする拡張モジュールが増えている。

     大規模なアプリケーションに適したオブジェクト指向の開発スタイルと手続き型のシンプルな開発スタイルはともに広く用いられており,両方のAPIがともにサポートされることはユーザーの選択の幅を広げる意味で有意義であろう。

    インターフェイス・抽象クラスと関数プロトタイプチェック

     PHP5では,抽象クラスとインターフェイスがサポートされる。PHPは多重継承をサポートしないが,インターフェイスにより複数の型を持つことが可能となる。

     抽象クラスとは,通常のクラスと異なり複数の異なるクラスの共通仕様を定めるために設定される抽象的なクラスで,サブクラス化して使うことが前提となる。また,インターフェイスはそれ自体はメソッドなどの実装を有さず,実装するべきメソッドの仕様のみを規定するものである。PHP5におけるインターフェイス・抽象クラスの構文はJavaとほぼ同じため,容易に理解できるであろう。簡単な例を以下に示す。

    interface MBText {
     public abstract function convert($src,$dst);
    }

    abstract class News {
     abstract function show($content);
     function load($content) {}
    }

    class MyNews extends News implements MBText {
     function convert($src,$dst) {
     }
     function show($name) {
     }
     function load($filename) {
     }
    }

    $obj = new MyNews();
    ?>

     このスクリプトでは,インターフェイスMBTextと抽象クラスNewsを宣言している。また,MyNewsクラスをNewsのサブクラスとして作成する際にimplements構文でMBTextの仕様を取り込んでいる。結果として,MyNewsクラスは,NewsクラスとMBTextインターフェイスの両方の特定を受け継ぐことになる。

     ZE2では,ZE1とは異なりメソッドのプロトタイプ宣言が親クラスと同じかどうかチェックされるようになっている。これに関連して,PHP 5.0.0RC2では,interface/abstractメソッドでプロトタイプ宣言に誤りがある場合は,致命的なエラーを発生するようになった。一方,通常のメソッドの場合は,従来のコードとの互換性を考慮して致命的なエラーではなくE_STRICT警告が発生する。

     例えば,上のスクリプトのMyNewsクラスにおいて仮に以下のように型が異なっていたとしよう。

    class MyNews extends News implements MBText {
     function convert($src) {
     }
     function show() {
     }
     function load() {
     }
    }

    このスクリプトを実行すると,抽象メソッドconvert(),show()に関してプロトタイプ宣言との整合性がないという致命的エラーを発生する。また,通常のメソッドload()についても同様のE_STRICT警告を発生する。以下にエラーメッセージの例を示す。

    Fatal error: Declaration of MyNews::convert() must be compatible with that of MBText::convert() in test.php on line 11

    Fatal error: Declaration of MyNews::show() must be compatible with that of News::show() in test.php on line 11

    Strict Standards: Declaration of MyNews::load() should be compatible with that of News::load() in test.php on line 11

     このように,PHP 5.0.0RC2では,インターフェイス・抽象クラスにおける型チェックが厳しくなったわけであるが,オブジェクト指向開発においてコードの再利用性を高めるために有意義な変更であると言える。

    その他のPHP関連の話題:PECLの新モジュール

      その他のPHP関連の話題として,PHP拡張モジュールの標準レポジトリPECL(http://pecl.php.net/)で最近公開されたモジュールの中から有用と思われるものを二点ほど紹介しよう。

    xmlReaderモジュールβ版公開

    まず,5月4日にxmlReaderというモジュールを紹介する。xmlReaderはβ版が公開されたばかりでバージョンも0.1であるが,XMLデータを処理する際に有用なモジュールである。xmlReaderはhttp://pecl.php.net/package-info.php?package=xmlReaderから入手できる。xmlreader-0.1.tgzを入手した後は,以下のようにpearコマンドを使ってインストールできる。

    # pear install xmlreader-0.1.tgz

     xmlReaderは,libxml2のxmlReader APIを実装したモジュールである。XMLデータの処理には,通常DOMインターフェイスが用いられるが,DOMは処理する際にXMLデータ全体を読み込むため,大量のデータの処理には向かないとされている。大量のデータの処理に向くAPIとしてイベントコールバック関数により処理を行うSAXインターフェイスが使用可能であるが,SAXは低レベルのインターフェイスしかサポートされていないという問題がある。

     ここで,最近注目されているのがC#のXmlTextReader API で実装されたPullパーサと呼ばれる仕組みである。このPullパーサとして実装されたxmlReader APIではカーソルという仕組みを導入し,一般のファイルと同様にシーケンシャルなデータ処理を行うことができる。XMLファイルの内容を表示する簡単なスクリプトを以下に示す。

    $xml = new XMLReader();
    $xml->open('example.xml');
    while ($xml->read()) {
     switch ($xml->nodeType) {
      case XMLREADER_ELEMENT:
      print "\n" . str_repeat("\t",$xml->depth);
      print $xml->localName . "\t";
      if ($xml->hasAttributes) {
       $attr = $xml->moveToFirstAttribute();
       while ($attr) {
    print "{$xml->name} -> {$xml->value},";
    $attr = $xml->moveToNextAttribute();
       }
      }
      break;
      case XMLREADER_TEXT:
      print $xml->value;
      break;
     }
    }
    ?>

     xmlReaderは,Relax NG,XML SchemaスキーマによるXMLデータ検証にも対応するなど高機能である。xmlReaderはまだ開発途上ではあるが,DOM,SAX,SimpleXMLに次ぐ第4のXMLインターフェイスとして注目される。

    idnモジュールβ版公開

     idnモジュールは,国際化ドメイン名(Internationalized Domain Name; IDN)に関する処理を行うライブラリlibidn(http://www.gnu.org/software/libidn/)へのインターフェイスを提供する拡張モジュールであり,4月26日にβ版(バージョン0.1)が公開された。国際化ドメイン名により漢字などのマルチバイト文字がドメイン名で使用できるようになる。idnモジュールは,国際化ドメイン名で使用されるアスキー互換表現形式の符号Punycode(RFC3492)とUTF-8の相互変換をサポートする。

    インストールするには,http://pecl.php.net/package-info.php?package=idnからidn-0.1.tgzをダウンロード・展開し,xlmReaderと同様にpearコマンドを用いる。なお,あらかじめlibidnがインストールされていることが必要である。

     以下に簡単な使用例を示す。

    $in = 'www.日本語.com'; // utf-8
    $idn = idn_to_ascii($in); // utf8 -> punicode
    print $in . "\n";
    print $idn . "\n"; // 出力:www.xn--wgv71a119e.com
    print idn_to_utf8($idn); // punicode -> utf8
    ?>

     国際化ドメインに関する同様な機能を提供するPHPモジュールは他にも存在するが,PECLに登録されたことでユーザーにとって利用しやすくなったと言える。

     今回は,PHP4およびPHP5の開発状況について紹介した。次回以降もPHPに関する最新の情報をお届けする予定である。

    廣川類(Rui Hirokawa)


    ■著者紹介 廣川類(ひろかわ・るい)氏
     PHPがまだPHP/FIと呼ばれていた1996年にPHPに出会い,以降,ドキュメント翻訳や国際化にかかわっている。著書に『PHP4徹底攻略』(ソフトバンクパブリッシング),『PHP4徹底攻略実戦編』(ソフトバンクパブリッシング)などがある。日本PHPユーザー会(2000年4月設立)ドキュメント部門幹事。