残り一年! PHP4からPHP5への移行

第2回PHP4.4以前からの移行とPHP5におけるオブジェクトの仕様変更

この連載は基本的にPHP4.4.xユーザがPHP5.2.xに移行する場合の注意点を解説しています。しかし、実際にはPHP4.4.xより前のPHPを利用されているユーザも多いと思います。ここではこれらのバージョンを利用しているユーザ向けに、重要な変更箇所を解説します。後半はオブジェクト関連の変更を解説します。

特に記述がない場合、PHP4はPHP4.4.x、PHP5はPHP5.2.xを意味します。

リファレンス仕様の変更

PHP4.4.0はPHP5.1.0と同時にリリースされました。Zendエンジンの仕様やその他の機能拡張を含めたリリースでした。Zendエンジンの変更には不適切なリファレンスの使用によりメモリ破壊が発生するバグを修正するためのリリースも含まれていました。

Zendエンジンの修正によりメモリ破壊は発生しなくなりましたが、一部動作仕様が変更されたり、エラーとなるようになりました。PHP4.4に移行していないユーザの場合、動作していたプログラムが動作しなくなったり、動作が変わる場合があります。しかし、動作が変わったり動作しなくなるケースは元々リファレンスの使用方法が間違っていたり不適切な場合です。大きな影響はないはずです。

PHPマニュアルに記載されている例

http://www.php.net/manual/en/migration51.references.php#migration51.references-fails

<?php
function func(&$arraykey) {
    return $arraykey; // function returns by value!
}

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

var_dump($z);
?>

このコードをPHP4.4.0未満、PHP5.1.0未満で実行すると

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

PHP4.4.0以上, PHP5.1.0以上で実行すると

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

と実行結果が異なります。構文的には新しい動作が正しい動作になります。ほかにもリファレンス関連の動作使用の修正が行われています。詳しくはhttp://www.php.net/manual/en/migration51.references.phpを参照してください。

無効な配列アクセス

[]を利用して配列変数を読むことはできません。現在のPHP4、PHP5では[]を利用した配列変数の読み取りはFatalエラーを発生させます。

例:[]は読み取りには利用できない
<?php
$a[] = 1;
$b = $a[];
?>
実行例:PHP4
[yohgaki@dev php4-php5-migration]$ ./php4 invalid-array-reading.php

Fatal error: Cannot use [] for reading in /home/yohgaki/php4-php5-migration/invalid-array-reading.php on line 3
実行例:PHP5
[yohgaki@dev php4-php5-migration]$ ./php5 invalid-array-reading.php

Fatal error: Cannot use [] for reading in /home/yohgaki/php4-php5-migration/invalid-array-reading.php on line 3

関数定義仕様の変更

古いPHP4では関数定義は実行時に動的に生成されていました。PHP5から関数定義はコンパイル時に定義されます。この仕様変更により次の例のコードは古いPHP4では動作し、現在のPHP4, PHP5では動作しません。

関数を定義するファイル ─ my_functions.php
<?php
if (defined('MY_FUNCITON')) {
   return;
}
define('MY_FUNCTION', 'defined');

function my_function() {}
?>
関数定義をファイルmy_function.phpを読み込むファイル ─ my_function_caller.php
<?php
include 'my_function.php';

my_function();

include 'my_function.php';
?>
実行例
[yohgaki@dev php4-php5-migration]$ /usr/local/php-4.4.7/bin/php my_function_caller.php

Fatal error: Cannot redeclare my_function() (previously declared in /home/yohgaki/php4-php5-migration/my_function.php:7) in /home/yohgaki/php4-php5-migration/my_function.php on line 7

my_function.phpのようなコードはinclude_once/require_onceが実装される前に重複した関数定義エラーなどを防ぐために利用されていました。かなり古いPHP用に書かれたPHPコードでなければ見られないスタイルです。現在では関数/クラスを定義するファイルを読み込む場合はrequire_once/include_onceを利用します。

関数定義をファイルmy_function.phpを読み込む
<?php
include_once 'my_function.php';

my_function();

// my_function.phpは読み込み済みで読み込まれない
// エラー無しに実行される
include_once 'my_function.php';
?>

PHPバイナリの名前

前回も紹介しましたが古いPHPではCGI用のバイナリが「php」から「php-cgi」に変更されています。CGIバイナリの名前を「php」に変更しても動作しますが、新しい名前でCGIを正常に動作させるにはWebサーバの設定ファイルの変更が必要となります。

長い名前のシステム定義配列

最近のPHPアプリケーションでは見かけなくなりましたが、$HTTP_POST_VARS、$HTTP_GET_VERSなどのPHPが定義する配列変数はphp.iniで初期化しないように設定できるようになっています。デフォルトで初期化されるように設定されているのでこれらの長い名前のシステム定義配列が初期化されないケースはあまりないと思いますが、もしこれらの配列が未定義の場合はphp.ini設定を変更します。

register_long_arrays=on

をphp.iniに追加します。Apacheであれば.htaccessからも変更できます。

register_globals=off

register_globalsのデフォルト設定がoffに変更されたのはPHP 4.2.0からなので、最近のアプリケーションではregister_globals=onを前提とするアプリケーションはほとんど見かけなくなりました。しかし、アプリケーションの中にはregister_globals=onを前提としているものもあります。PHPをサポートするホスティングサービスで新しいPHPを提供していても、アプリケーションの互換性のためにregister_globals=onをデフォルトに設定している会社も少なくありません。

アプリケーションがregister_globals=onを前提としているためやむを得ずresgister_globals=onに設定する場合は、安全性に注意してください。

参照カウンタの32ビット化

PHP4からPHP5へ移行する場合の注意点ではありませんが、動作が変わるのでこの仕様変更を紹介します。PHP5.0.0から変数の参照カウンタが16ビットから32ビットに拡張されました。この拡張は移行時に問題を発生することはありませんが、PHP4ではクラッシュしていたプログラムがPHP5から正常に動作する場合があります。

例:PHP4でクラッシュしPHP5では問題なく実行されるコード
<?php
$max = 1 << 16;
$v = 123;
for ($i = 1; $i < $max; $i++) {
  $arr[] = &$v;
}

echo $v;
?>
例:PHP4(Linux環境)での実行例
[yohgaki@dev php4-php5-migration]$ ./php4 reference_counter.php
123
*** glibc detected *** /usr/local/php-4.4.7/bin/php: free(): invalid next size (fast): 0x09029b78 ***
======= Backtrace: =========
/lib/libc.so.6[0x79c211]
/lib/libc.so.6(__libc_free+0x83)[0x79d863]
/usr/local/php-4.4.7/bin/php(shutdown_memory_manager+0x82)[0x8162142]
/usr/local/php-4.4.7/bin/php(php_request_shutdown+0x2f7)[0x8143b67]
/usr/local/php-4.4.7/bin/php(main+0x3b9)[0x818bbf9]
/lib/libc.so.6(__libc_start_main+0xdc)[0x75074c]
/usr/local/php-4.4.7/bin/php(gdImageCreateFromPngCtx+0x145)[0x806d5b1]
例:PHP5(Linux環境)での実行例
[yohgaki@dev php4-php5-migration]$ ./php5 reference_counter.php
123

PHP4ではメモリエラーでクラッシュしていることが分かります。PHPがクラッシュするとWeb環境ではページに何も表示されずログにもなにも記録されていない場合がほとんどです。Web環境でクラッシュを検出するにはgdbなどのデバッガを利用できますが、このクラッシュはメモリマネージャでエラーが発生していることが分かります。大きいアプリケーションの場合、クラッシュの原因となるコードがどこなのか特定する作業は困難です。

PHP5に移行することにより参照カウンタのオーバーフローによる原因不明のクラッシュで悩まされることはなくなると思います。

オブジェクト関連の変更

PHP5になりオブジェクト指向プログラミングをサポートする機能が強化されました。PHP4では関数の戻り値がオブジェクト変数であってもオブジェクト変数としてアクセスできない、例外オブジェクトをサポートしないなど、現在のオブジェクト指向プログラミング言語としては機能的に見劣りしていました。PHP5ではこれらの課題が解決されていますが互換性の問題も発生しています。次の項から次回にかけてはオブジェクト関連の変更を解説します。

オブジェクト変数の代入

PHP4のオブジェクト変数は、ほかの変数と同様に"="(代入演算)でコピーが作成されていました。PHP5からオブジェクト変数はハンドルで取り扱われ、"="代入演算子でオブジェクト変数を代入するとオブジェクトのコピーは作成されず、オブジェクトへのハンドルがコピーされるようになりました。ほかのオブジェクト指向プログラミング言語に近い操作が可能になっています。

例:オブジェクトの代入動作の違い
<?php
class foo {var $v = 0;}    // 空のクラス定義
$obj1 = new foo;   // オブジェクトの生成
$obj2 = $obj1;     // $obj2はPHP4ではコピー、PHP5では同じオブジェクト
$obj2->v = 1;
var_dump($obj1, $obj2);
?>
PHP4の出力
object(foo)(1) {
  ["v"]=>
  int(0)   ←ここが異なる
}
object(foo)(1) {
  ["v"]=>
  int(1)
}
PHP5の出力:vプロパティは両方1
object(foo)#1 (1) {
  ["v"]=>
  int(1)   ←同じオブジェクトなので$obj2を変更すると同じ値になる
}
object(foo)#1 (1) {
  ["v"]=>
  int(1)
}

非常に大きな問題に見えますが、この違いが問題になるケースはあまり多くありません。PHP4でオブジェクトのコピーでなくオブジェクトの参照を作成した場合、"&"参照演算子でオブジェクト変数の参照を作成します。

$obj2 = &$obj1;

このコードを実行すると、PHP4では$obj2変数は$obj1変数の参照になり、$obj1と$obj2は同じオブジェクトへアクセス可能なオブジェクト変数になります。PHP5ではPHP4と同じく$obj2は$obj1の参照になります。違いは通常変数と同じ参照なのか、オブジェクトハンドルの参照なのかだけで結果的に同じ動作になります。

オブジェクト自身を返すコードの典型的な例は、コンストラクタで初期化したオブジェクト自身を返す場合です。

$obj = & new foo;

PHP4の場合、オブジェクト自身を返すため"& new foo"の&演算子は必須でした。PHP5では&演算子の有無に関係なくオブジェクト自身を返すことができます。参照演算子&が利用されてもPHP5で実行した場合に違いが発生することはありません。

オブジェクトのコピー作成

"="代入演算子によりオブジェクトのコピーが作成されることを期待しているコードは変更が必要です。PHP5は"="代入演算子でコピーが作成できないので、Java言語のようにclone文が用意されています。

例:clone文の利用
<?php
class foo {var $v = 0;}  // 空のクラス定義
$obj1 = new foo;         // オブジェクトの生成
$obj2 = clone $obj1;     // コピーが作成され代入される
$obj2->v = 1;
var_dump($obj1, $obj2);
?>
PHP5での実行例
object(foo)#1 (1) {
  ["v"]=>
  int(0)     ←コピーを変更しているので値は変わらない
}
object(foo)#2 (1) {
  ["v"]=>
  int(1)
}

PHP5オブジェクトはclone時にどのようにオブジェクトをコピーするか定義するコピーコンストラクタが定義できます。PHP4からPHP5への移行には関係ないので解説は省略します。詳しくはマニュアルや書籍を参照してください。

オブジェクトのコピーが必要になるケースは多くありませんが、本当にコピーが必要だった場合にはプログラムが誤作動するので確認が必要です。

オブジェクト変数の代入がコピーであるかハンドルであるかは、オブジェクト変数の比較に大きな影響を与えます。オブジェクト変数の比較については別の項目で解説します。

$this変数の取り扱い

PHP5からオブジェクト自身を表す$this変数が上書きできなくなりました。$thisを上書きすると$thisオブジェクトが別のオブジェクトに変わってしまいます。PHP4の$thisオブジェクト変数が書き換えれる仕様はあまり有用な使い方はできないのでほとんど使われていないと思います。筆者の知っている例では古いPukiwikiは$thisを書き換えていました。

例:$thisの書き換え
<?php
class foo {
   function __construct() {
      $this = 123;
      $return &$this;
   }
}
$obj = new foo;
var_dump($obj);
?>
PHP5
Fatal error: Cannot re-assign $this in - on line 4
PHP4
object(foo)(0) {
}

PHP4ではエラーなく終了しますが、PHP5ではFatalエラーになりスクリプトの実行が停止します。このエラーは回避する設定などは用意されていないので、$thisの書き換えを行わずに実行できるようコードを修正しなければなりません。

まとめ

今回はPHP4.4より古いバージョン向けに移行に必要と思われる情報とオブジェクト関連の仕様変更を紹介しました。オブジェクト関連の仕様変更は一部のみ紹介しています。 次回もオブジェクト関連の仕様変更、注意点などを解説します。

おすすめ記事

記事・ニュース一覧