レンタルサーバーの管理画面には、たいてい「PHPバージョンの切り替え」というメニューがある。サポートが終わった古いバージョンを使い続けるのは避けたいし、WordPressのサイトヘルスからも「より新しいPHPへの更新」を勧められる。それでも、切り替えたとたんに画面が真っ白になった、「このサイトで重大なエラーが発生しました」と表示された、という話は後を絶たない。
PHPのバージョンを上げたとき、何が、どのような順序で壊れるのか。その手がかりになるのが、非推奨(deprecated) と 削除(removed) という2つの段階の違いである。
今回は、このブログが動いている共用サーバーにインストールされている複数のPHP(7.0〜8.5)で、同じ数行のコードを実際に実行した結果を材料に、バージョンアップで起きることと、安全に切り替えるための考え方を整理する。
機能はいきなり消えるのではなく、段階を踏んで消える
PHPでは、ある関数や書き方をやめることが決まると、多くの場合、次の順番で扱いが変わっていく。
- 通常どおり使える(何のメッセージも出ない)
- 非推奨(deprecated): 動作はそのまま。ただし実行するたびに
Deprecatedという種類の通知が出る - 削除(removed): その関数や書き方自体がなくなる。呼び出すとエラー(多くの場合は致命的エラー)になる
補足: 「非推奨」は「まだ動くが、将来のバージョンでなくなる予定なので書き換えてほしい」という予告である。PHPでは、非推奨にしたものをすぐには消さず、次のメジャーバージョン(7 → 8 のように先頭の数字が変わるとき)でまとめて削除するのが通例になっている。バージョン番号の数字の意味は「セマンティックバージョニング(SemVer)の考え方」で解説した。
この流れが実際にどう見えるかを、配列を1要素ずつ取り出す古い関数 each() で確かめてみる。サーバーにある PHP 7.0・7.4・8.0 で、同じ1行を実行した。
/opt/php-<version>/bin/php -d display_errors=stderr -d error_reporting=-1 \
-r '$a=[1]; var_dump(each($a) !== false);'
結果は次の通りだった。
| PHP | 出力 | 段階 |
|---|---|---|
| 7.0.33 | bool(true) |
通常どおり使える |
| 7.4.33 | Deprecated: The each() function is deprecated... のあとに bool(true) |
非推奨(動作は変わらない) |
| 8.0.30 | Fatal error: Uncaught Error: Call to undefined function each() |
削除(処理がそこで止まる) |
each() は PHP 7.2 で非推奨になり、PHP 8.0 で削除された。7.4 では警告が出ても結果は true のままで、サイトの見た目にも何の変化もない。ところが 8.0 では関数そのものが存在しないため、その行で処理が止まる。
ここに、PHPのバージョンアップでよく起きる事故の構図がある。非推奨の通知は、何年も前から出ていた。ただ、誰も見ていなかった。そして削除のバージョンに上げた日に、初めて目に見える形で壊れる。
非推奨の通知は、なぜ見落とされるのか
WordPressの本番環境では、通常 WP_DEBUG が false になっている。この状態では、エラーや通知が画面に表示されないように設定される。つまり非推奨の通知は、何も対策をしなければ画面にもログにも残らず、静かに捨てられていることが多い。
一方 WP_DEBUG を true にすると、WordPressの初期化処理(wp-includes/load.php の wp_debug_mode())が error_reporting( E_ALL ) を設定し、通知もすべて報告の対象になる。このとき、画面に表示するかどうかは WP_DEBUG_DISPLAY、ファイルに記録するかどうかは WP_DEBUG_LOG で切り替えられる。本番サイトで確認するなら、画面には出さずにログだけを取るのが基本だ(各定数の詳細は「wp-config.phpの主要定数解説」、ログの考え方は「ログレベル設計とローテーション」を参照)。
// wp-config.php(確認期間中だけ)
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true ); // wp-content/debug.log に記録
define( 'WP_DEBUG_DISPLAY', false ); // 訪問者の画面には出さない
非推奨の通知は、言い換えれば「次のメジャーバージョンで壊れる箇所の一覧」である。バージョンアップの前にこのログを一定期間取っておけば、どのプラグインやテーマのどの行が、削除の段階で止まるのかを事前に知ることができる。
「非推奨なら動くから安心」とは限らない
非推奨の段階では、処理の結果は変わらない。それなら放っておいてもサイトは壊れない、と考えたくなるが、実際にはそうとも言い切れない。通知そのものが出力に混ざることで、別のものが壊れることがあるからだ。
このブログの運営元が開発している保守ツールでも、これを実際に経験した。PHP 8.2 以降と古いWP-CLI(2.8 未満)を組み合わせた環境で、WP-CLI自身のコードから
Deprecated: Creation of dynamic property WP_CLI\Dispatcher\CompositeCommand::$longdesc is deprecated in ...
という通知が大量に出力された。WP-CLIの処理自体は正常に終わっていたが、ツールはWP-CLIの出力をJSONとして受け取ってプラグインの一覧などを読み取っていたため、JSONの手前に通知の行が混ざった結果、JSONとして読み取れずに一覧の取得が失敗するという形で表に出た。非推奨の通知は「動作は変わらない」が、「出力は変わる」のである。
対策として、ツール側では次の2段構えにした。
- WP-CLIをPHPに直接渡して起動している場合は、PHPの起動オプション
-d error_reporting='E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED'で、非推奨の通知だけを報告の対象から外す - それでも通知が混ざった場合に備え、出力の中から JSON の開始位置(
[または{)と終了位置を探して、その範囲だけを読み取る
ここで大切なのは、通知を消すことは問題を解決したことにはならないという点だ。通知を抑えたのは、ツールが受け取るJSONを守るためであり、非推奨の書き方そのものは残っている。WP-CLI側の該当箇所は、新しいバージョンで修正されている。本来の対処は、通知の元になっているコード(この場合はWP-CLI)を更新することである。
WordPressのサイトでも同じことが起きる。WP_DEBUG_DISPLAY が有効なまま非推奨の通知が画面に出ると、HTMLの途中に通知の文が挿入されるだけでなく、通知がHTTPヘッダーより先に出力されて Cannot modify header information - headers already sent という警告が続き、リダイレクトやログインが正しく動かなくなることがある。
バージョンごとに何が変わったか — 実際に実行して比べる
非推奨と削除のほかにも、バージョンアップで挙動が変わるものがある。代表的なものを、同じサーバーの PHP 8.0・8.1・8.2・8.4 で実行して比べた。
1. 数値と文字列の比較(PHP 8.0 で挙動が変わった)
var_dump(0 == "abc");
PHP 7.0 では bool(true)、PHP 8.0 以降では bool(false) になった。これは非推奨や削除ではなく、同じコードの結果そのものが変わる変更である。エラーも通知も出ないため、ログを見ても気付けない。条件分岐の結果が変わり、思わぬ分岐に進むという形で影響が出る。PHP 8.0 のように大きな変更を含むバージョンでは、このような「通知なしの挙動の変化」が一番見つけにくい。
2. 組み込み関数への null の受け渡し(PHP 8.1 で非推奨)
echo strlen(null);
PHP 8.0 では何も出ずに 0 が返る。PHP 8.1 以降では strlen(): Passing null to parameter #1 ($string) of type string is deprecated という通知が出る(結果は同じ 0)。WordPressのプラグインでは、値が空のときに null のまま文字列関数に渡しているコードが少なくないため、PHP 8.1 に上げた直後にログに大量に出やすい通知の一つだ。
3. 動的プロパティ(PHP 8.2 で非推奨)
class A {}
$a = new A;
$a->x = 1; // クラスに宣言していないプロパティを作る
PHP 8.1 までは何も出ない。PHP 8.2 以降では Creation of dynamic property A::$x is deprecated という通知が出る。前の節のWP-CLIの事例も、この変更によるものだった。
WordPressのコアも、この変更への対応を進めている。このブログのWordPress(7.1.2)の wp-includes と wp-admin を検索すると、#[AllowDynamicProperties] という属性が付いたファイルが130ある。これは「このクラスでは動的プロパティを許可する」と明示する仕組みで、既存のプラグインとの互換性を保ちながら、少しずつ宣言済みのプロパティへ移行するための措置である。
4. 暗黙の nullable 型(PHP 8.4 で非推奨)
function f(stdClass $x = null) { return 1; }
引数の型を stdClass と宣言しながら、既定値を null にする書き方である。PHP 8.2 までは何も出ないが、PHP 8.4 以降では Implicitly marking parameter $x as nullable is deprecated という通知が出る。正しい書き方は ?stdClass $x = null のように、null を許すことを型の側で明示することだ。
この4つをまとめると、次のようになる。
| 変更 | 7.0 | 8.0 | 8.1 | 8.2 | 8.4 |
|---|---|---|---|---|---|
0 == "abc" |
true |
false |
false |
false |
false |
strlen(null) |
通知なし | 通知なし | Deprecated | Deprecated | Deprecated |
| 動的プロパティ | 通知なし | 通知なし | 通知なし | Deprecated | Deprecated |
| 暗黙の nullable 型 | 通知なし | 通知なし | 通知なし | 通知なし | Deprecated |
each() |
使える | 削除(Fatal) | 削除 | 削除 | 削除 |
同じ PHP 8 系の中でも、マイナーバージョン(8.1 → 8.2 のように2番目の数字が変わるとき)ごとに新しい非推奨が加わっている。マイナーバージョンでは原則として削除は行われないが、通知の量は増えていく。8.x のどこかで出始めた非推奨は、将来のメジャーバージョンで削除の候補になる。
「致命的エラー」が出たとき、WordPressはどうなるか
非推奨の段階を見落としたまま削除のバージョンに上げると、削除された関数を呼んだ箇所で Fatal error が発生し、PHPの処理はそこで止まる。
WordPress 5.2 以降では、このとき画面に「このサイトで重大なエラーが発生しました」というメッセージを出し、管理者のメールアドレスに、原因となったプラグインまたはテーマの名前を含むメールを送る仕組み(リカバリーモード)がある。メールに記載されたリンクから管理画面に入れば、問題のプラグインを停止できる。
ただし、この仕組みが働くのはWordPressの読み込みが一定のところまで進んだ場合に限られる。wp-config.php やコアそのものがそのPHPバージョンに対応していない場合は、真っ白な画面やHTTP 500(HTTPステータスコードの基礎を参照)になり、管理画面にも入れない。その場合は、サーバーの管理画面でPHPのバージョンを元に戻すのが最短の復旧手段になる。
「必要なPHPバージョン」はどこで確かめるか
WordPressには、PHPのバージョンを確かめるための仕組みがいくつか組み込まれている。
- コアの最低要件: このブログのWordPress(7.1.2)の
wp-includes/version.phpでは$required_php_version = '7.4'と定義されている。これより古いPHPでは、そのバージョンのWordPressに更新できない - プラグイン・テーマの
Requires PHP: WordPress 5.3 以降、プラグインのヘッダーにRequires PHP:を書けるようになった。WordPressはis_php_version_compatible()(中身はversion_compare( PHP_VERSION, $required, '>=' ))で比較し、要件を満たさないプラグインの有効化や更新を止める
ただし、Requires PHP が表しているのは下限だけである。「PHP 7.4 以上」と書かれたプラグインが、PHP 8.4 で非推奨の通知を出さずに動くとは限らない。上限側の対応状況は、プラグインの更新履歴や開発元の情報、そして後述の事前確認で確かめるしかない。
もう一つの落とし穴 — 「どのPHP」が動いているか
PHPのバージョンを確かめるとき、意外に見落とされるのが「サイトを表示しているPHPと、コマンドラインで動くPHPは同じとは限らない」という点である。
このブログのサーバーでSSHから wp --info を実行すると、PHP binary: /usr/bin/php・PHP version: 8.0.30 と表示された。一方、同じサーバーの /opt 以下には PHP 7.0 から 8.5 まで多数のバージョンが並んでおり、Webサイトでどれを使うかはドメインごとに管理画面で選ぶ方式になっている。つまり、SSHで php -v や wp --info を見ても、Webで実際に動いているPHPのバージョンは分からない。
この保守ツールでは、この違いを踏まえて、WordPressの更新前にまずWebサーバーを経由して実際に動いているPHPのバージョンを取得し、それができなかったときに限ってWP-CLIのPHPのバージョンを使う、という順番で確認している。取得したバージョンがWordPressの要件に満たない場合は、更新に進まない。CLIとWebのどちらで取得した値かも記録に残しているのは、後から結果を見たときに「どちらのPHPを見て判断したのか」を区別できるようにするためだ。
逆の方向にも注意が必要である。Webサイトを PHP 8.4 に切り替えても、cron やWP-CLIから動く処理は古いPHPのまま、ということがある(cronの仕組みは「crontab構文の基礎とWP-Cronとの違い」を参照)。その場合、Webでは出ない通知やエラーが、定期実行の処理でだけ出ることになる。
安全にバージョンを上げる順番
ここまでの内容を踏まえると、PHPのバージョンアップは次の順番で進めるのが安全である。
- バックアップを取る: ファイルとデータベースの両方。PHPの切り替えは元に戻せることが多いが、切り替えた状態でプラグインの更新などを重ねると戻せなくなる(考え方は「3-2-1バックアップルール」を参照)
- 現在のバージョンで非推奨のログを取る:
WP_DEBUG_LOGを一定期間有効にし、どのプラグイン・テーマが通知を出しているかを確認する - プラグイン・テーマ・コアを先に更新する: 多くの非推奨は、開発元の新しいバージョンで修正されている。更新が止まっているプラグインは、置き換えを検討する
- 検証環境で新しいバージョンに切り替える: 本番と同じ構成のコピー(ステージング環境)で切り替え、主要な画面・フォーム・管理画面の操作を確認する。可能なら、静的解析ツール(PHP_CodeSniffer の PHPCompatibility ルールなど)で、削除された関数の使用箇所を機械的に洗い出す
- 本番を切り替え、すぐに確認する: トップページ・記事ページ・管理画面・問い合わせフォームなどを確認し、
debug.logに新しいFatal errorやDeprecatedが出ていないかを見る - 一度に大きく飛ばさない: 7.4 から 8.4 へ一気に上げると、どのバージョンの変更で壊れたのかが分かりにくい。可能なら 8.1・8.2 と段階を踏む
- 確認が終わったら
WP_DEBUGを戻す: ログファイルは肥大化するうえ、公開ディレクトリに置いたままにするのは避けたい
よくある落とし穴
- 非推奨の通知を「無害だから」と抑制して終わりにする: 抑制は応急処置であり、削除のバージョンに上げた日に同じ箇所が致命的エラーになる
- SSHの
php -vでWebのPHPバージョンを判断する: CLIとWebで別のPHPが動いていることがある Requires PHPを満たしていれば新しいPHPでも動くと考える: 表しているのは下限だけで、上限側の対応は別に確かめる必要がある- 通知が出ないから大丈夫と考える:
0 == "abc"のように、通知を出さずに結果だけが変わる変更もある - 本番で
WP_DEBUG_DISPLAYを有効にしたまま確認する: 訪問者の画面に通知が表示され、headers already sentによってリダイレクトやログインが動かなくなることもある - PHPを上げたあとでプラグインを更新する: 古いPHPで動く最後のバージョンと、新しいPHPに対応した最初のバージョンの間で、どちらの環境でも問題のない状態を作ってから切り替えるほうが、切り戻しやすい
まとめ
PHPの機能は、いきなり消えるのではなく、通常どおり使える → 非推奨(Deprecated・動作は同じで通知が出る)→ 削除(呼び出すと致命的エラー) という段階を踏んで消えていく。このブログのサーバーで each() を実行すると、PHP 7.0 では何も出ず、7.4 では通知が出ても結果は同じで、8.0 では Call to undefined function で処理が止まった。
非推奨の通知は、本番環境では画面にもログにも残らず捨てられていることが多い。しかし、それは「次のメジャーバージョンで壊れる箇所の一覧」でもある。一方で、通知が出力に混ざることで、JSONの読み取りやHTTPヘッダーなど別のものが先に壊れることもある。
バージョンを上げる前には、バックアップ → 非推奨のログの確認 → プラグイン・テーマの更新 → 検証環境での切り替え → 本番の切り替えと確認、の順で進める。そして、そのとき確かめているPHPが、Webで実際に動いているPHPと同じものかどうかも、あわせて確認しておきたい。