PHP 8.0だとWordPressがうまく動かない件

WordPressを動かしているサーバーのPHPを8.0にしたところ、WordPressの管理画面(ダッシュボード)を開くのに時間がかかるようになってしまいました。
また、ダッシュボードに表示されているウィジェットも一部正常に表示されなくなってしまいました。

結論から言うと、PHP 8.0になってある関数の戻り値が変わってしまったことが原因です。
そこに至るまでの経緯と対処法を記事にしたいと思います。

WordPressを5.8にアップデートすることで改善されます
関連記事

WordPress 5.8 Tatumがリリースされました。 アップデートの内容については公式サイトをご覧ください。 個人的に気になったのは「WebPサポートの追加」ですね。 今までもプラグインを入れることで対応はできていたようで[…]

現象

  • WordPressの管理画面(ダッシュボード)の表示に時間がかかる。
    また、ダッシュボードに表示される一部のウィジェットが正常に表示されなくなる。
  • ダッシュボードを開こうとした瞬間からCPUコアの一つが使用率100%になる。

状況の整理

問題なく表示される場合もあり、全てがダメというわけではないようなので状況を整理します。

問題ない

  • PHP 7.xの場合
  • PHP 8.0で標準テーマ(Twenty Twenty-One)の場合

問題あり

  • PHP 8.0で某テーマを使用した場合

原因を推測してみる

CPU使用率の上がり方、時間はかかるものの待っていれば正常な状態ではないにせよ一応表示はされる、という点から考えると処理の中で無限ループに陥っているのではないかと推測しました。
ダッシュボードが表示されればCPU使用率はガクッと下がるのでほぼ間違いないのではないかと思いました。

某テーマのせいではなかった

PHP 8.0でもテーマによってはうまく動くものもあるので当初は某テーマの不具合を疑いました。
しかし、ログファイルを見てそれが間違いであることが分かりました。

PHP message: PHP Fatal error: Maximum execution time of 30 seconds exceeded in 〜

確実に無限ループしていると思われ、示されていたファイルはWordPress本体のファイルでした。

wp-includes/widgets.phpが原因

ログファイルに示されていた箇所はこちらになります。

while ( stristr( $link, 'http' ) !== $link ) {
    $link = substr( $link, 1 );
}

substr関数に渡しているオフセット値が1なので$linkの中の2文字目から後ろを返します。
今回は$linkの中がnullだったんですが、その場合substr関数の結果は何が返ってくると思いますか。

PHP 8.0のsubstr関数

$link = null;
var_dump(substr($link, 1)); // string(0) ""

PHP 7.xのsubstr関数

$link = null;
var_dump(substr($link, 1)); // bool(false)

そして、stristr関数で該当する文字列が見つからなかった場合は何が返ってくると思いますか。

PHP 8.0及びPHP 7.xのstristr関数

$link = null;
var_dump(stristr($link, 'http')); // bool(false)

falseと空文字を厳密比較しているので100万年待ってもループを抜けてくることはありません。
(実際には実行時間の制限(今回は30秒)に引っかかって処理は戻ってきます)
PHP 7.xではfalseを返していたsubstr関数が、PHP 8.0になって空文字を返すようになってしまったことが無限ループの原因です。


詳しくコードを追っていないのですが、この問題となった部分はウィジェット内のaタグに埋め込むリンク先(hrefの中身)を生成しているところのようです。
当初疑ってしまった某テーマにはたまたまリンク先のない(aタグではなく通常のテキストとして表示される)ものがあり、そのせいでこのバグ(?)を踏んでしまったようです。

対処法

上記の比較している箇所を厳密比較から曖昧比較(!=)に変えれば一応は直ります。
ただ、元々なぜ厳密比較にしているかの理由が分からないので曖昧比較にした状態で本番運用するのもそれはそれで不安が残ります。
WordPress本体のコードなので近いうちにアップデートされるはずです。
それまではPHP 8.0への移行をしないか、何らかの不具合が発生する可能性を念頭に置いて曖昧比較にするか、サイト管理者の判断次第だと思います。

まとめ

substr関数の戻り値の変更、個人的には結構影響が大きいと思うのですが、皆さんはどう感じたでしょうか。
時々こういう破壊的な変更をしてくる場合がありますからバージョンアップは慎重に行う必要がありますね。

余談ですが、個人的にこの表記はコードレビューで真っ先に指摘します。

$link = substr( $link, 1 );

括弧前後のスペースに何の意味があるのか全く分からない…。