PHP 5.1 へのアップグレード

  1. 参照に関する処理の変更
    1. 概要
    2. PHP 4.3 以前では動作するものの、現在は動作しないコード
    3. PHP 4.3 以前では正当なコードであったが、現在はエラーとなるもの
    4. PHP 4.3 以前では動作しないが、現在は動作するコード
    5. PHP 5.0 で「正常に動作すべきだった」コード
    6. 現れて、そしていなくなった警告
  2. [] の読み込み
  3. instanceof, is_a(), is_subclass_of(), catch
  4. 関数のパラメータとしての整数値
  5. private な抽象メソッド
  6. インターフェースでのアクセス権の定義
  7. 継承規則の変更
  8. クラス定数
  9. 拡張モジュール
    1. PHP コアから削除された拡張モジュール
    2. PHP 5.1 での新しい拡張モジュールのクラス定数
  10. 日付/時刻のサポート
  11. データベースサポートの変更
    1. PDO 概要
    2. MySQL サポートの変更
    3. SQLite サポートの変更
  12. 移行のためのより詳細な情報
  13. E_STRICT エラーのチェック

1. 参照に関する処理の変更

1a. 概要

PHP スクリプトの開発者としての観点から見ると、既存のコードに与える影響がもっとも大きいのは、PHP 4.4.0 以降での参照に関する扱いの変更です。

PHP 4.3 を含むそれ以前のバージョンでは、本来は値を返すべき場所、たとえば定数・一時的な値 (例: 式の結果)・値を返す関数の結果などで参照を使用することが可能でした。以下がその例です。

<?php

$foo
= "123";

function
return_value() {
    global
$foo;
    return
$foo;
}

$bar = &return_value();

?>

このコードは PHP 4.3 以下では通常通り動作しますが、一般的に結果は未定義となります。Zend エンジンはこれらの値を参照として正しく扱えません。このバグにより、再現不可能な問題が数多く発生していました。特に大規模なコードになるほどこの問題は深刻でした。

PHP 4.4.0、PHP 5.0.4 以降のリリースでは Zend エンジンが修正され、参照を使用すべきでない場面で参照の操作が行われた場合はそれを「知る」ことができるようになりました。今ではそのような場合には実際の値が使用されるようになり、警告を発生するようになりました。この警告の形式は、PHP 4.4.0 以降では E_NOTICE・PHP 5.0.4 以降では E_STRICT となります。

これまでメモリ破壊を起こす可能性のあったコードは、もはやその心配はなくなりました。しかし、既存のコードの中にはこれまでと挙動が変わってしまうものもあります。

1b. PHP 4.3 以前では動作するものの、現在は動作しないコード


<?php

function func(&$arraykey) {
    return
$arraykey; // 関数は値を返します!
}

$array = array('a', 'b', 'c');
foreach (
array_keys($array) as $key) {
    
$y = &func($array[$key]);
    
$z[] =& $y;
}

var_dump($z);

?>

参照に関する修正が行われる前の PHP で上のスクリプトを実行すると、このような結果になります。


array(3) {
  [0]=>
  &string(1) "a"
  [1]=>
  &string(1) "b"
  [2]=>
  &string(1) "c"
}

修正後は、同じコードの結果が次のようになります。


array(3) {
  [0]=>
  &string(1) "c"
  [1]=>
  &string(1) "c"
  [2]=>
  &string(1) "c"
}

理由は、今回の変更によって func() の結果が値として代入されるようになったことです。$y の値は代入しなおされ、そして、$z では $y への参照が残り続けます。この修正が行われる前は、func() の結果が参照として代入され、 leading $y to be re-bound on each assignment. 一時的な変数を参照で渡そうとすることが、メモリ破壊の原因となっていました。

修正前のバージョンの PHP と修正後のバージョンの PHP とで、このようなコードの挙動を同じにすることは可能です。func() の定義を変更して参照を返すようにするか、あるいは func() が返す結果を参照として代入する処理を取り除けばよいのです。


<?php

function func() {
    return
'関数の返す値';
}

$x = 'もとの値';
$y =& $x;
$y = &func();
echo
$x;

?>

PHP 4.3 では $x は 'もとの値' となりますが、この変更以降のバージョンでは '関数の返す値' となります。関数は参照を返さず、値が直接代入されることを覚えておきましょう。もう一度いいます。このような挙動の違いをなくすには、func() が参照を返すようにするか、関数の返す値を直接参照として代入することをやめるかのどちらかを行います。

1c. PHP 4.3 以前では正当なコードであったが、現在はエラーとなるもの


<?php

class Foo {

    function
getThis() {
        return
$this;
    }

    function
destroyThis() {
        
$baz =& $this->getThis();
    }
}

$bar = new Foo();
$bar->destroyThis();
var_dump($bar);

?>

PHP 5.0.3 では、$bar はオブジェクトを返さずに NULL と評価されます。なぜなら、getThis() が値を返しているにもかかわらず、それを参照として代入しているからです。現在ではこれは期待通りに動作しますが、実際のところこれは不正なコードです。PHP 4.4 では E_NOTICE、PHP 5.0.4 以降では E_STRICT をスローします。

1d. PHP 4.3 以前では動作しないが、現在は動作するコード


<?php

function &f() {
    
$x = "foo";
    
var_dump($x);
    print
"$x\n";
    return(
$a);
}

for (
$i = 0; $i < 3; $i++) {
    
$h = &f();
}

?>

PHP 4.3 では var_dump を 3 度目にコールした際に NULL を返し、初期化されていない値への参照を返すことでメモリの破壊を引き起こしてしまいます。このコードは PHP 5.0.4 以降では動作しますが、それより前のバージョンの PHP ではエラーとなります。


<?php

$arr
= array('a1' => array('alfa' => 'ok'));
$arr =& $arr['a1'];
echo
'-'.$arr['alfa']."-\n";

?>

PHP 5.0.5 より前のバージョンでは、このようにして配列の要素に参照を代入することができませんでした。現在はできるようになっています。

1e. PHP 5.0 で「正常に動作すべきだった」コード

参照関連の修正が行われるより前の PHP 5.0 ではいくつかのバグが報告されていましたが、それらは今では正常に「動作します」。しかし、いずれの場合についても PHP 5.1 ではエラーがスローされます。なぜなら、まず第一にそのコードが不正なものだからです。現在では self:: を使用して値を参照で返すことができるようになりましたが、E_STRICT レベルの警告をスローします。また、オーバーロードされたオブジェクトに参照を代入しようとすると、たとえ代入自体は正常に動作していても E_ERROR に遭遇することになるでしょう。

1f. 現れて、そしていなくなった警告

<?php

function & foo() {
    
$var = 'ok';
    return
$var;
}

function &
bar() {
    return
foo();
}

$a =& bar();
echo
"$a\n";

?>

参照を返す関数をネストしてコールすることは、PHP 4.3 および PHP 5.1 のどちらでも有効です。しかし、それらの間のバージョンの PHP では、E_NOTICE あるいは E_STRICT が間違って発生してしまいます。

2. [] の読み込み


<?php

class XmlTest {

    function
test_ref(&$test) {
        
$test = "ok";
    }

    function
test($test) { }

    function
run() {
        
$ar = array();
        
$this->test_ref($ar[]);
        
var_dump($ar);
        
$this->test($ar[]);
    }
}

$o = new XmlTest();
$o->run();

?>

これは常に E_ERROR レベルの致命的なエラーを発生します。なぜなら、PHP では [] を読み込みに使用できないからです。PHP 4.4.2 および PHP 5.0.5 以降で、これは不正なコードとなりました。

3. instanceof, is_a(), is_subclass_of(), catch

PHP 5.0 で is_a() は非推奨となり、"instanceof" 演算子に置き換えられました。"instanceof" の初期の実装にはいくつかの問題があり、クラスを探すために __autoload() に頼っていました。もしクラスが存在しなかった場合、__autoload() に失敗するために "instanceof" は致命的な E_ERROR をスローしていました。同じ理由で、"catch" 演算子や is_subclass_of() 関数でも同様の現象が発生していました。

PHP 5.1 では、これらの関数や演算子は __autoload() をコールしません。また、PHP 5.0 ではこの問題の回避策として class_exists() が使用可能です。大きな問題ではありませんが、この回避策はもはや必要ありません。

4. 関数のパラメータとしての整数値

PHP 5.0 の登場に伴い、パラメータのパース用の新しい API が公開されました。多くの PHP 関数でこの API が使用されています。PHP 5.0 から 5.1 までの間のすべてのバージョンではこの API での整数の処理が非常に厳しいものとなっており、パラメータとして整数を受け付けている PHP 関数に不正な値を渡せないようになっていました。このチェックは現在ではより緩やかなものとなっており、" 123" や "123 " といった不正な値でも受け付けるように変更されています。これは PHP 5.0 でも同様です。しかし、コードの安全性の確保や入力内容の検証を促進するため、そのような文字列が整数として渡された際には PHP 関数は E_NOTICE を発行します。

5. private な抽象メソッド

PHP 5.0.0 から PHP 5.0.4 まででは private な抽象メソッドがサポートされていましたが、禁止されました。なぜなら「private」と「抽象メソッド」とは決して両立することのない概念だからです。

6. インターフェースでのアクセス権の定義

PHP 5.0 では、インターフェース内での関数定義はクラス内での関数定義と同じように扱われました。2004 年 10 月以降はそうではなくなり、インターフェース内での関数定義では「public」以外のアクセス権を指定できなくなりました。そして 2005 年 4 月、PHP 5.0b1 のリリース前に、「static」も許可されるようになりました。しかし、「protected」や「private」を指定しようとすると E_ERROR をスローします。「abstract」も同様です。この変更が既存のコードに影響を及ぼすことはないはずです。結局のところ、インターフェース内でこれら (protected, private, abstract) を指定してもそれは意味のないものだからです。

7. 継承規則の変更

PHP 5.0 では、基底クラスの同名の関数定義と一致しない関数を派生クラスの中で定義することが可能でした。たとえば以下のようなものです。


class Base {
    function &return_by_ref() {
        $r = 1;
        return $r;
    }
}

class Derived extends Base {
    function return_by_ref() {
        return 1;
    }
}

PHP 5.1 では、このコードは E_STRICT エラーを発生させます。

8. クラス定数

PHP 5.0 では、以下のコードは正しいものでした。


<?php

class test {
    const
foobar = 'foo';
    const
foobar = 'bar';
}

?>

PHP 5.1 では、クラス定数を再定義すると、致命的なエラー E_ERROR がスローされます。

9. 拡張モジュール

9a. PHP コアから削除された拡張モジュール

PHP 5.1 をダウンロードして最初に気づくことのひとつは、いくつかの古い拡張モジュールがなくなっていることでしょう。これらの拡張モジュールのうち、現在も使用可能で活発にメンテナンスが続けられているものについては PHP Extension Community Library (PECL) http://pecl.php.net で入手可能です。Windows 版のバイナリも定期的にビルドされており、PHP 5.1 用の PECL 拡張モジュールのバイナリは http://pecl4win.php.net/list.php/5_1 から取得可能です。

モジュール名        代替モジュール/開発状況
=========           ========================
ext/cpdf            pecl/pdflib
ext/dbx             pecl/dbx
ext/dio             pecl/dio
ext/fam             メンテナンスが滞っています
ext/ingres_ii       pecl/ingres
ext/ircg            メンテナンスが滞っています
ext/mcve            pecl/mcve
ext/mnogosearch     メンテナンスが滞っています
ext/oracle          ext/oci8 あるいは ext/pdo_oci
ext/ovrimos         メンテナンスが滞っています
ext/pfpro           メンテナンスが滞っています
                    - 代替モジュールは http://pecl.php.net/packages.php?catpid=18&catname=Payment です
ext/w32api          pecl/ffi
ext/yp              メンテナンスが滞っています
sapi/activescript   http://pecl4win.php.net/ext.php/php5activescript.dll (PECL パッケージ)
                    あるいは pecl/activescript (CVS)

PECL モジュールのうち、メンテナンスが滞っているもの (いつの間にかサポートされなくなっていたり、現在メンテナがいなかったり、PECL パッケージとしてリリースされていなかったりするもの) は、http://cvs.php.net/pecl/ の CVS から取得可能です。しかし、これらのモジュールはサポートされておらず、インストールして使用するのは大変かもしれません。

9b. PHP 5.1 での新しい拡張モジュールのクラス定数

Zend Engine 2.1 の API では、拡張モジュールの開発者がクラス定数を定義することが可能となりました。SPL・PDO・ext/XMLReader・ext/date などの PHP 5.1 用に書かれた新しい拡張モジュールでは、この形式の定数を使用しており、C 形式の

PDO_CLASS_CONSTANT

ではなく

PDO::CLASS_CONSTANT

となっています。これは、PHP のグローバル名前空間の汚染を最小限に抑えるためです。

10. 日付/時刻のサポート

PHP 5.1 では、日付/時刻のサポートが完全に書き直され、タイムゾーンを「知る」ためにシステムの設定を使用しないようになりました。そのかわりに、以下の順番でタイムゾーンを取得します。

正確性を保証する (そして警告 E_STRICT を出さないようにする) ためには、php.ini の中に以下の形式でタイムゾーンを定義する必要があります。

date.timezone = Europe/London

サポートされるタイムゾーンをこの形式で並べた一覧表が、PHP マニュアルの http://www.php.net/manual/ja/timezones.php にあります。

11. データベースサポートの変更

11a. PDO 概要

PHP Data Objects (PDO) は PHP 5.0 の時に PECL 拡張モジュールとして公開され、その後 PHP 5.1 で PHP のコア配布物に組み込まれるようになりました。PDO 拡張モジュールはデータベースへのアクセスのための一貫したインターフェースを提供し、データベース固有の PDO ドライバとともに使用されます。各ドライバはデータベース固有の関数を保持していることもありますが、クエリの発行やデータの取得といった基本的な機能については PDO 関数でカバーされています。この関数は、PDO::__construct() で指定したドライバを使用します。

PDO 拡張モジュールやドライバは、共有モジュールとしてのビルドを想定していることに注意しましょう。これにより、PECL からドライバをアップグレードする際に PHP そのものを再ビルドする必要がなくなります。

PHP 5.1 のリリース時点では、PDO は広範囲に及ぶテストを済ませておりほとんどの環境でうまく動作するはずです。しかし、PDO やそのドライバは比較的歴史が浅く、データベース固有の機能のいくつかを実装できていないということを理解しておくことが大切です。新しいプロジェクトで PDO を使用する際には、事前に動作検証を十分に行うようにしましょう。

既存のコードは、一般的にはこれまで存在したデータベース拡張モジュールに依存しています。これらについてもメンテナンスが続けられます。

PDO 拡張モジュールについてのより詳細な情報は、マニュアルの http://www.php.net/manual/ja/ref.pdo.php にあります。

11b. MySQL サポートの変更

PHP 4 では、MySQL 3 のサポートが組み込まれていました。PHP 5.0 のリリース時には「mysql」および「mysqli」という 2 つの MySQL 拡張モジュールがありました。これらは、それぞれ MySQL < 4.1 および MySQL 4.1 以降をサポートするように設計されていました。PHP でサポートされるあらゆるデータベースの API への高速なインターフェースを提供する PDO が公開されたことに伴い、PDO を使用して書かれた PHP コードでは、PDO_MYSQL ドライバによって現行のいずれのバージョン (MySQL 3、4 あるいは 5) でもサポートすることが可能となりました。これは、コンパイル時に使用した MySQL ライブラリのバージョンに依存します。後方互換性を確保するためにこれまでの MySQL 拡張モジュールも残されていますが、デフォルトでは有効になりません。

11c. SQLite サポートの変更

PHP 5.0 では、組み込みの sqlite 拡張モジュールで SQLite 2 がサポートされていました。PHP 4.3 および PHP 4.4 でも、PECL 拡張モジュールとして同じものが使用可能でした。PDO が公開されたことで、sqlite 拡張モジュールは 'sqlite2' という名前の PDO ドライバとして機能するようになりました。このため、PHP 5.1 での sqlite 拡張モジュールは PDO 拡張モジュールに依存しています。

PHP 5.1 では、sqlite への接続用にいろいろなインターフェースを公開しています。

sqlite 拡張モジュールでは "classic" な 手続き型/オブジェクト指向 API を提供しており、これは以前のバージョンの PHP と同じように使用できます。またこれとは別に PDO 'sqlite2' ドライバを提供しており、こちらは PDO API を使用して SQLite 2 データベースにアクセスします。

さらに、PDO_SQLITE は 'sqlite' バージョン 3 ドライバを提供します。SQLite バージョン 3 は SQLite バージョン 2 に比べて非常に優れていますが、2 つのバージョンの間でファイルフォーマットに互換性がありません。

以前のバージョンの PHP で動作している SQLite ベースのプロジェクトがあるのなら、ext/sqlite を使用し続ければ何の問題もありません。しかし、PDO および sqlite を明示的に有効にする必要があります。新しいプロジェクトの場合は、PDO および 'sqlite' (バージョン 3) ドライバを使用すべきです。これは SQLite 2 に比べて高速で、ロック処理が改善されており、プリペアドステートメントやバイナリ列をネイティブにサポートしています。

SQLite 拡張モジュールを使用するには、PDO を有効にする必要があります。もし PDO を共有モジュールとしてビルドしたのなら、SQLite 拡張モジュールも同様に共有モジュールとする必要があります。これは、PDO ドライバを提供している他のすべての拡張モジュールについても同様です。

12. 移行のためのより詳細な情報

PHP 4 から PHP 5 への移行についての一般的な情報は、PHP マニュアルの http://www.php.net/manual/ja/migration5.php から関連するセクションを参照ください。

13. E_STRICT エラーのチェック

ひとつのスクリプトについてのみチェックしたいのであれば、PHP コマンドラインの 文法チェック機能を使用して E_STRICT エラーを抜き出すことができます。

php -d error_reporting=4095 -l script_to_check.php

大量のファイルをチェックするのなら、以下のシェルスクリプトで同様のことが可能です。

#!/bin/sh

directory=$1

shift

# チェックする拡張子を指定します
extensions="php inc"

check_file ()
{
  echo -ne "Doing PHP syntax check on $1 ..."

  # オプション
  ERRORS=`/www/php/bin/php -d display_errors=1 -d html_errors=0 -d error_prepend_string=" " -d error_append_string=" " -d error_reporting=4095 -l $1 | grep -v "No syntax errors detected"`

  if test -z "$ERRORS"; then
    echo -ne "OK."
  else
    echo -e "Errors found!\n$ERRORS"
  fi

  echo
}

# loop over remaining file args
for FILE in "$@" ; do
  for ext in $extensions; do
     if echo $FILE | grep "\.$ext$" > /dev/null; then
       if test -f $FILE; then
         check_file "$FILE"
       fi
     fi
  done
done