PHPプログラムをcronでスケジュール実行しています。PHP7からPHP8にアップグレードすることで速度アップするメリットがあるようなので気軽にPHP8に変更しました。ひっそりとPHPプログラムが異常終了していました。
無言で終了していた例
-
変数の初期化は必須。nullを許容しない関数がたくさん増えました。
例えばcount関数はログ出力しないで落ちるなど、無言で終了するので致命的です。
function a(){ if( 条件 ){$list[] = "a";} if( count($list) ){ echo "ok"; } }a();
条件を満たさない場合、$listは未定義(不定値)になります。
配列以外をcount関数に渡すと無言で落ちます。
count関数の他、利用する機会の多いarray_key_exists関数などでも同様です。 -
異常な四則演算はスルーしません。無言で終了します。
error_reporting(-1); echo 1 + v().PHP_EOL; echo 1 - v().PHP_EOL; echo 1 / v().PHP_EOL; echo 1 * v().PHP_EOL; function v(){ return "---"; }
PHP8では初めの+で終了します。PHP7.4はこのような異常な四則演算でも結果を返してくれます。
1 1 INF 0
バグが顕在化して動かなくなるってことですね。それでも無言終了は困ります。
-
空配列でmin,maxは動きません。無言で終了します。
error_reporting(-1); echo "min".'"'.min([]).'"'.PHP_EOL; echo "max".'"'.max([]).'"'.PHP_EOL; echo "sum".'"'.array_sum([]).'"'.PHP_EOL;
PHP8ではmin([])で終了します(array_sumは動作します)。PHP7.4は以下のようにとりあえず動作します。
min"" max"" sum"0"
-
配列を受け付けない関数が増えています。無言で終了します。
$_SERVER変数を表示する単純なプログラムは動きません。
foreach( $_SERVER as $key => $val ){ echo '<li>' . $key . ":" . urlencode($val) . "</li>"; }
URLに?x=a等設定で、$_SERVER[‘argv’]=[“x=a”]という配列が格納されていました。
上記ループで配列を引数に渡すとurlencodeの箇所で終了します。まさか単純な処理で落ちるとは考えが及ばず、時間ロスしました・・・
事例から見えてきた修正のポイント
ソースコードレビューでチェックしたいポイントは以下3点です。
-
PHP標準関数の引数、処理の流れで引数に不正なものがないかをチェックする
配列を求めている関数に不定値を含めて配列以外を渡すのはNGです。
文字列を求めている関数に配列を渡すのはNGです(不定値はとりあえず動作するようです)。
PHP8は厳密です。 -
四則演算(余りを求める含む)で文字列を渡すパターンがないかをチェックする
functionやメソッドの戻り値を使った四則演算を重点的にチェックしたいですね。
-
min関数、max関数(Math関数は注意が必要かも)へ空配列が渡るパターンがないかをチェックする
countでチェックして、1件以上ある場合のみ使っているかチェックが必要です(必ずデータがある場合は含みません)。
徹底してやっても、$_SERVERの例でわかるように、実際動作させることで止まってわかることもあるかと思います。これ困りますよね。使ってる変数も、実環境で値を取得して明らかにした上でレビューするしかないかも。
まとめ:PHP8は打たれ弱い印象です。
PHP7.4までは記述ミスではない限り、大抵どんな異常なデータでも受け止めてくれた。分厚い筋肉に覆われていて力強い、コンクリートかよ!そんな印象を持っています。
PHP8以降は簡単に倒れてしまいます。猫ぱんちで倒れます。
コンパイルでエラーチェックする方法やコンソールにエラーを出力する方法はあるんでしょうか?正常系はなんとか修正できました。
配列が[]の状態でmax()で落ちるとか結構処理内容に依存してしまうので困っていますよ。
レビューでは変数の存在確認をしていることを確認したい
ソースコードは長くなる傾向になります。ただしっかり確認することで無言終了を回避できます。
- isset($変数)で変数が存在を確認している
- 文字列引数は、事前にis_string($変数)で文字列であることを確認している
- 配列引数は、事前にis_array($変数)で配列であることを確認している
- 配列要素数1以上必要な引数は、count($変数)で存在を確認している
補足:どうやって特定しているの?ブロック単位でechoしています。
exceptionでエラーが出ていればラッキーなんですが、そんな出力なく、die()している感じです。
IDEでphpデバッグ環境を整えていないので、このような状況でIDEでデバッグができるのか?よくわかっていません。
- 1)いつもとは違うログ出力の箇所から止まっているソースを特定します。
でもどこで止まっているのかはわかりません。PHP7では動いていたんですから・・・ - 2)特定するため、一連の処理のブロック単位(例えば、先頭とreturn 前)でecho “適当な文字”;を追加します。
php8でいつもの処理を実行します。通過している箇所はechoで出力されます。途中で止まったブロックを特定します。
このブロックを特定し、修正しても、その先でまた止まる可能性があります。全部特定するまで残しておくと後戻り少なく済みます。 - 3)特定したブロックで、さらにecho “任意の文字”;で止まる場所を特定します。
こうやって絞り込んで終了している箇所を特定しました。
もっとスマートに見つける方法が知りたいですね。
コメント