/ #PHP #WordPress

古い 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 は、それを全部例外にして教えてくれる、と思うことにしました。