古い PHP のコードを 8.3 に上げたら落ちたところまとめ
警告がエラーになった
PHP 7.4 で動いていた古い WordPress のサイトを、PHP 8.3 に上げる作業をしました。 自作のテーマや、WordPress の外に置いてある PHP のフォームなど、年季の入ったコードがたくさんあります。
PHP 8 では、7.4 まで警告(Warning)を出して動き続けていた書き方の多くが、例外を投げて止まるようになりました。 画面が真っ白になったり、500 エラーになったりします。 実際に直したところを、種類ごとにまとめておきます。
1. なくなった関数
create_function()
PHP 8.0 で削除されました。 WordPress のフィルターで「何も返さない関数」を作るのによく使われていました:
// 8.3 では Error: Call to undefined function create_function()
add_filter('get_search_form', create_function('$a', 'return null;'));
WordPress には同じことをする関数が最初から用意されているので、それに置き換えます:
add_filter('get_search_form', '__return_null');
__return_true __return_false __return_empty_string などもあります。 それ以外の処理なら、無名関数(function ($a) { ... })で書き直します。
get_magic_quotes_gpc()
これも PHP 8.0 で削除されました。 ただ、この関数は PHP 5.4 からずっと false を返すだけでした。 呼んでいる箇所が多くて書き換えるのが大変なら、同じ動きをする関数を用意してしまうのが手っ取り早いです:
if (!function_exists('get_magic_quotes_gpc')) {
function get_magic_quotes_gpc() { return false; }
}
2. クォートしていない配列のキー
$query->query_vars[search] = false;
PHP 7.4 までは「search という定数はないので、文字列 'search' とみなします」という警告で済んでいました。 8 では Error: Undefined constant "search" で止まります。 クォートを付けるだけです:
$query->query_vars['search'] = false;
3. count() に配列以外を渡す
PHP 8 で一番多く引っかかったのがこれです。
$results = $wpdb->get_results($sql);
if (count($results) > 0) { ... }
$wpdb->get_results() は、クエリが失敗すると null を返すことがあります。 7.4 の count(null) は警告を出して 0 を返していました。 8 では TypeError です:
TypeError: count(): Argument #1 ($value) must be of type Countable|array, null given
「中身があるか」を知りたいだけなら、empty() で判定します:
if (!empty($results)) { ... }
null かもしれない値のプロパティを読むところは、?? で既定値を決めておきます:
$total = $results[0]->count ?? 0;
4. クエリパラメーターが配列とは限らない
意外と多かったのがこのパターンです。 検索フォームでは ?prices[]=100-199 のように配列で値を送る前提なのに、
?prices=100-199
?prices=abc
のように文字列で来ることがあります。 古いリンクやボットが、こういう URL でアクセスしてきます。
7.4 ではどれも警告で済んでいました。 8 では、配列を前提にした関数がまとめて TypeError になります:
TypeError: in_array(): Argument #2 ($haystack) must be of type array, string given
TypeError: number_format(): Argument #1 ($num) must be of type int|float, string given
受け取ったところで、型と形式を揃えておきます:
// 配列前提のパラメーターは、文字列で来ても配列にする
$prices = (array) ($_GET['prices'] ?? []);
// 「100-199」「-500」「1500-」の形式以外は捨てる
$prices = array_values(array_filter($prices, function ($price) {
return is_string($price) && preg_match('/^(\d+-\d*|\d*-\d+)$/', $price);
}));
number_format('1500') のような数字だけの文字列は 8 でも通ります。 落ちるのは数値として読めない文字列のときだけなので、手で動かしただけでは気づきにくいです。
5. ついでに: "${var}" は 8.2 から非推奨
SQL を組み立てているところに、こんな書き方がありました:
$where .= "AND c.parent_id IN (0${parents}) ";
これはまだ動きますが、8.2 から Deprecated が出るようになりました。 {$var} に書き換えておきます:
$where .= "AND c.parent_id IN (0{$parents}) ";
確かめ方
上の挙動は、Docker の公式イメージで 7.4 と 8.3 を並べて確かめました:
$ docker run --rm -v "$PWD":/w php:8.3-cli php /w/test.php
error_reporting=-1 を付けて 7.4 で動かすと、8 で落ちる箇所の多くが警告として出てきます。 上げる前に、今のバージョンのまま警告を全部出してみるのがおすすめです:
$ docker run --rm -v "$PWD":/w php:7.4-cli php -d error_reporting=-1 /w/test.php
古いコードほど「警告は出ているけど動いている」ところが溜まっています。 PHP 8 は、それを全部例外にして教えてくれる、と思うことにしました。
