参照に関する処理の変更
- 概要
- PHP 4.3.x では動作するものの、現在は動作しないコード
- PHP 4.3.x では動作するものの、現在はエラーとなるコード
- PHP 4.3.x では動作しなかったが、現在は動作するコード
- 本来は PHP 5.0.x で正常に動作すべきだったコード
- 現れて、 そしていなくなった警告
概要
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
となります。
これまでメモリ破壊を起こす可能性のあったコードは、もはやその心配はなくなりました。 しかし、既存のコードの中にはこれまでと挙動が変わってしまうものもあります。
PHP 4.3.x では動作するものの、現在は動作しないコード
<?phpfunction 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()
の結果が参照として代入され、
先頭の $y
は毎回代入されてしまっていました。
一時的な変数を参照で渡そうとすることが、メモリ破壊の原因となっていました。
修正前のバージョンの PHP と修正後のバージョンの PHP とで、
このようなコードの挙動を同じにすることは可能です。
func()
の定義を変更して参照を返すようにするか、あるいは
func()
が返す結果を参照として代入する処理を取り除けばよいのです。
<?phpfunction func() { return '関数の返す値';}$x = 'original value';$y =& $x;$y = &func();echo $x;?>
PHP 4.3 では $x
は 'もとの値' となりますが、
この変更以降のバージョンでは
'関数の返す値' となります。関数は参照を返さず、
値が直接代入されることを覚えておきましょう。もう一度いいます。
このような挙動の違いをなくすには、func()
が参照を返すようにするか、
関数の返す値を直接参照として代入することをやめるかのどちらかを行います。
PHP 4.3.x では動作するものの、現在はエラーとなるコード
<?phpclass 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
をスローします。
PHP 4.3.x では動作しなかったが、現在は動作するコード
<?phpfunction &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 より前のバージョンでは、 このようにして配列の要素に参照を代入することができませんでした。 現在はできるようになっています。
本来は PHP 5.0.x で正常に動作すべきだった
コード
参照関連の修正が行われるより前の PHP 5.0 ではいくつかのバグが報告されていましたが、
それらは今では正常に「動作します」。しかし、いずれの場合についても PHP 5.1.x
ではエラーがスローされます。なぜなら、まず第一にそのコードが不正なものだからです。
現在では self::
を使用して値を参照で返すことができるようになりましたが、
E_STRICT
レベルの警告をスローします。
また、オーバーロードされたオブジェクトに参照を代入しようとすると、
たとえ代入自体は正常に動作していても E_ERROR
に遭遇することになるでしょう。
現れて、そしていなくなった警告
参照を返す関数をネストしてコールすることは、PHP 4.3.x および PHP 5.1.x
のどちらでも有効です。しかし、それらの間のバージョンの PHP では、
E_NOTICE
あるいは E_STRICT
が間違って発生してしまいます。
<?phpfunction & foo() { $var = 'ok'; return $var;}function & bar() { return foo();}$a =& bar();echo "$a\n";?>