読者です 読者をやめる 読者になる 読者になる

ミニマムなPHP5.4移行ガイド

 「サイボウズ・アドベントカレンダー」の2日目です(これまでの記事一覧)。


こんにちは。Garoonのメンテナンスチームの横田です。

JavaScriptだなんだと言っていますが、今日はPHPのバージョンアップの話です。

以前話題になっていた記事やコメントをみると、恐ろしすぎてついていけないです。しかし、いつかはPHPのサポートが切れて、PHP5.4への移行をしなくてはいけません。gkbrモノですよね!特段すごいことでないと前置きしておきますが、できるだけコードを触らずにPHP5.4で動かすためには、どうしたらいいのかをちょちょいと説明しちゃいます!

PHP5.4対策

まずは、php.iniファイルのE_STRICTを切ります。PHP5.4ではデフォルトでONになりますので、明示的に切りましょう。

Call-time pass-by-reference(関数呼び出し時の参照渡し)がPHPの機能として削除されてしまっているので、オプションを削除してください。ちなみに関数呼び出し時の参照渡しとは、以下のようなコードです。

/**
 * @param $a $aは参照渡し
 * @param $b $bは値渡し
 * @see http://php.net/manual/ja/language.references.pass.php
 * @return bool
 * この関数だけでは、Fatal Errorは発生しないことに注意してください
 **/
function somethingMethod( &$a, $b ) {
   return true;
}
$a = 1;
$b = 2;
/** ここでエラーが発生 */
somethingMethod(&$a, $b);

php.iniの設定

error_reporting = E_ALL & ~E_STRICT
;; 削除する
;;allow_call_time_pass_reference = On

ほかにも削除されているディレクティブがありますので、ここで確認してみてください。

call_time_referenceに対応する

PHP5.4では Call-time pass-by-referenceが php -l コマンドで見つけることができるので、実行せずとも問題のコードを見つけることができます。素晴らしい!これとE_STRICT切りで、PHP5.4は動いたも同然です!

php -l trunk/code/something.php
Fatal error: Call-time pass-by-reference has been removed in trunk/code/something.php on line 112
...

ちなみに社内のコードでは150箇所ほどでした(´・ω・`)ヒィ。ここを対応したら、だいたいは動くようになりましたー。

あと、php -lでFatal Errorが発生してしまうと、後のコードは、見てくれないので、修正とリントを繰り返してくださいね。

文字列offset問題

上の2つでPHPそのものは動きますが、文字列のOffset問題は、非常に辛い問題でして、品質保証という点に関しては回帰試験が必要でしょう。

(´・ω・`)キツイ…

$str = 'abc';

var_dump($str['1']);
var_dump(isset($str['1']));

var_dump($str['1.0']);
var_dump(isset($str['1.0']));

var_dump($str['x']);
var_dump(isset($str['x']));

var_dump($str['1x']);
var_dump(isset($str['1x']));

PHP5.3の場合

string(1) "b"
bool(true)
string(1) "b"
bool(true)
string(1) "a"
bool(true)
string(1) "b"
bool(true)

PHP5.4の場合

string(1) "b"
bool(true)

Warning: Illegal string offset '1.0' in /tmp/t.php on line 7
string(1) "b"
bool(false)

Warning: Illegal string offset 'x' in /tmp/t.php on line 9
string(1) "a"
bool(false)
string(1) "b"
bool(false)
  • 真偽判定がひっくり返る
  • 新たにエラーが発生するように

E_STRICTを見つけ出す方法

E_STRICT対応を逃げても今まで挙げた対応が出来れば、PHP5.4でも動きます。この方法はE_STRICTも対応したいという方向けです。

E_STRICTの中にはコンパイル時に発生するものがあります。なので、phpの実行時にエラーレベルをMAXにして、php -lをかけるとすこし幸せになれます。といっても実行時エラーも多く、継承関係がおかしいのぐらいしか警告してくれませんが、実際にコードを動かさなくても、出せるエラーがあります。

class ParentClass {
   public function methodA($a,$b){} //E_STRICT error
}

class ChildClass extends ParentClass {
   public function methodA($a){}
}

エラーをMAXにしてphp -lをかけます。

./plain-php5.4.8/bin/php -d display_errors=1 -d html_errors=0 -d error_prepend_string=" " -d error_append_string=" " -d error_reporting=4095 -l sample.php
Strict Standards: Declaration of ChildClass::methodA() should be compatible with ParentClass::methodA($a, $b) in sample.php on line 17

テストコードのtips

やっぱりどうしてもテストを書けないという時があります。そういう時は、レビューアーとQAとで協力して乗り切る他ありませんが、test_helperを使うと少し幸せになれるかもしれません。

関数の中にプロセスが終了関数があって、テストが途中終了する

クラス・メソッドならまだ、事態はマシです。テストコードを書くチャンスがあります。「レガシーコード改善ガイド」(マイケル・C・フェザーズ著) では、グローバル関数は、オブジェクト接合部で解決されますが、PHPはそんなに甘くないです。

たとえばこんなコードがあって、

function cybozu_throw_error($err_msg) {
   print_error($err_msg);
   die();//最悪(>_<;)
}

こんなところに使われているとか

class ClassA {
   public function someMethod() {
       if( $this->isValid() ) {
           cybozu_throw_error("invalid in some Method");
       }
   }
}

ClassA::isValid()の返り値がtrueだと、PHPUnitが途中で終了して、テストもままなりません。

peclライブラリのtest_helperを利用する

こんなかんじで、グローバル関数の置き換えができたりします。

function fake_cybozu_throw_error() {/* do nothing */}
rename_function( "cybozu_throw_error", "orig_cybozu_throw_error");
rename_function( "fake_cybozu_throw_error", "cybozu_throw_error");

Garoonを改善してくださるプログラマ募集

PHPマニュアルにのっていることがほとんどで、お茶を濁した感がすごいです(スイマセン)。レガシーコードを触っているとだんだん周りが見えなくなってくるので、「こういう方法もあるよー」的なバージョンアップ対応方法を紹介してみました。少しづつPHP5.4に向けて、準備出来るところもあるはずです。いきなり全部やろうとすると大やけどをするので、用法用量を守ってがんばりましょう。

僕が現在関わっているGaroonを改善してくださる同志を募集しております。くわしくは採用情報へ。

ほんとたすけてくだしあ・・・ 一緒にがんばりましょうー。


明日は「Webブラウザでフリック体験」をお送りします。お楽しみに。