PHP アップグレード特集 | 起点 7.3 系
7.3.0〜7.3.33 のどれで動いていても、直す箇所は同じです。越える大きな山は PHP 8.0 の 1 つ。7.4 を挟まずに 8.4.27 へ直行する進め方を基本に、8.3.36 を選ぶ場合の読み方も書きました。
対象は PHP 7.3 系のすべてのパッチ版、7.3.0 から 7.3.33 までの 34 版です。PHP はパッチ版(3 つ目の番号)の間で後方互換性を保つ方針なので、7.3.11 で動いているサーバーも 7.3.33 で動いているサーバーも、8.x へ移すときに直す箇所は変わりません。このページの一覧は、7.3 系の最終版 7.3.33(2021-11-18 リリース)を起点に数えています。
移行先は 2026-10-21 に公開された 4 つのセキュリティ修正版です。起点と移行先の組み合わせごとの項目数は次のとおりで、7.3.0 や 7.3.19 から移す場合も同じ数になります。
PHP 7.3.0 / PHP 7.3.1 / PHP 7.3.2 / PHP 7.3.3 / PHP 7.3.4 / PHP 7.3.5 / PHP 7.3.6 / PHP 7.3.7 / PHP 7.3.8 / PHP 7.3.9 / PHP 7.3.10 / PHP 7.3.11 / PHP 7.3.12 / PHP 7.3.13 / PHP 7.3.14 / PHP 7.3.15 / PHP 7.3.16 / PHP 7.3.17 / PHP 7.3.18 / PHP 7.3.19 / PHP 7.3.20 / PHP 7.3.21 / PHP 7.3.22 / PHP 7.3.23 / PHP 7.3.24 / PHP 7.3.25 / PHP 7.3.26 / PHP 7.3.27 / PHP 7.3.28 / PHP 7.3.29 / PHP 7.3.30 / PHP 7.3.31 / PHP 7.3.32 / PHP 7.3.33
PHP 7.3.44 という版はありません。7.3 系の最後のリリースは 7.3.33 で、7.3.34 以降の番号(7.3.40、7.3.44 など)は出ていません。7.3 系で動いているなら、どのパッチ番号でもこのページが該当します。
手元の番号は、サーバー上で php -v を実行して確かめてください。ディストリビューションのパッケージでは 7.3.33-1+… のように後ろに独自の番号が付く場合があります。PHP 本体の版は先頭の 3 つの数字です。Web サーバー経由の PHP と CLI の PHP が別の版になっている構成もあるので、気になる場合は phpinfo() の出力も見ておくと確実です。
7.3 系の途中、7.3.23 で入ったセキュリティ修正(bug #79699)により、PHP は受け取った Cookie の名前を URL デコードしなくなりました。%20 などを含む名前の Cookie は、7.3.22 以前とは別の名前として $_COOKIE に入ります。
7.3.0〜7.3.22 から移す場合は、下の一覧に加えて、この点も確認してください。見る場所は、setcookie() に渡している名前と、$_COOKIE のキーで読んでいる名前の 2 か所です。名前が英数字とアンダースコアだけなら影響はありません。7.3.23〜7.3.33 で動いているサーバーは、すでにこの挙動になっています(ChangeLog 7.3.23)。
7.3 系からは、8.4.27 へ 1 回で直行するのを基本にします。7.3 と 8.4 の間で大きく崩れるのは 8.0 だけで、8.1〜8.4 は Deprecated(動作は続く警告)と限定的な削除・挙動変更が中心です。8.0 の山を越える作業はどの移行先を選んでも同じなので、一度で済ませたほうが手戻りが少なくなります。
7.4 を経由する 2 段階も考えられます。7.4 には、8.0 で消える書き方(波括弧のオフセット、括弧なしのネストした三項演算子、(real) キャスト、引数が逆順の implode())に Deprecated を出してくれる利点があります。ただ、同じものは PHPCompatibility で静的に見つかり、多くは Rector で自動的に書き換えられます。7.4 自体もサポートが終わっていて PHP2Shell の正式な修正版が出ていない版なので、途中の着地点としてサーバーに置く理由は薄いと考えます。
| 移行先 | 7.3.33 からの項目数 | サポート期限 | 向いている場合 |
|---|---|---|---|
| 8.4.27推奨 | 97(削除 20・非推奨 32・挙動変更 45) | 2028-12-31 | 特に事情がなければこれ。8.5 へ進むときの差分も 22 項目に収まる |
| 8.5.12 | 119(削除 21・非推奨 44・挙動変更 54) | 2029-12-31 | サポート期間を最長にしたい場合。使っているフレームワークや主要ライブラリが 8.5 対応を明記しているかを先に確認する |
| 8.3.36 | 81(削除 18・非推奨 25・挙動変更 38) | 2027-12-31 | IMAP 拡張を使っている、8.4 の非推奨や round() の変更を今回は持ち込みたくない、といった事情がある場合の妥協点 |
| 8.2.35選ばない | 67(削除 17・非推奨 21・挙動変更 29) | 2026-12-31 | 2026年10月の時点で残りは約 2 か月。移し終えた直後にまた移行が必要になる |
「7.3.33 から 8.3.36 へ」と決めている場合は、このページを 7.3.html?to=8.3 で開いてください。下の一覧で 8.4 と 8.5 のブロックが折りたたまれ、対象が 81 項目に絞られます。移行先のボタンでも同じ切り替えができます。
8.3.36 で対象から外れるのは [8.4] の 16 項目です。中身を見ると、8.3.36 を選ぶ理由と、選んだあとに残る宿題の両方が分かります。
imap_open() でメールを受信しているアプリは、8.3.36 ならこの問題を先送りできます。8.4 に上げるときは PECL 版をビルドするか、純 PHP のライブラリへの置き換えが必要です。function f(Foo $x = null) の非推奨(v84-02)、round() の境界値の変化(v84-05)、bcrypt の既定コストが 10 から 12 になる変更(v84-06)も 8.3.36 では起きません。ただし 8.3 のセキュリティサポートは 2027-12-31 で終わり、1 年あまりで次の移行が来ます。そのときの作業を小さくするために、今回の改修で次の 2 つは済ませておくのがおすすめです。
?Foo $x = null の形で書く。? を付ける書き方は 7.1 から使えるので、7.3 でも 8.3 でも動き、8.4 で Deprecated も出ません。Rector の規則で一括変換できます。password_hash() にコストを書いていない箇所を洗い出しておく。8.4 で既定が 12 になると、ログイン時の再ハッシュで CPU 負荷が上がります。据え置くなら ['cost' => 10] を明示し、上げるならサーバーの余力を測っておきます。道具の設定も移行先に合わせて変えます。このページ下の「コーディングエージェントに一括で任せる」の節は 8.4.27 を前提に書いているので、8.3.36 にする場合は次の値に読み替えてください。
# Docker の公式イメージ
FROM php:8.3.36-cli
# PHPCompatibility(7.3 と 8.3 の両方で動かす期間があるなら範囲で)
vendor/bin/phpcs -ps . --standard=PHPCompatibility --runtime-set testVersion 7.3-8.3 --ignore=*/vendor/*
# Rector(rector.php)
->withPhpSets(php83: true);
# PHPStan(phpstan.neon)
phpVersion: 80300
# Composer
composer why-not php 8.3.367.3 系から上げるときに、エラーを出さずに結果だけが変わるもの、または 1 か所で画面全体が止まるものを 12 件選びました。一覧の 97 項目を読む前に、まずこの 12 件が自分のコードに当てはまるかを見てください。各カードから一覧の該当項目へ飛べます。
$str{0} や $arr{'k'} は 7.3 では警告もなく動いていましたが、8.0 では Parse error になり、そのファイルが丸ごと読み込めなくなります。7.4 を経由しないので、Deprecated で事前に気づく機会がありません。
"" == 0 が true から false に変わりました。フォームの空欄を 0 とみなす if ($qty == 0) のような判定は、エラーを出さずに分岐だけが変わります。in_array() や switch も同じ規則で比べます。
"abc" + 1 は TypeError で止まり、"5 apples" + 1 は Warning を出して 6 になります。CSV やフォームの値をそのまま計算に回しているコードは、入力によって止まるようになります。
PDO の既定のエラーモードが例外になりました。if (!$stmt) で失敗を判定していた処理には到達せず、捕まえていなければその場で Fatal error になります。
mysqli の既定も例外を投げる設定に変わりました。mysqli_query(...) or die(mysqli_error($link)) という古い定型句のエラー処理は、もう実行されません。
trim(null) や htmlspecialchars(null) が Deprecated になります。DB の NULL 列や未設定の $_GET をそのまま文字列関数に渡すコードが多いと、ログの大半がこの警告で埋まります。
PDO MySQL(エミュレートプリペアド有効時)と PDO SQLite が、数値の列を文字列ではなく int や float で返します。$row['id'] === '1' が false になり、json_encode() の出力も "1" から 1 に変わります。
宣言していないプロパティへの代入が Deprecated になります。7.3 時代のクラスは、コンストラクタの外で $this->foo = … と後付けしているものが珍しくありません。
strtolower() や ucfirst() は ASCII だけを変換するようになりました。setlocale() でロケールを設定し、ASCII 以外の文字の変換をこれらに任せていた場合に結果が変わります。
境界に数字の文字列と数字以外の文字列を混ぜると、数の並びではなく文字の並びが返るようになりました。step に 0 を渡すと ValueError です。文字列のまま境界を渡している箇所を探します。
→ 一覧の該当項目へfunction f(Foo $x = null) が Deprecated になります。7.3 時代のコードには大量にある書き方で、件数はこれが最も多くなりがちです。8.3.36 を選ぶ場合は今回の対象外ですが、直しておく価値はあります。
内部の実装が書き換わり、一部の境界値で丸めの結果が変わりました。金額や税額の計算で round() を使っている箇所は、移行前と移行後で同じ入力の結果を突き合わせます。8.3.36 なら対象外です。
7.3 系から上げるときに確認する変更を、PHP 7.4・8.0・8.1・8.2・8.3・8.4・8.5 の順に並べました。後ろの版へ進むほど項目が積み上がる累積の一覧で、移行先が 8.4.27 なら 7.4〜8.4 のブロック(97 項目)、8.3.36 なら 7.4〜8.3 のブロック(81 項目)が対象です。7.3 で入った変更はすでに手元の環境で動いているので含めていません(7.3.22 以前から上げる場合の Cookie 名だけは上の注意を見てください)。
区分は 3 つです。削除は Fatal error・Parse error・Error 例外になるか、機能や拡張そのものがなくなるもの。非推奨は E_DEPRECATED が出るものの動作は続くもの(将来の版で削除)。挙動変更はエラーを出さずに結果やエラーの種類が変わるもので、テストで差を見つけるしかありません。「要確認」の印は、一次情報の本文で裏取りを続けている項目です。各項目には v80-01 のような id があり、エージェントへの指示や報告でそのまま使えます。
※以下の一覧に載せたコード・コマンド・プロンプトはあくまで例です。最終確認は php.net の公式 ChangeLog・移行ガイドで行ってください。内容の利用により生じた損害について当方は責任を負いません(詳しくはページ末尾の免責事項)。
移行先 8.4.27 まで
公式: PHP 7.4 移行ガイド(日本語) / 後方互換性のない変更 / 非推奨 / その他の変更 / ChangeLog 7.4.0 / UPGRADING
ext/interbase、ext/recode、ext/wddx を本体から削除(PECL へ)
必要な修正PDO_Firebird、iconv() / mb_convert_encoding()、json_encode() / serialize() に移行
括弧なしのネストした三項演算子 $a ? $b : $c ? $d : $e を非推奨([8.0] で Fatal)
必要な修正括弧で結合順を明示する。PHP の三項は左結合なので旧来の意味は ($a ? $b : $c) ? $d : $e
波括弧による文字列・配列オフセットアクセス $str{0} / $arr{'k'} を非推奨([8.0] で Parse error)
必要な修正$str[0] / $arr['k'] に置換
(real) キャストと is_real() を非推奨([8.0] で削除)
必要な修正(float) と is_float()
array_key_exists() にオブジェクトを渡すことを非推奨([8.0] で TypeError)
必要な修正isset($obj->prop) か property_exists()
implode() の引数逆順 implode($pieces, $glue) を非推奨([8.0] で TypeError)
必要な修正implode($glue, $pieces) に統一(join() も同様)
get_magic_quotes_gpc() / get_magic_quotes_runtime()、money_format()、hebrevc()、convert_cyr_string()、ezmlm_hash()、restore_include_path()、FILTER_SANITIZE_MAGIC_QUOTES、allow_url_include INI、mb_strrpos() の第 3 引数へのエンコーディング指定、親クラスのないクラス内での parent::、ldap_control_paged_result()、Reflection*::export()、クロージャからの $this のアンバインド、base_convert() / bindec() / octdec() / hexdec() への不正文字 を非推奨(多くは [8.0] で削除)
必要な修正呼び出しを削除(常に false)、NumberFormatter::formatCurrency()、hebrev()、iconv()、FILTER_SANITIZE_ADD_SLASHES、オフセットを明示して第 4 引数にエンコーディング、parent:: を削除、ldap_search() の controls 引数、(string)$reflection、入力文字の検証
null / bool / int / float / リソースに配列アクセス($null['key'])すると E_NOTICE「Trying to access array offset on value of type null」([8.0] で E_WARNING)
必要な修正isset() / ?? で防御する。DB の取得結果が false のときに $row['id'] する典型パターンに注意
PASSWORD_DEFAULT が int 1 から null に、PASSWORD_BCRYPT が '2y' に(文字列化)。password_hash($p, 1) のような数値直書きは動くが === 比較は壊れる
必要な修正定数を使う
fread() / fwrite() が失敗時に false を返す(以前は '' / 0)。openssl_random_pseudo_bytes() は失敗時に例外
必要な修正=== false で判定する
DateInterval 同士の比較(== / <)は警告を出して常に false。get_object_vars(ArrayObject) が内部配列ではなく ArrayObject 自身のプロパティを返す
必要な修正比較は days や秒に換算、getArrayCopy() を使う
fn が予約語に(function fn() や fn という名前のメソッド・クラスは Parse error)
必要な修正リネーム
新規組み込み関数の追加(mb_str_split / password_algos / get_mangled_object_vars 等)。同名の自作関数(mb_str_split のポリフィルが典型)があると Fatal
必要な修正function_exists() ガード
公式: PHP 8.0 移行ガイド(日本語) / 後方互換性のない変更 / 非推奨 / その他の変更 / ChangeLog 8.0.0 / UPGRADING
文字列と数値の緩い比較:数値と非数値文字列の == は数値を文字列化して比較する。0 == "0"、0 == "0.0"、42 == " 42" のような数値文字列との比較は true のままだが、0 == "foo"、0 == ""、42 == "42foo" は true から false に変わる(null == 0 は true のまま)。switch (0) { case "foo": } や in_array("foo", [0]) も影響
必要な修正=== と明示キャストに置き換える。「空文字列を 0 とみなす」前提の入力処理(if ($qty == 0))を全件確認。移行先で最も多く静かに壊れる変更
数値文字列の扱い:非数値文字列("abc")との算術・ビット演算は TypeError、先頭のみ数値("5 apples")は E_WARNING「A non-numeric value」で 5 として計算。「Illegal string offset」も TypeError。末尾の空白を含む数値文字列("123 ")は数値扱い
必要な修正入力を (int) / (float) で明示変換、is_numeric() で検証。計算前の null / '' は 0 に正規化
算術・ビット演算子(+ - * / ** % << >> & |(ビット和) ^ ~ ++ --)を配列・リソース・(演算子オーバーロードのない)オブジェクトに適用すると TypeError。配列 + 配列(結合)だけは可
必要な修正型を確認して演算する。$count + $arr のような誤りを修正
未定義定数の裸参照($arr[key]、echo FOO)は Error「Undefined constant」(以前は文字列として解釈して警告)
必要な修正$arr['key'] のように引用符を付ける。定数は define() / const
警告から Error 例外に昇格:非オブジェクトへのプロパティ書き込み($null->x = 1。null / false / '' から stdClass を自動生成しない)、PHP_INT_MAX キーの次への追加、不正な型(配列・オブジェクト)をキーや文字列オフセットに使用、スカラーへの配列インデックス書き込み($int[0] = 1)、非配列 / 非 Traversable の展開、内部関数への引数数の誤り(ArgumentCountError)、count() への非 Countable(TypeError)
必要な修正該当コードを修正。$obj = null; $obj->x = 1; のパターンは $obj = new stdClass; を明示
Notice から Warning に昇格:未定義変数・未定義プロパティ・未定義配列キーの読み取り、非オブジェクトのプロパティ読み取り、非配列への配列アクセス、配列→文字列変換、リソースをキーに使用、null / bool / float の文字列オフセット、範囲外の文字列オフセット、空文字列の文字列オフセット代入。複数バイトの文字列オフセット代入は警告
必要な修正isset() / ?? / array_key_exists() で防御。ログ量が激増するので display_errors=Off・log_errors=On を確認
@ 演算子で致命的エラー(E_ERROR / E_CORE_ERROR / E_COMPILE_ERROR / E_USER_ERROR / E_RECOVERABLE_ERROR / E_PARSE)を抑制できない。@ 使用時に error_reporting() が 0 ではなく 4437 を返す
必要な修正エラーハンドラ内の if (error_reporting() == 0) return false; を if (!(error_reporting() & $errno)) return false; に書き換える
error_reporting の既定が E_ALL(以前は E_NOTICE と E_DEPRECATED を除外)。display_startup_errors が既定で有効
必要な修正本番は display_errors=Off、log_errors=On、error_reporting を明示。画面に Deprecated が出て崩れる事故を防ぐ
言語機能を削除:(real) / (unset) キャスト、track_errors / $php_errormsg、create_function()、each()、__autoload()、大文字小文字非区別の定数(define の第 3 引数 true)、array_key_exists() へのオブジェクト、$a{0} の波括弧オフセット、$errcontext、PHP 4 形式コンストラクタ(同名メソッドは通常メソッド扱い)、非静的メソッドの静的呼び出し(is_callable('Foo::bar') も false)、クロージャからの $this アンバインド、親のないクラスでの parent(コンパイル時 Fatal)、括弧なしのネスト三項
必要な修正(float) / = null、error_get_last()、無名関数、foreach、spl_autoload_register()、定数名統一、isset() / property_exists()、$a[0]、第 5 引数削除、__construct()、インスタンス経由、括弧で明示
内部関数の引数型検査が厳格化:型不一致で TypeError(strlen([])、array_merge(null) 等)、値が不正で ValueError(str_repeat($s, -1)、array_fill の負の個数 等)。以前は警告+null
必要な修正呼び出し前に型を保証する。null を渡しているなら [8.1] の非推奨と合わせて ?? '' / ?? [] で正規化
match / mixed が予約語に。#[ はコメントではなくアトリビュート開始(#[ で始まる行コメントが構文エラー)。名前空間名に空白不可(Foo \ Bar)、予約語を名前空間セグメントに使用可(new\x の解釈が変わる)
必要な修正#[ で始まるコメントを # [ や // に修正、クラス名・関数名を変更
連結演算子 . の優先順位が + - << >> より低くなった。"Sum: " . $a + $b は ("Sum: " . $a) + $b から "Sum: " . ($a + $b) に
必要な修正括弧で明示(7.4 で E_DEPRECATED が出ていた箇所)
定数で null に解決されるデフォルト値(function f(int $x = CONST_NULL))が暗黙に nullable にならない
必要な修正?int $x = CONST_NULL か int $x = null に
assert() は既定で AssertionError 例外を投げる(assert.exception=1)。文字列引数を評価しない。assert.quiet_eval / ASSERT_QUIET_EVAL を削除。名前空間内で assert() を定義不可
必要な修正assert('$a == $b') は assert($a == $b) に。本番は zend.assertions=-1。旧挙動が必要なら assert.exception=0
マジックメソッドのシグネチャ検査(__toString(): string、__get(string $name): mixed、__isset(): bool、__sleep(): array、__set_state(array): object 等。型を宣言している場合に不一致なら Fatal)。call_user_func_array() の文字列キーは名前付き引数として解釈。名前付き引数の導入で内部関数の引数名が正式化(一部の引数名が変更)
必要な修正型宣言を規定どおりに、call_user_func_array($f, array_values($args)) に
継承時のシグネチャ非互換(LSP 違反)は常に Fatal(以前は一部警告)。トレイトの抽象メソッドのシグネチャ検査。トレイトのエイリアス(use T1, T2 { func as other; })で曖昧な参照は Fatal。private メソッドへの final は警告
必要な修正子クラスのメソッド宣言を親に合わせる(引数の追加・型の狭め・戻り値の広げに注意)
float → string の変換がロケール非依存に(setlocale(LC_ALL, 'de_DE') でも 3.14)。var_dump() / debug_zval_dump() の float は serialize_precision で出力(0.1 + 0.2 が float(0.30000000000000004))。起動時ロケールは常に "C"(環境変数 LC_CTYPE を継承しない)
必要な修正ロケール依存の数値整形は number_format() / NumberFormatter。setlocale() を明示的に呼ぶ
ソートが安定化(同値要素の順序が入力順に固定されるため、以前と結果が変わりうる)。コールバックが参照引数を取る場合は警告(array_filter / array_reduce 等)。ソート比較関数が bool を返すと Deprecated
必要な修正比較関数は <=> で -1 / 0 / 1 を返す
リソースからオブジェクトへ移行:curl_init() → CurlHandle、curl_multi_init() / curl_share_init()、GD(imagecreate* → GdImage)、openssl_x509_read() / openssl_pkey_new() / openssl_csr_new()、socket_create() → Socket、xml_parser_create() → XMLParser、XMLWriter、inflate_init() / deflate_init()、shmop_open()、msg_get_queue() / sem_get() / shm_attach()、enchant_broker_init()。is_resource() が false になる。curl_close() / imagedestroy() / xml_parser_free() は no-op([8.5] で非推奨)
必要な修正is_resource($h) を $h !== false か instanceof に書き換える。get_resource_type() を使うコードを修正
PDO の既定エラーモードが PDO::ERRMODE_SILENT から PDO::ERRMODE_EXCEPTION に。PDO::query(string $query, ?int $fetchMode = null, mixed ...$args) と PDOStatement::setFetchMode(int $mode, mixed ...$args) のシグネチャ変更。PDO::inTransaction() が実際のトランザクション状態を返す(暗黙コミット後は false)
必要な修正戻り値 false 判定(if (!$stmt))を try / catch (PDOException) に。旧挙動が必要なら new PDO(..., [PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENT])
関数・定数を削除:hebrevc()、convert_cyr_string()、money_format()、ezmlm_hash()、restore_include_path()、get_magic_quotes_gpc() / get_magic_quotes_runtime()、fgetss() / gzgetss() / SplFileObject::fgetss()、string.strip_tags フィルタ、FILTER_SANITIZE_MAGIC_QUOTES、FILTER_FLAG_SCHEME_REQUIRED / FILTER_FLAG_HOST_REQUIRED、INPUT_REQUEST / INPUT_SESSION、image2wbmp() / png2wbmp() / jpeg2wbmp()、read_exif_data()、gmp_random()、INTL_IDNA_VARIANT_2003、Normalizer::NONE、ldap_sort() / ldap_control_paged_result() / ldap_control_paged_result_response()、mbstring.func_overload と MB_OVERLOAD_*、mbereg 系エイリアス、mb_ereg_replace() の e オプション、mb_parse_str() / parse_str() の 1 引数呼び出し、implode() の逆順、mktime() / gmmktime() の引数なし、pg_connect() の複数引数形式、pg_lo_import() / pg_lo_export() の接続を末尾に渡す形式、oci_internal_debug()、imap_header()、ZipArchive::OPSYS_Z_CPM、DOM の未実装クラス(DOMConfiguration 等)とメソッド(DOMNode::getFeature 等)、pdo_odbc.db2_instance_name INI
必要な修正hebrev()、iconv()、NumberFormatter、削除、呼び出し削除、fgets()+strip_tags()、FILTER_SANITIZE_ADD_SLASHES、フラグ削除、imagewbmp()、exif_read_data()、gmp_random_bits()、INTL_IDNA_VARIANT_UTS46、Normalizer::FORM_C 等、usort、mb_* を明示、mb_ereg_*、mb_ereg_replace_callback()、parse_str($s, $out)、implode($glue, $pieces)、time()、pg_connect('host=... dbname=...')、接続を第 1 引数に、imap_headerinfo()、OPSYS_CPM
ext/xmlrpc を本体から削除(PECL へ)
必要な修正PECL xmlrpc(メンテナンス縮小)か、純 PHP の XML-RPC ライブラリ / JSON-RPC に移行
標準関数の挙動変更:strpos() 等の needle は常に文字列(int を chr() 扱いしない)で空文字列も可、substr() / substr_count() / array_splice() の length に null 可、substr() の範囲外オフセットは false ではなく '' を返す、vsprintf() / vprintf() / vfprintf() の args は配列必須、password_hash() の salt は無視して警告、quotemeta('') は ''、parse_url() が空のクエリ / フラグメント("?" / "#")を null ではなく '' で区別、__sleep() が返す存在しないプロパティは無視、crypt() は salt 必須で DES フォールバック削除(未知の salt は *0)、HTTP ストリームラッパーが HTTP/1.1 を名乗る、Windows の exec() 系の引用規則統一、CURLOPT_POSTFIELDS にオブジェクト不可((array) キャストが必要)、openssl_seal() / openssl_open() の method 必須、pg_fetch_all() が 0 件で false ではなく []、odbc_connect() が接続を再利用しない、enchant_* が null ではなく []、imagecropauto() の mode に -1 不可、sem_get() の auto_release が bool、tidy::repairString() / repairFile() が static、XMLReader::open() / XML() が static(継承クラスで宣言変更が必要)、SplFixedArray が IteratorAggregate(rewind / current / key / next / valid を削除)、SplFileObject::seek() が常に行頭へ、SplDoublyLinkedList::push() / unshift() / SplQueue::enqueue() が void、spl_autoload_register() は常に TypeError、SplHeap::compare() のシグネチャ固定、Phar メタデータを自動 unserialize しない、Reflection の invoke() / newInstance() が可変長引数、ReflectionType::__toString() の出力形式変更(?int など)、ReflectionMethod::isConstructor() がインターフェースでも true、PCRE の不正エスケープをリテラル扱いしない(X 修飾子は無視)、Tokenizer の T_COMMENT に末尾改行を含まない・T_NAME_QUALIFIED 等の新トークン、disable_functions の関数は未定義扱い(再定義可)、data:// への書き込み不可、匿名クラス名に親クラス名を含む、debug_backtrace() / getTrace() が引数の参照を返さない、捕捉されない例外でもデストラクタ実行、コンストラクタ内 exit() でデストラクタ不実行、zlib.output_compression が image/* でも無効にならない、ソースファイル中の予期しない NUL バイトは ParseError
必要な修正各項目の置換先に従う。特に substr() の false 判定、parse_url() の null 判定、strpos($s, 65) の int needle、SplFixedArray の手動反復、XMLReader を継承したクラスを確認
必須引数の後にデフォルト値付き引数を置く function f($a = [], $b) を非推奨($a は必須扱いになりデフォルト値は無視される。Type $a = null の形だけは暗黙 nullable として許容、ただし [8.4] で非推奨)
必要な修正引数順を入れ替えるか、デフォルト値を外す
ReflectionParameter::getClass() / isArray() / isCallable()、enchant_broker_set_dict_path() / enchant_broker_get_dict_path() / enchant_dict_add_to_personal() / enchant_dict_is_in_session()、libxml_disable_entity_loader()、pgsql の旧エイリアス(pg_errormessage / pg_numrows / pg_numfields / pg_fieldname / pg_fieldsize / pg_fieldtype / pg_fieldnum / pg_result / pg_freeresult / pg_cmdtuples / pg_getlastoid / pg_setclientencoding / pg_clientencoding / pg_lo* 系)、zip_open() 等の手続き型 Zip 関数、openssl_x509_free() / openssl_pkey_free() / shmop_close()、PGSQL_LIBPQ_VERSION_STR を非推奨
必要な修正getType()、enchant_broker_set_dict_path は削除、libxml_disable_entity_loader は呼ばない(8.0 以降は既定で安全)、pg_last_error() / pg_num_rows() 等の正式名、ZipArchive クラス、free 系は削除、PGSQL_LIBPQ_VERSION
新規組み込み関数の追加(str_contains / str_starts_with / str_ends_with / fdiv / get_debug_type / get_resource_id / preg_last_error_msg)。同名の自作関数(str_contains のポリフィルが典型)があると Fatal
必要な修正function_exists() ガード
公式: PHP 8.1 移行ガイド(日本語) / 後方互換性のない変更 / 非推奨 / その他の変更 / ChangeLog 8.1.0 / UPGRADING
内部関数の非 nullable 引数への null 渡しを非推奨(strlen(null)、trim(null)、htmlspecialchars(null)、str_replace の subject に null、strpos(null, ...)、mb_strlen(null)、preg_match(..., null)、explode(',', null)、json_decode(null) 等)。PHP 9 で TypeError 予定
必要な修正$v ?? '' や (string) で正規化する。DB の NULL 列や未設定の $_GET を文字列関数に直接渡す箇所が大量に該当するので、ログで Deprecated を集計して潰す
mysqli の既定エラー報告モードが MYSQLI_REPORT_OFF から MYSQLI_REPORT_ERROR|MYSQLI_REPORT_STRICT に。接続失敗や SQL エラーで mysqli_sql_exception が投げられ、mysqli_query(...) or die(mysqli_error()) 式のエラー処理に到達しない
必要な修正try / catch (mysqli_sql_exception) に書き換える。暫定的に旧挙動へ戻すなら接続前に mysqli_report(MYSQLI_REPORT_OFF)
htmlspecialchars() / htmlentities() / htmlspecialchars_decode() / html_entity_decode() / get_html_translation_table() の既定フラグが ENT_COMPAT|ENT_HTML401 から ENT_QUOTES|ENT_SUBSTITUTE|ENT_HTML401 に。シングルクォートが ' に変換され、不正なバイト列は空文字列ではなく U+FFFD に置換される
必要な修正旧挙動が必要なら ENT_COMPAT を明示。出力の期待値(テスト・スナップショット・属性値内のクォート処理)を見直す
PDO MySQL(エミュレートプリペアド有効時)と PDO SQLite が整数・浮動小数点の列を文字列ではなく int / float で返す(ネイティブ型)
必要な修正=== '1' のような厳密比較、json_encode() の出力("1" → 1)、文字列連結前提のコードを修正。旧挙動は PDO::ATTR_STRINGIFY_FETCHES => true。mysqli でも mysqlnd はネイティブ型を返す点に注意
$GLOBALS 全体への書き込み($GLOBALS = [])、参照取得(&$GLOBALS、foreach ($GLOBALS as &$v))、配列関数による破壊的操作(array_pop($GLOBALS))はコンパイルエラー。$GLOBALS['x'] = 1 や $GLOBALS の読み取り・コピーは従来どおり
必要な修正個別キーの操作に書き換える
継承したメソッド内の static 変数が親クラスと子クラスで共有される(以前は子クラスごとに独立)
必要な修正子クラスごとの状態が必要なら static プロパティや配列に切り替える
リソースからオブジェクトへ移行:finfo_open() → finfo、ftp_connect() → FTP\Connection、imap_open() → IMAP\Connection、ldap_connect() → LDAP\Connection、ldap_search() → LDAP\Result、pg_connect() → PgSql\Connection、pg_query() → PgSql\Result、pg_lo_open() → PgSql\Lob、pspell_new() → PSpell\Dictionary、imageloadfont() → GdFont
必要な修正is_resource() 判定を !== false か instanceof に
内部クラス・インターフェースのメソッドをオーバーライドする際の戻り値型の非互換(ArrayAccess::offsetGet() / IteratorAggregate::getIterator() / Countable::count() / JsonSerializable::jsonSerialize() / Iterator::current() などを戻り値型なしで実装)
必要な修正戻り値型(: mixed / : Iterator / : int 等)を宣言するか、7.x 互換を保つなら #[\ReturnTypeWillChange] を付ける
Serializable インターフェースを __serialize() / __unserialize() なしで実装することを非推奨
必要な修正両マジックメソッドを追加実装する(Serializable は PHP 9 で削除予定)
精度を失う float から int への暗黙変換($arr[1.5]、"1.5" % 2、int 型引数への 1.5、1.5 << 1)を非推奨
必要な修正明示的に (int) キャスト
false からの自動配列化($a = false; $a[] = 1;)を非推奨(null / 未定義からは引き続き可)
必要な修正$a = []; で初期化する。関数が失敗時に false を返し、それに [] 代入するパターンに注意
トレイトの static メソッド・static プロパティへの直接アクセス(MyTrait::method() / MyTrait::$prop)を非推奨
必要な修正トレイトを使用するクラス経由でアクセス
date_sunrise() / date_sunset()、strftime() / gmstrftime()、strptime()、mhash*() 系(mhash / mhash_count / mhash_get_block_size / mhash_get_hash_name / mhash_keygen_s2k)、FILTER_SANITIZE_STRING / FILTER_SANITIZE_STRIPPED、odbc_result_all()、PDO::FETCH_SERIALIZE、mysqli::init()、key() / current() / next() / prev() / reset() / end() のオブジェクトへの使用、ctype_*() への非文字列引数、pgsql 関数の接続引数省略、auto_detect_line_endings INI、filter.default INI、oci8.old_oci_close_semantics INI、IntlCalendar::roll() への bool を非推奨
必要な修正date_sun_info()、date() / DateTime::format() / IntlDateFormatter(日本語の曜日・月名は IntlDateFormatter)、date_parse_from_format() / DateTime::createFromFormat()、hash() / hash_hmac()、htmlspecialchars()、odbc_fetch_array のループ、独自シリアライズ、new mysqli()、get_mangled_object_vars() / ArrayIterator、(string) キャスト、接続を明示、ファイル読み込みで改行を正規化
mysqli_fetch_fields() / mysqli_fetch_field_direct() の max_length が常に 0。version_compare() が未文書化の演算子省略形を拒否
必要な修正最大長が必要なら mysqli_result::fetch_field() 前に store_result、version_compare の第 3 引数は 'lt' / 'ge' 等の正式表記
readonly / never が予約語に(クラス名・関数名に使えない)。enum が宣言構文のキーワードになり、enum の直後に識別子が続くコードの解釈が変わる。新規組み込み関数の追加(array_is_list / enum_exists / fsync / fdatasync)で同名の自作関数は Fatal
必要な修正リネーム、function_exists() ガード
公式: PHP 8.2 移行ガイド(日本語) / 後方互換性のない変更 / 非推奨 / その他の変更 / ChangeLog 8.2.0 / UPGRADING
動的プロパティの生成(宣言していないプロパティへの $obj->foo = 1)を非推奨(stdClass と __get() / __set() 実装クラスは対象外)。PHP 9 で Error 予定
必要な修正プロパティを宣言する(推奨)。古いクラスを一括で延命するなら #[\AllowDynamicProperties] をクラスに付与(子クラスにも適用)。他人のオブジェクトにデータを付けるなら WeakMap
"${var}" / "${expr}" 形式の文字列補間を非推奨
必要な修正"$var" / "{$var}" / "{${expr}}" に書き換える。テンプレート系の古いコードは sed で一括置換しやすい
utf8_encode() / utf8_decode() を非推奨([9.0] で削除予定)
必要な修正mb_convert_encoding($s, 'UTF-8', 'ISO-8859-1') / iconv()。Shift_JIS 変換の意図で utf8_encode を誤用しているコード(Latin-1 専用)は根本から直す
相対的なコールアブル:"self::method" / "parent::method" / "static::method" / ["self", "method"] / ["parent", "method"] / ["static", "method"] / ["Foo", "Bar::method"] / [new Foo, "Bar::method"] を非推奨
必要な修正[self::class, 'method'] / [$this, 'method'] / クロージャ($this->method(...))に置換
mbstring の QPrint / Base64 / Uuencode / HTML-ENTITIES「エンコーディング」を非推奨(mb_convert_encoding($s, 'HTML-ENTITIES', 'UTF-8') が典型)
必要な修正mb_encode_numericentity() / htmlspecialchars() / htmlentities()、base64_encode()、quoted_printable_encode()、convert_uuencode() に置換
SplFileInfo::_bad_state_ex() を非推奨
必要な修正呼ばない(内部用)
strtolower() / strtoupper() / stripos() / strripos() / lcfirst() / ucfirst() / ucwords() / str_ireplace() がロケール非依存(ASCII のみ変換)に
必要な修正マルチバイトやロケール依存の変換は mb_strtolower() / mb_strtoupper() / mb_convert_case() に
str_split('') が [''] ではなく [] を返す
必要な修正空配列を前提に修正(foreach は影響なし、count() や [0] 参照に注意)
ksort() / krsort() の SORT_REGULAR が数値文字列を PHP 8 の比較規則(文字列と数値の比較変更)で並べる
必要な修正順序依存のコードを確認。数値キーなら SORT_NUMERIC を明示
var_export() がクラス名を先頭 \ 付きの完全修飾名で出力(\Foo\Bar::__set_state(...))。strcmp() / strcasecmp() / strncmp() / strncasecmp() / substr_compare() が差分値ではなく -1 / 0 / 1 を返す
必要な修正var_export の出力を文字列比較・保存しているコードを修正。strcmp の戻り値を差分値として使うコード(=== 2 など)を < 0 / > 0 判定に
glob() は open_basedir で全ファイルが除外されると false ではなく [] を返す。FilesystemIterator のコンストラクタに flags を明示指定した場合、SKIP_DOTS が自動では付かなくなった(. と .. が列挙される)。ODBC / PDO_ODBC が接続文字列中のユーザー名・パスワードをエスケープする
必要な修正戻り値判定の修正、flags を渡す場合は FilesystemIterator::SKIP_DOTS を含める、DSN にユーザー名を埋め込んでいる場合は引数と二重指定しない
mysqli が libmysqlclient でビルドできなくなり mysqlnd 必須。mysqli.reconnect INI と自動再接続機能を削除
必要な修正接続切れは明示的に再接続する([8.4] で mysqli_ping() も非推奨)。ディストリビューションのパッケージ構成を確認
新規組み込み関数の追加(mysqli_execute_query / openssl_cipher_key_length / curl_upkeep / memory_reset_peak_usage / ini_parse_quantity / libxml_get_external_entity_loader 等)。同名の自作関数は Fatal
必要な修正function_exists() ガード
公式: PHP 8.3 移行ガイド(日本語) / 後方互換性のない変更 / 非推奨 / その他の変更 / ChangeLog 8.3.0 / UPGRADING
range() の変更:境界にオブジェクト・リソース・配列を渡すと TypeError、step に 0 や INF / NAN は ValueError、増加範囲に負の step は ValueError、int に解釈できる float の step は int 扱い、空文字列の境界は E_WARNING(0 扱い)、片方が数字の文字列ならもう片方も文字列として扱い文字の範囲を返す(range('9', 'A') が [9,8,...,0] から ['9', ':', ';', ..., 'A'] に)
必要な修正境界と step の型を揃える。文字列の数字を渡している箇所は (int) キャスト
空配列に負のインデックス $n を代入すると次の暗黙キーが $n + 1 になる(以前は 0)。例:$a = []; $a[-5] = 'x'; $a[] = 'y'; → キーは -5, -4
必要な修正負のキーの後に [] 追加するコードを確認
トレイトの static プロパティが親クラスから継承したものを再宣言する(クラスごとに独立したストレージ)。クラス定数の可視性分散をインターフェース継承でも検査
必要な修正共有を期待していたコードを修正
Date 拡張が DateError / DateException 階層の例外・エラーを投げる(new DateTime('不正') は DateMalformedStringException、new DateTimeZone('不正') は DateInvalidTimeZoneException、DateInterval の不正文字列は DateMalformedIntervalStringException など。以前は警告や汎用 Exception)
必要な修正catch (Exception $e) は引き続き捕捉できる。例外クラス名で分岐しているコードとメッセージのスナップショットを確認
proc_get_status() を複数回呼んでも正しい値を返す(結果をキャッシュ、"cached" キー追加)。呼び出しスタックの上限に近いコードは Error 例外(zend.max_allowed_stack_size)。WeakMap の自己参照キーが GC で回収される。ZTS ビルド(Linux)で Zend Max Execution Timers が既定で有効
必要な修正深い再帰のコードはスタック使用量を見直す
DOM:親のないノードへの after() / before() / replaceWith() が例外ではなく no-op、DOMParentNode / DOMChildNode のメソッドが document なしでも動作、createAttributeNS() の既定名前空間・接頭辞衝突の修正、DOM クラスに新メソッド・プロパティが追加されたため継承クラスの同名宣言は互換が必要
必要な修正DOMDocument 等を継承しているクラスの宣言を確認
opcache.consistency_checks INI を削除。FFI の void 関数は CData ではなく null を返す。Phar / SNMP のクラス定数に型を宣言
必要な修正php.ini から削除、FFI の戻り値判定を修正
number_format() が負の decimals を解釈する(num を abs(decimals) 桁で整数部側に丸める。以前は無視)。file() が無効なフラグ(FILE_APPEND など)を拒否
必要な修正負の decimals を渡しているなら 0 に、file() のフラグを確認
unserialize() のエラーが E_NOTICE から E_WARNING に。array_sum() / array_product() が + / * 演算子と同じ型変換規則で計算し、対応しない型(配列・オブジェクト)には E_WARNING を出す。highlight_file() / highlight_string() の HTML 出力形式が変更(<pre><code> でラップ、改行を \n に)
必要な修正エラーハンドラの扱いを確認、配列の合計に非数値が混ざっていないか確認、highlight 系の出力に CSS を当てている場合は調整
空文字列・非数値文字列・英数字以外の文字列への ++、空文字列・非数値文字列への -- を非推奨(Perl 風の文字列インクリメント "a"++ → "b" はソフト非推奨)
必要な修正数値は (int) キャストしてから演算、文字列の連番は str_increment() / str_decrement()
get_class() / get_parent_class() の引数なし呼び出しを非推奨
必要な修正static::class / self::class / get_class($this) / get_parent_class($this)
MT_RAND_PHP(mt_srand の旧実装モード)、assert_options()、ASSERT_ACTIVE / ASSERT_BAIL / ASSERT_CALLBACK / ASSERT_EXCEPTION / ASSERT_WARNING 定数、assert.* INI(assert.active / assert.warning 等)を非推奨
必要な修正zend.assertions INI で制御。assert_options(ASSERT_ACTIVE, 0) 等の呼び出しを削除
SQLite3::enableExceptions(false) を非推奨(将来は例外のみ)。U_MULTIPLE_DECIMAL_SEPERATORS 定数(→ U_MULTIPLE_DECIMAL_SEPARATORS)、NumberFormatter::TYPE_CURRENCY、ldap_connect($host, $port) の分離指定(→ URI 形式)、mb_strimwidth() の負の幅、Phar::setStub(resource, length)、FFI::new() / cast() / type() の静的呼び出し、ReflectionProperty::setValue() の 1 引数呼び出し(static は第 1 引数 null)、dba_fetch() の第 3 引数 dba、ZipArchive::FL_RECOMPRESS を非推奨
必要な修正括弧内の代替へ。SQLite3 は例外ベースのエラー処理に移行
新規組み込み関数の追加(json_validate / str_increment / str_decrement / mb_str_pad / stream_context_set_options / ldap_exop_sync 等)。同名の自作関数(json_validate のポリフィルが典型)は Fatal
必要な修正function_exists() ガード
公式: PHP 8.4 移行ガイド(日本語) / 後方互換性のない変更 / 非推奨 / 削除された拡張モジュール / その他の変更 / ChangeLog 8.4.0 / UPGRADING
IMAP(imap_*)、OCI8、PDO_OCI、PSpell 拡張を本体から削除(PECL へ)
必要な修正pecl install でビルドし直す(IMAP の PECL 版は libc-client / uw-imap 依存で入手が難しいため、Webklex/php-imap や ddeboer/imap など純 PHP ライブラリへの移行を推奨)。OCI8 / PDO_OCI は PECL 版、PSpell は enchant に移行
暗黙の nullable 引数 function f(T $x = null) を非推奨(PHP 9 で Error 予定)。null デフォルトの後に必須引数が続く function f(T $a, T2 $b = null, T3 $c) は、[8.0] 以来の「必須引数より前のオプション引数」非推奨にも該当
必要な修正?T $x = null か T|null $x = null に。必須引数より前なら ?T2 $b にしてデフォルト値を外す。Rector(ExplicitNullableParamTypeRector)で一括変換可
E_STRICT 定数を非推奨(E_STRICT エラーレベル自体は削除)。trigger_error() に E_USER_ERROR を渡すことを非推奨。クラス名 _ を非推奨。0 ** -1 と pow(0, -1)(0 の負乗)を非推奨
必要な修正error_reporting(E_ALL & ~E_STRICT) は E_ALL に、trigger_error(E_USER_ERROR) は例外か exit() に、fpow() を使う
exit() / die() が言語構造から関数相当に変更。exit; の形も引き続き可だが、配列など不正な型の引数は TypeError、strict_types や名前付き引数の影響を受ける
必要な修正exit(array) 等の誤用を修正
round() の内部実装を書き換え(事前丸めを廃止、0.49999999999999994 の丸めが 1 から 0 になるなど)。一部の境界値で結果が以前と変わる。無効な mode は ValueError
必要な修正金額計算など厳密な丸めは BCMath(bcround は 8.4 で追加)や intdiv を検討
password_hash() の bcrypt(PASSWORD_BCRYPT / PASSWORD_DEFAULT)の既定コストが 10 から 12 に。既存のハッシュに対して password_needs_rehash() が true を返し、ログイン時の再ハッシュで CPU 負荷が約 4 倍になる
必要な修正負荷を据え置くなら ['cost' => 10] を明示。再ハッシュ処理の実装を確認
リソースからオブジェクトへ移行:dba_open() / dba_popen() → Dba\Connection、odbc_connect() / odbc_pconnect() → Odbc\Connection、odbc_exec() 等 → Odbc\Result、SoapClient::$httpurl / $sdl / $typemap → 内部オブジェクト
必要な修正is_resource() 判定を修正
PHP_DEBUG / PHP_ZTS 定数が int から bool に。GMP クラスが final(継承不可)。MYSQLI_SET_CHARSET_DIR / MYSQLI_STMT_ATTR_PREFETCH_ROWS / MYSQLI_CURSOR_TYPE_FOR_UPDATE / MYSQLI_CURSOR_TYPE_SCROLLABLE / MYSQLI_TYPE_INTERVAL 定数を削除。比較中の再帰(相互参照するオブジェクトの ==)は Fatal ではなく Error 例外
必要な修正=== 1 比較の修正、GMP を継承したクラスの修正、定数の削除
MySQL の wait_timeout 超過時のエラーコードが 2006 から 4031 に(MySQL 8.0.24 以降のサーバー)。JIT の既定が opcache.jit=disable(有効化には opcache.jit=tracing と opcache.jit_buffer_size を明示)。PCRE2 10.44 により {,3} を量指定子と解釈(以前はリテラル)
必要な修正再接続判定で 2006 を見ているコードに 4031 を追加、php.ini の JIT 設定を確認、{,n} をリテラルで使う正規表現は \{,n} にエスケープ
不正な引数が警告や false から ValueError / TypeError に変わった関数が複数ある(例:unserialize() の allowed_classes オプションが配列でも bool でもない場合、mb_http_output() の encoding に NUL バイトを含む場合と mb_http_input() の type が不正な場合、str_getcsv() の separator / enclosure / escape の長さが不正な場合、GD の imagejpeg() / imagewebp() / imagepng() / imageavif() の quality や imagescale() / imagefilter() の一部引数)
必要な修正呼び出し前に引数を検証する
mysqli_ping() / mysqli::ping()、mysqli_kill() / mysqli::kill()、mysqli_refresh() / mysqli::refresh() と MYSQLI_REFRESH_* 定数、mysqli_store_result() の mode 引数と MYSQLI_STORE_RESULT_COPY_DATA を非推奨
必要な修正ping は削除(自動再接続は [8.2] で消えている。SELECT 1 で疎通確認)、KILL / FLUSH は SQL で実行
セッション:session_set_save_handler() の 3 引数以上の形式、session.sid_length / session.sid_bits_per_character の変更、session.use_only_cookies / session.use_trans_sid / session.trans_sid_tags / session.trans_sid_hosts / session.referer_check の変更、SID 定数を非推奨
必要な修正SessionHandlerInterface 実装クラスを 2 引数形式で登録、INI は既定値(32 文字 16 進のセッション ID)に戻しストレージ側を対応、URL へのセッション ID 埋め込み(trans_sid)は廃止して Cookie のみに
fputcsv() / fgetcsv() / str_getcsv() および SplFileObject::fputcsv() / fgetcsv() / setCsvControl() の escape 引数の省略(既定 "\\")を非推奨
必要な修正escape: '' を明示する(RFC 4180 準拠にするなら空文字列)。既存 CSV がバックスラッシュを含む場合は "\\" を明示して互換維持
xml_set_object() と xml_set_*() ハンドラ登録への非 callable 文字列(メソッド名だけの文字列)を非推奨
必要な修正[$this, 'startElement'] のような callable 配列かクロージャを渡す
lcg_value()(→ Random\Randomizer::getFloat())、unserialize() の大文字 S タグ、stream_context_set_option() の 2 引数形式(→ stream_context_set_options())、DatePeriod::__construct(string $isostr)(→ DatePeriod::createFromISO8601String())、SUNFUNCS_RET_* 定数、CURLOPT_BINARYTRANSFER、DOM_PHP_ERR と DOMDocument::$actualEncoding / $config、DOMEntity::$actualEncoding / $encoding / $version、hash 関数への不正なオプション、IntlCalendar::set() / IntlGregorianCalendar::__construct() の 3 引数以上、ldap_connect() の 3 引数以上(→ ldap_connect_wallet())、ldap_exop() の 5 引数以上(→ ldap_exop_sync())、pg_fetch_result() / pg_field_prtlen() / pg_field_is_null() の 2 引数形式(→ row に null を渡す 3 引数)、PDO_PGSQL のドル引用文字列内の ?? エスケープ、ReflectionMethod::__construct() の 1 引数形式(→ createFromMethodName())、SoapServer::addFunction() への int と SOAP_FUNCTIONS_ALL、SplFixedArray::__wakeup()、dba_key_split() への null / false を非推奨
必要な修正括弧内の代替へ
新規組み込み関数の追加(array_find / array_find_key / array_any / array_all / mb_trim / mb_ltrim / mb_rtrim / mb_ucfirst / mb_lcfirst / fpow / http_get_last_response_headers / request_parse_body / grapheme_str_split / bcfloor / bcceil / bcround 等)。同名の自作関数(array_find / array_any / mb_trim が典型)は Fatal
必要な修正function_exists() ガード
公式: PHP 8.5 移行ガイド(日本語) / 後方互換性のない変更 / 非推奨 / その他の変更 / ChangeLog 8.5.0 / UPGRADING
バッククォート演算子 `command`(shell_exec のエイリアス)を非推奨
必要な修正shell_exec() に置換(さらに escapeshellarg() を確認)
非正規のキャスト名 (boolean) / (integer) / (double) / (binary) を非推奨
必要な修正(bool) / (int) / (float) / (string) に一括置換
case 文をコロンではなくセミコロンで終える case 1; を非推奨
必要な修正case 1: に修正
null を配列オフセットに使うこと($arr[null]、isset($arr[$undefined]) の $undefined が null)と array_key_exists(null, $arr) を非推奨
必要な修正キーを '' に正規化するか、null にならないよう事前チェック。未初期化変数をキーに使う古いコードが典型
非数値文字列のインクリメント("a"++ → "b")を非推奨([8.3] のソフト非推奨が正式な E_DEPRECATED に)
必要な修正str_increment() に置換
$http_response_header(file_get_contents('http://...') 後に自動定義されるローカル変数)を非推奨
必要な修正http_get_last_response_headers()(8.4 で追加)に置換
register_argc_argv による非 CLI SAPI での $_SERVER['argv'] / argc のクエリ文字列からの導出を非推奨
必要な修正register_argc_argv=0 に設定し $_GET / $_SERVER['QUERY_STRING'] を使う
__sleep() / __wakeup() をソフト非推奨(E_DEPRECATED なし)。__debugInfo() の null 返却、定数の再宣言、report_memleaks INI、出力ハンドラ内での出力、不正なクロージャのバインド(static クロージャへのインスタンス束縛、無関係なクラスへのメソッド束縛、$this のアンバインド、内部クラスのスコープへの束縛、関数由来クロージャのスコープ変更)を非推奨
必要な修正__serialize() / __unserialize() を実装、空配列を返す、定数の二重定義を解消、Closure::bind の使い方を見直す
curl_close() / curl_share_close()、finfo_close()(と finfo_buffer() の context 引数)、imagedestroy()、xml_parser_free() を非推奨([8.0] / [8.1] 以降すでに no-op)
必要な修正呼び出し行を削除するだけでよい
MHASH_* 定数(MHASH_MD5 等)を非推奨(削除ではない)。mysqli_execute()(→ mysqli_stmt_execute())、socket_set_timeout()(→ stream_set_timeout())、DATE_RFC7231 / DateTimeInterface::RFC7231、intl.error_level INI、openssl_pkey_derive() の key_length、chr() への 0〜255 範囲外の値、ord() への複数バイト文字列、readdir() / rewinddir() / closedir() への null、LDAP の Oracle Instant Client 用の呼び出し(ldap_connect_wallet 等)と GSLC_SSL_* 定数を非推奨
必要な修正括弧内の代替へ。hash() に移行、chr($n % 256) のように明示
PDO のドライバ固有定数とメソッドを非推奨:PDO::MYSQL_ATTR_*(→ Pdo\Mysql::ATTR_*。例:PDO::MYSQL_ATTR_INIT_COMMAND → Pdo\Mysql::ATTR_INIT_COMMAND、MYSQL_ATTR_USE_BUFFERED_QUERY、MYSQL_ATTR_SSL_CA 等)、PDO::SQLITE_*、PDO::PGSQL_ATTR_DISABLE_PREPARES、PDO::ODBC_*、PDO::DBLIB_*、PDO::FB_*、PDO::sqliteCreateFunction() / sqliteCreateAggregate() / sqliteCreateCollation()(→ Pdo\Sqlite::createFunction() 等)、PDO::pgsqlCopyFromArray() 等(→ Pdo\Pgsql::copyFromArray() 等)。"uri:" 形式の DSN と PDO::PGSQL_TRANSACTION_* も非推奨
必要な修正PDO::connect() で Pdo\Mysql 等のサブクラスを取得し、新しい定数・メソッドに置換。接続オプション配列に PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4' を書いている箇所が典型
Reflection*::setAccessible()(8.1 以降は無効)、存在しない定数への ReflectionClass::getConstant()、デフォルト値のないプロパティへの ReflectionProperty::getDefaultValue()、spl_autoload_unregister(spl_autoload_call(...)) による全解除、SplObjectStorage::contains() / attach() / detach()(→ offsetExists() / offsetSet() / offsetUnset())、ArrayObject / ArrayIterator へのオブジェクト渡しを非推奨
必要な修正括弧内の代替へ。setAccessible() は行ごと削除
disable_classes INI を削除
必要な修正php.ini から削除。クラス単位の無効化が必要なら別の手段(オートローダーで弾く等)
Opcache が常に PHP バイナリに組み込まれ常にロードされる(opcache.enable / opcache.enable_cli は引き続き有効)。zend_extension=opcache.so / php_opcache.dll の INI 指定は警告
必要な修正php.ini / conf.d から zend_extension=opcache の行を削除。--enable-opcache / --disable-opcache の configure オプションも削除
class_alias() で "array" / "callable" を別名に使えない。比較不能なオブジェクト(enum、CurlHandle 等の内部クラス)と bool の緩い比較が常に (bool)$object の結果に統一。トレイトが親クラスより先にバインドされる。コンパイル時・クラスリンク時のエラーが常に遅延処理される。#[\Attribute] を抽象クラス・enum・インターフェース・トレイトに付けるとコンパイルエラー(#[\DelayedTargetValidation] で実行時に遅延可)。gc_collect_cycles() の戻り値に間接回収分を含まない。tick ハンドラの停止タイミングが shutdown 関数・デストラクタ・出力ハンドラ処理の後に
必要な修正該当コードを確認
null 以外の非配列(int / string / object)を [] や list() で分割代入すると E_WARNING。int で表現できない float(や float に見える文字列)の int キャストで警告、NAN のキャストで警告
必要な修正分割代入の前に is_array() で検査、桁あふれの計算を見直す
PDO:PDO::FETCH_GROUP / FETCH_UNIQUE / FETCH_CLASSTYPE / FETCH_PROPS_LATE / FETCH_SERIALIZE の定数値が変更(数値を直書きしていると壊れる)。FETCH_CLASS のコンストラクタ引数が call_user_func_array() 準拠(文字列キーは名前付き引数、参照渡しは [&$v])。fetch 中の setFetchMode() は Error。FETCH_PROPS_LATE を FETCH_CLASS 以外と併用、fetchAll() に FETCH_INTO は ValueError。PDO_SQLITE の quote() が NUL バイトを含む文字列で例外 / 警告
必要な修正定数名で参照する、コンストラクタ引数の配列を整数キーに
mysqli:構築済みオブジェクトでの mysqli コンストラクタ再呼び出し($db->__construct(...) による再接続)は Error。セッション:$_SESSION のキーにパイプ文字(|)を含むと警告(以前は保存を静かに失敗)、session_start() の options が連想配列でないと ValueError、read_and_close が int 互換でないと TypeError。SimpleXMLElement::xpath() がノード集合以外を返す式で警告+false(以前は空配列)
必要な修正再接続は新しい mysqli を生成、セッションキーからパイプ文字を除去、xpath の戻り値判定を修正
Intl は ICU 57.1 以上が必須。IntlDateFormatter::setTimeZone() が未初期化時に IntlException。Locale メソッドへの NUL バイトで ValueError。Collator::SORT_REGULAR の数値文字列の扱いを ext/standard の SORT_REGULAR に統一。mbstring の Unicode データが 17.0 に更新
必要な修正ビルド環境の ICU を確認、ソート順の期待値を確認
不正な引数で ValueError / TypeError になる関数の追加:bzcompress() の block_size / work_factor、finfo_file() の NUL バイト(TypeError → ValueError)、ldap_get_option() / ldap_set_option() の不正オプション、pcntl_exec() の NUL バイト、posix_kill() / posix_setpgid() / posix_setrlimit() の範囲外、snmpget() 等のホスト名・ポート・タイムアウト、socket_create_listen() / socket_bind() / socket_sendto() のポート範囲、socket_addrinfo_lookup() の hints、socket_set_option() のマルチキャスト、tidy のコンストラクタ・parseFile() / parseString() の設定値、setlocale() に整数 0 を渡す(TypeError)、PDO_FIREBIRD のカーソル名長
必要な修正呼び出し前に引数を検証
その他:SoapClient::__doRequest() に省略可能な第 6 引数 uriParserClass が追加(オーバーライドしている子クラスはシグネチャ互換の確認が必要)。ArrayObject が enum を受け付けない。SplFileObject::fwrite() の length が nullable で既定 null。DOMNamedNodeMap / DOMNodeList 等のクローンは失敗。printf 系で精度未指定時の扱いを修正(0 として扱う)。ODBC は ODBC 3.5 以上を前提(ODBCVER ビルドフラグと特定ドライバ向けビルドフラグを削除)。PCRE を PCRE2_EXTRA_ALLOW_LOOKAROUND_BSK なしでビルド
必要な修正SoapClient を継承したクラスの __doRequest 宣言を確認
新規組み込み関数の追加(array_first / array_last / get_error_handler / get_exception_handler / grapheme_levenshtein / locale_is_right_to_left / curl_multi_get_handles 等)。同名の自作関数(array_first / array_last はヘルパー名として頻出)は Fatal
必要な修正function_exists() ガードか名前空間付き関数に
7.3 系からの移行で手順を組むときに気をつけたいのは、移行に使う道具の多くがもう 7.3 では動かないことです。Rector と PHPStan の現行版は PHP 7.4 以上を要求するので、7.3 のサーバーに入れて動かすことはできません。解析もテストも、移行先の PHP を入れたコンテナの中で動かす前提で組み立てます。以下は、特集トップの「推奨する進め方」を 7.3 起点向けに具体化したものです。
最初に、本番と同じ 7.3 環境で使っている拡張と設定を記録します。8.4 で本体から外れた拡張(IMAP・OCI8・PDO_OCI・PSpell)や、8.0 で外れた xmlrpc を使っていないかは、この時点で分かります。
mkdir -p docs/php-upgrade
php -v > docs/php-upgrade/php73-version.txt
php -m > docs/php-upgrade/php73-modules.txt
php -i | grep -E '^(error_reporting|display_errors|log_errors|default_charset|mbstring\.|session\.)' > docs/php-upgrade/php73-ini.txt
# 外れた拡張の関数を呼んでいないか(vendor/ を除く)
grep -rnE '\b(imap_|oci_|pspell_|xmlrpc_|wddx_|recode_|ibase_)' --include='*.php' --exclude-dir=vendor .Composer を使っているなら、依存パッケージが 8.4 を受け入れるかも見ます。7.3 時代のフレームワークは 8.x に対応していない版のことが多く、フレームワークの更新がこの移行で最も大きな作業になる場合があります。
composer why-not php 8.4.27 # 8.4.27 を拒んでいるパッケージ
composer outdated --direct # 更新が出ている直接依存テストが無いアプリでも、主要な処理を CLI から呼んで出力をファイルに残しておけば、移行後に同じスクリプトを 8.4.27 で動かして diff で比べられます。Docker の公式イメージには php:7.3.33-cli のタグが残っているので、本番サーバーに手を入れずに比較の基準を作れます。
# tests/smoke.php: 主要な関数・クラスを呼び、結果を var_export() で出力するだけのスクリプト
docker run --rm -v "$PWD":/app -w /app php:7.3.33-cli php tests/smoke.php > docs/php-upgrade/smoke-7.3.33.txt
# 移行作業のあとで同じものを移行先で動かす
docker run --rm -v "$PWD":/app -w /app php:8.4.27-cli php tests/smoke.php > docs/php-upgrade/smoke-8.4.27.txt
diff -u docs/php-upgrade/smoke-7.3.33.txt docs/php-upgrade/smoke-8.4.27.txtスモークテストには、上の「特に危ない変更」に当たる処理を優先して入れます。空文字列や null を混ぜた入力での比較、DB から取った値の型、金額の丸めなど、エラーにならずに結果だけが変わるものほど、このやり方でしか見つかりません。
解析は下の「任せる前に用意しておくもの」で作る移行先のコンテナ(php-upgrade:8.4.27)の中で動かします。7.3 から上げる場合、まず出てくるのは 8.0 で削除された書き方です。testVersion を 7.3-8.4 の範囲で指定すると、切り替えの期間に両方の版で動くかどうかも分かります。
vendor/bin/phpcs -ps . --standard=PHPCompatibility --runtime-set testVersion 7.3-8.4 \
--ignore=*/vendor/* --report=summary
# ファイルごとの詳細は CSV に出して、項目 id と突き合わせる
vendor/bin/phpcs . --standard=PHPCompatibility --runtime-set testVersion 7.3-8.4 \
--ignore=*/vendor/* --report=csv > docs/php-upgrade/phpcompat.csv7.3 から 8.4 までの規則を一度に当てると、差分が大きすぎて読めません。7.4、8.0、8.4 の 3 回に分けて、それぞれ --dry-run の差分を読んでからコミットすると、あとで問題が出たときにどの規則が原因かを追えます。withPhpSets() の名前付き引数は、その版までの規則をまとめて当てる指定です。
# 1 回目: ->withPhpSets(php74: true) 波括弧オフセット、(real)、implode() の引数順など
vendor/bin/rector process --dry-run > docs/php-upgrade/rector-74.diff
vendor/bin/rector process && git commit -am "Rector: PHP 7.4 まで"
# 2 回目: ->withPhpSets(php80: true) 8.0 の削除・変更に当たる書き換え
# 3 回目: ->withPhpSets(php84: true) 暗黙の nullable など 8.1〜8.4 の分Rector が自動で直せるのは、書き方を機械的に置き換えれば済むものです。== の比較や PDO の型のように、意味を判断しないと直せない挙動変更は残ります。そこは次の手順で拾います。
8.0 から error_reporting の既定値が E_ALL になり、7.3 では Notice だったものの多くが Warning に上がりました(v80-06、v80-08)。移行先のコンテナで画面を出さずにログだけを取り、メッセージの種類ごとに数えると、どこから手を付けるかが決めやすくなります。
docker run --rm -it -p 8080:8080 -v "$PWD":/app -w /app php-upgrade:8.4.27 \
php -d error_reporting=E_ALL -d display_errors=Off -d log_errors=On \
-d error_log=/app/docs/php-upgrade/php-errors.log \
-S 0.0.0.0:8080 -t public
# ブラウザで主要な画面を一通り操作したあと、件数の多い順に並べる
sed -E 's/^\[[^]]*\] //; s/ in \/app\/.* on line [0-9]+$//' docs/php-upgrade/php-errors.log \
| sort | uniq -c | sort -rn | head -407.3 起点では、内部関数への null、動的プロパティ、暗黙の nullable の 3 種類が上位を占めることが多いはずです。どれも機械的に直せる種類なので、件数が多くても慌てる必要はありません。@ 演算子や error_reporting の変更で黙らせるのではなく、ログが 0 件になるまで直します。
ステージングに php:8.4.27-fpm などの移行先を用意し、本番と同じ php.ini の設定で動かします。8.0 以降は display_startup_errors も既定で有効なので、本番用の設定では display_errors=Off と log_errors=On を明示します。7.3 用の php.ini をそのまま持ち込むと、8.x で削除された INI 項目の警告が出ることがあるので、起動時のログも確認してください。
本番の切り替えでは、戻す手段を用意したうえで行います。ただし 7.3 系には PHP2Shell の修正がまったく出ていないので、7.3 の環境に戻すのは短時間の退避にとどめ、外部からの到達範囲を絞った状態で使ってください。
このページの一覧は、人が読むチェックリストであると同時に、Claude Code や Codex にそのまま渡せる作業指示の材料でもあります。7.3 系から 8.4.27 までの変更は項目数が多いので、全部を手で追うより、機械で拾えるものは静的解析と Rector に任せ、エージェントには「一覧と突き合わせて残りを探し、直した理由を項目 id で説明する」ところまでを頼むのが現実的です。
ただし、エラーを出さずに結果だけが変わる種類の変更は、ツールでもエージェントでも見落としが出ます。この節の最後にある「検収で人が見るところ」は、エージェントの報告を受け取ったあとに必ず人が確認してください。
※ツール名・設定の書き方・コマンドは、2026年10月時点の各公式ドキュメントで確認したものです。
git switch -c php-upgrade-8.4 のように移行専用のブランチを切り、エージェントには main を直接触らせない。php:8.4.27-cli のようにパッチ版まで固定したタグがあります。ホストの PHP ではなく、このコンテナで解析とテストを動かします。docs/php-upgrade/reference.html として保存しておくと、エージェントがネットワークに出られない設定でも一覧を読めます。FROM php:8.4.27-cli
RUN apt-get update && apt-get install -y --no-install-recommends git unzip \
&& rm -rf /var/lib/apt/lists/*
# アプリが使う拡張をここで足す(例: MySQL)
RUN docker-php-ext-install pdo_mysql mysqli
COPY --from=composer/composer:2-bin /composer /usr/bin/composer
WORKDIR /appgit switch -c php-upgrade-8.4
mkdir -p docs/php-upgrade
curl -sL -o docs/php-upgrade/reference.html "https://shinichi.noguchi.jp.net/feature/phpupgrade/7.3.html"
docker build -t php-upgrade:8.4.27 docker/php-upgrade
# 以降のコマンドはこのコンテナの中で動かす
docker run --rm -it -v "$PWD":/app php-upgrade:8.4.27 bashエージェントに頼む前に、決まった規則で拾える変更はツールで拾っておきます。エージェントにもこれらを実行させ、その結果を作業の起点にさせると、読み落としが減ります。
| ツール | 何をするか | 公式 |
|---|---|---|
| PHPCompatibility | PHP_CodeSniffer の規則集。指定した PHP の版で削除・非推奨になった関数や構文を静的に検出する。testVersion に移行先の版を渡す。 | GitHub |
| Rector | PHP の版ごとの書き換え規則を持つ自動リファクタリングツール。rector.php に移行先を指定し、差分を確認してから適用する。 | getrector.com |
| PHPStan | 静的解析。phpVersion で解析の前提にする PHP の版を、level(0〜10)で厳しさを決める。 | phpstan.org |
| Psalm | 静的解析。psalm.xml の phpVersion 属性で前提の版を指定する。未指定なら composer.json の php 指定を読む。PHPStan とどちらか一方で足ります。 | psalm.dev |
| PHPUnit | テストの実行。PHPUnit 自体にも対応する PHP の版があるので、移行先で動く版をサポート表で選ぶ。 | phpunit.de |
| Composer | composer why-not php 8.4.27(prohibits の別名)で移行先の PHP を拒んでいる依存パッケージを、composer outdated --direct で更新が出ている直接依存を一覧にする。 | getcomposer.org |
PHPCompatibility は、README が案内する 10 系を入れます。安定版の最新 9.3.5 は 2019年12月の版で、PHP 8.0 の正式リリースより前のものです。10 系は2026年10月時点でまだプレリリース(10.0.0-alpha2)で、PHP 7.2 以上と PHP_CodeSniffer 4.0.2 以上が必要です。古いアプリの composer.json に入れると依存が衝突することがあるので、その場合は tools/ のような別ディレクトリに解析用の composer.json を作って入れてください。
composer config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true
composer require --dev phpcompatibility/php-compatibility:"^10.0.0@dev"
# 移行先だけで動けばよい場合
vendor/bin/phpcs -ps . --standard=PHPCompatibility --runtime-set testVersion 8.4 --ignore=*/vendor/*
# 切り替えの間、旧版と移行先の両方で動かす必要がある場合は範囲で指定する
vendor/bin/phpcs -ps . --standard=PHPCompatibility --runtime-set testVersion 7.3-8.4 --ignore=*/vendor/*Rector は、composer.json の "php" 指定から移行先を読ませる書き方が公式の推奨です。先に composer.json の require.php を ^8.4 にしてから、引数なしの withPhpSets() を使います。composer.json をまだ変えられないときは、名前付き引数で版を 1 つだけ指定します(名前付き引数なので、Rector は PHP 8.0 以上で動かします)。差分が大きすぎて読めない場合は、規則を安全なものから 1 つずつ足していく withPhpLevel(0) から始め、数字を 1 ずつ上げる方法も公式に用意されています。
<?php
use Rector\Config\RectorConfig;
return RectorConfig::configure()
->withPaths([
__DIR__ . '/src',
__DIR__ . '/public',
])
// composer.json の "require": {"php": "^8.4"} を読み、その版までの規則をまとめて適用する
->withPhpSets();
// composer.json を先に変えられない場合は、上の行の代わりに版を 1 つだけ指定する
// (8.4 なら php84、8.5 なら php85。withPhpSets() は 1 回しか呼べない)
// ->withPhpSets(php84: true);composer require rector/rector --dev
vendor/bin/rector process --dry-run # 差分を見るだけ
vendor/bin/rector process # 確認してから適用PHPStan は、phpVersion を指定しないと PHPStan を動かしている PHP の版が前提になります。移行先のコンテナで動かすならそのままでも合いますが、設定ファイルに書いておくほうが取り違えがありません。古いコードにいきなり高いレベルを当てると指摘が多すぎて読めないので、0 から始めて上げていきます。
parameters:
level: 0
phpVersion: 80400 # PHP 8.4。8.5 なら 80500
paths:
- src
- publicClaude Code は、リポジトリ直下の CLAUDE.md を毎回読み込みます。移行の間だけ守らせたい約束をここに書いておくと、指示のたびに繰り返さずに済みます。
## PHP 移行のルール(7.3.33 → 8.4.27) - 参照資料は https://shinichi.noguchi.jp.net/feature/phpupgrade/7.3.html(保存版: docs/php-upgrade/reference.html)。7.3 系から 8.4 までに入った削除・非推奨・挙動変更の一覧で、各項目に id(例: v80-05)がある。 - 実行・解析・テストは Docker の php-upgrade:8.4.27 イメージ(docker/php-upgrade/Dockerfile)で行う。ホストの php は使わない。 - 変更の根拠は、参照資料の項目 id で示す。 - 1 コミットに 1 種類の変更。Rector の自動変換と手作業の修正を同じコミットに混ぜない。 - @ 演算子、error_reporting の変更、空の catch で警告やエラーを隠さない。 - テストの期待値を変えるときは、変える理由を必ず報告する。 - vendor/ 配下は編集しない。依存パッケージは composer で更新する。 - 次の作業の前には止まって確認を求める: composer.json の require の変更、DB のスキーマ・文字コードに関わる変更、php.ini・.htaccess・FPM 設定など本番環境の設定の変更、業務上の計算結果が変わりうる修正。
一括依頼のプロンプトです。段階ごとに止まって報告させる形にしています。一度に最後まで走らせると、途中の判断を人が確かめる機会がなくなるためです。
このリポジトリの PHP を 7.3.33(7.3 系)から 8.4.27 へ移行します。CLAUDE.md(Codex の場合は AGENTS.md)の移行ルールに従ってください。 参照資料: https://shinichi.noguchi.jp.net/feature/phpupgrade/7.3.html(保存版: docs/php-upgrade/reference.html) 参照資料の「起点から移行先までに入った変更の全一覧」が、7.3 系から 8.4 までに入った削除・非推奨・挙動変更のチェックリストです。移行先 8.4.27 より新しい版のブロックは対象外です。各項目の id(例: v80-05)を作業記録に使ってください。 作業は次の 6 段階で進め、段階ごとに止まって報告してください。私の返事があるまで次の段階に進まないでください。 ① インベントリ - composer.json と composer.lock、使っている PHP 拡張、php.ini の設定に依存している箇所、PHP ファイルの数、テストの有無と実行方法を調べ、docs/php-upgrade/inventory.md にまとめる。 - composer why-not php 8.4.27 と composer outdated --direct を実行し、移行先で動かない依存パッケージと、その更新先の版を一覧にする。 - この段階ではコードを変更しない。 ② Rector による自動変換 - rector.php を用意し、vendor/bin/rector process --dry-run の差分を規則ごとに要約して見せる。 - 私が承認した規則だけを適用する。規則ごとにコミットを分け、コミットメッセージに規則名を入れる。 ③ 参照資料の一覧との突き合わせ - 一覧の項目を上から順に 1 つずつ取り上げ、該当しそうな関数名・定数名・INI 名・構文をリポジトリ全体から grep で探す(vendor/ は除く)。 - 結果を docs/php-upgrade/checklist.md に表で残す。列は「項目 id・区分・内容・該当箇所(ファイル:行)・対応(該当なし/修正済み/要判断)」。全項目を 1 行ずつ埋め、飛ばさない。 - エラーにならず結果だけが変わる挙動変更(== の比較、文字コードの既定値、round() や range() の結果など)は、直す前に「要判断」として私に見せる。 ④ Deprecated と警告の全件収集 - php-upgrade:8.4.27 のコンテナで error_reporting=E_ALL、display_errors=stderr、log_errors=On にしてテストと主要な画面・バッチを動かし、E_DEPRECATED・E_WARNING・E_NOTICE を全件ログに出す。 - 1 件ずつ原因を直し、最後にログが 0 件になったことをログの実物で示す。@ 演算子や error_reporting の変更で隠すことは禁止。 ⑤ テスト - 既存のテストと、移行前の出力を記録したスモークテストを移行先で実行し、移行前と結果が一致することを確かめる。一致しないものは差分をそのまま見せる。 - vendor/bin/phpcs --standard=PHPCompatibility --runtime-set testVersion 8.4 と vendor/bin/phpstan analyse を実行し、残った指摘を一覧にする。 ⑥ 変更報告 - 変更内容を表で報告する。列は「項目 id・ファイル・変更前・変更後・理由・テストで確認したか」。 - 自信のない変更、業務ロジックの意味が変わりうる変更、DB や文字コードに関わる変更は別の表に分け、「人の確認が必要」と明記する。
差分ができたら、作業した会話とは別のセッションでレビューさせます。同じ会話の中だと、自分が下した判断をそのまま追認しがちだからです。
これは PHP 7.3.33 から 8.4.27 への移行作業の差分です。git diff main...HEAD を読み、レビューしてください。参照資料: https://shinichi.noguchi.jp.net/feature/phpupgrade/7.3.html(保存版: docs/php-upgrade/reference.html) 次の観点で問題のある箇所を探し、「ファイル:行・問題・根拠になる参照資料の項目 id・修正案」の表で返してください。観点ごとに、問題がなければ「なし」と書いてください。 1. 変更の前後で結果が変わる箇所: == と === の置き換え、文字列と数値の比較、round()・range()・文字列関数の戻り値、PDO で取得した値の型 2. 内部関数に null が渡る箇所を、(string) などのキャストや ?? '' で黙らせていないか。本来 null が来ないはずの値なら、null が来る原因の側を直すべき 3. 動的プロパティを #[\AllowDynamicProperties] で逃がしていないか。逃がした場合はその理由が書かれているか 4. @ 演算子、error_reporting の変更、空の catch で警告やエラーを隠していないか 5. 文字コード: htmlspecialchars() の既定値、mb_* の内部エンコーディング、DB 接続の charset が移行前と同じ結果になるか 6. テストが変更箇所を実際に通っているか。期待値を書き換えて通しただけのテストがあれば指摘する コードは変更せず、レビュー結果だけを返してください。
非対話で動かす場合は claude -p を使います。段階を区切って 1 段階ずつ実行し、出力を人が読んでから次を流します。--allowedTools に挙げたツールは確認なしで使われるので、最初は読み取りと書き出しだけに絞っておくと安全です。
# ③の突き合わせだけを、コードを変更させずに実行する
claude -p "docs/php-upgrade/reference.html の変更一覧(7.3 系から 8.4 まで)を上から 1 項目ずつ取り上げ、リポジトリ(vendor/ を除く)を grep して該当箇所を探し、docs/php-upgrade/checklist.md に「項目 id・区分・内容・該当箇所・対応」の表で書いてください。コードは変更しないでください。" \
--allowedTools "Read,Grep,Glob,Write"
# 差分のレビュー(レビュー用プロンプトを docs/php-upgrade/review-prompt.md に保存しておく)
git diff main...HEAD | claude -p "$(cat docs/php-upgrade/review-prompt.md)" > docs/php-upgrade/review.mdレビュー役は、サブエージェントとして定義しておくと何度でも同じ観点で呼び出せます。.claude/agents/ に置いたファイルはチームで共有できます。
--- name: php-upgrade-reviewer description: PHP 7.3.33 から 8.4.27 への移行差分をレビューする。移行作業のコミット後や PR 作成前に使う。 tools: Read, Grep, Glob, Bash --- あなたは PHP の版移行のレビュー担当です。git diff main...HEAD を読み、docs/php-upgrade/reference.html の変更一覧(7.3 系から 8.4 まで)と照らして、次を確認してください。 1. 変更の前後で結果が変わる箇所(== と ===、文字列と数値の比較、round()・range()、PDO の取得値の型) 2. null を内部関数に渡す箇所をキャストで黙らせていないか 3. 動的プロパティを #[\AllowDynamicProperties] で逃がしていないか 4. @ 演算子・error_reporting の変更・空の catch で警告を隠していないか 5. 文字コードの既定値の変化が結果に出ていないか 6. 期待値を書き換えただけのテストがないか 結果は「ファイル:行・問題・根拠の項目 id・修正案」の表で返し、コードは変更しないこと。
hooks を使うと、Claude Code がファイルを編集するたびに PHPStan を走らせ、指摘をその場で Claude に返せます。PostToolUse の hook が終了コード 2 で終わると、標準エラーの内容が Claude に渡されます(編集そのものは取り消されません)。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/phpstan-changed.sh"
}
]
}
]
}
}#!/bin/bash
# 編集されたファイルのパスを標準入力の JSON から取り出す(jq が必要)
file=$(jq -r '.tool_input.file_path // empty')
case "$file" in
*.php) ;;
*) exit 0 ;;
esac
project="$(cd "$(dirname "$0")/../.." && pwd)"
rel="${file#"$project"/}"
out=$(docker run --rm -v "$project":/app php-upgrade:8.4.27 \
vendor/bin/phpstan analyse --no-progress --error-format=raw "$rel" 2>&1)
if [ $? -ne 0 ]; then
echo "$out" >&2
exit 2
fi
exit 0Codex は AGENTS.md を読みます。リポジトリのルートから作業ディレクトリまでの各階層の AGENTS.md が順に結合され、作業場所に近いものが優先されます。内容は CLAUDE.md の移行ルールと同じで構いません。
# AGENTS.md ## このリポジトリで進めている作業 PHP を 7.3.33(7.3 系)から 8.4.27 へ移行している。 参照資料: https://shinichi.noguchi.jp.net/feature/phpupgrade/7.3.html(保存版: docs/php-upgrade/reference.html)。7.3 系から 8.4 までに入った削除・非推奨・挙動変更の一覧で、各項目に id(例: v80-05)がある。 ## 実行環境 - 解析とテストは Docker の php-upgrade:8.4.27 イメージで行う(docker/php-upgrade/Dockerfile)。ホストの php は使わない。 - 静的解析: vendor/bin/phpcs --standard=PHPCompatibility --runtime-set testVersion 8.4 --ignore=*/vendor/* . - 静的解析: vendor/bin/phpstan analyse - 自動変換: vendor/bin/rector process --dry-run で差分を確認してから適用する ## 守ること - 変更の根拠は参照資料の項目 id で示す。 - 1 コミットに 1 種類の変更。Rector の自動変換と手作業の修正を混ぜない。 - @ 演算子、error_reporting の変更、空の catch で警告を隠さない。 - テストの期待値を変えたときは理由を報告する。 - vendor/ 配下は編集しない。 - composer.json の require、DB のスキーマや文字コード、php.ini などの本番設定に関わる変更は行わず、提案として報告に書く。
codex exec は非対話で動き、既定では読み取り専用のサンドボックスで実行されます。ファイルを書き換えさせるときは --sandbox workspace-write を付けます。上の一括依頼プロンプトを docs/php-upgrade/prompt.md に保存し、段階を指定して流します。
# ①インベントリだけ(読み取り専用のまま。最後の報告を -o でファイルに残す)
codex exec -o docs/php-upgrade/01-inventory-report.md \
"docs/php-upgrade/prompt.md を読み、①インベントリだけを実行して結果を報告してください。①以外の段階には進まないでください。"
# ③突き合わせ(checklist.md を書かせるので workspace-write)
codex exec --sandbox workspace-write -o docs/php-upgrade/03-checklist-report.md \
"docs/php-upgrade/prompt.md を読み、③参照資料の一覧との突き合わせだけを実行してください。コードは変更せず、docs/php-upgrade/checklist.md だけを書いてください。"
# プロンプト全体を標準入力から渡すこともできる
cat docs/php-upgrade/prompt.md | codex exec --sandbox workspace-write -必須ではありませんが、次のものは移行作業で役に立ちます。どれも公式のリポジトリかドキュメントで存在と導入方法を確認できたものだけを載せています。Claude Code 向けには、Anthropic の公式プラグインマーケットプレイス(claude-plugins-official)に PHP 向けのプラグインもあります。
| 名前 | 移行作業での使いどころ | 導入 |
|---|---|---|
| php-lsp Claude Code プラグイン | PHP の言語サーバー Intelephense を Claude Code につなぎ、定義元や参照の検索、診断を使えるようにする。関数の呼び出し元を漏れなく追うときに効く。 | npm install -g intelephense のあと /plugin install php-lsp@claude-plugins-official |
| code-modernization Claude Code プラグイン(Anthropic 製) | 古いコードベースの調査、業務ルールの抽出、承認制の計画、移行後の動作が旧コードと同じであることの検証までを段階的に進める。同じ技術の新しい版への移行も対象。大きなシステムでは多数のエージェントが動くので、まず 1 モジュールで試すよう README に書かれている。 | /plugin install code-modernization@claude-plugins-official のあと /code-modernization:modernize |
| Context7 MCP サーバー | ライブラリの版ごとの最新ドキュメントをエージェントに渡す。Rector や PHPStan、フレームワークの新しい書き方を、学習時点の古い知識ではなく現行の資料で確かめさせる。 | リモート https://mcp.context7.com/mcp。Claude Code・Codex それぞれの手順は公式の一覧 |
| GitHub MCP Server MCP サーバー | Issue・PR・ワークフローを操作する。「要判断」の項目を Issue に起こす、移行 PR の CI 結果を読ませる、といった使い方。 | リモート https://api.githubcopilot.com/mcp/。Claude Code の手順は公式ガイド |
| Playwright MCP MCP サーバー | ブラウザを操作する。移行前と移行後の画面を同じ手順で開き、表示や入力の結果を比べる回帰確認に使う。 | claude mcp add playwright npx @playwright/mcp@latestCodex: codex mcp add playwright npx "@playwright/mcp@latest" |
| Serena MCP サーバー | 言語サーバーを使い、シンボル単位でコードを検索・編集する道具をエージェントに渡す。対応言語に PHP が含まれる。 | 公式 README の手順(uv が必要) |
| Laravel Boost Laravel アプリの場合 | Laravel 公式の MCP サーバーとガイドライン。PHP と Laravel の版、DB スキーマ、直近のエラーログをエージェントに読ませ、インストール済みパッケージの版に合ったドキュメントを検索させる。ガイドラインの対象は Laravel 10.x〜13.x。 | composer require laravel/boost --dev のあと php artisan boost:install |
Context7・GitHub・Playwright・Serena・Laravel Boost は、同じ公式マーケットプレイスの外部プラグイン(external_plugins)としても登録されています。外部プラグインの中身は Anthropic が検証したものではない、と README に書かれているので、入れる前に各プロジェクトのページを確認してください。
エージェントの報告が「テストはすべて通過」でも、次の項目は人が差分と実際の出力を見て確かめます。どれもエラーを出さずに結果だけが変わる種類のものです。
== による比較。PHP 8.0 で数値と数値でない文字列の比較規則が変わり、0 == "a" が true から false になった。switch や in_array() の判定も同じ影響を受ける。htmlspecialchars() などの既定の文字コードやフラグの変化、mb_* の内部エンコーディング、DB 接続の charset。round() と range() の結果。金額や件数の端数処理、ページ送りの範囲を、移行前の出力と突き合わせる。=== の比較や JSON の出力が変わる。null を渡している箇所。キャストで警告を消しただけになっていないか。#[\AllowDynamicProperties] で逃がした箇所が、本当にそのままでよいか。E_DEPRECATED がゼロであること。エージェントの報告ではなく、移行先のコンテナで error_reporting=E_ALL にして取ったログの実物で確認する。7.3 系から上げるときに経由する版の、php.net の移行ガイドと php-src の UPGRADING です。上の一覧の各ブロックの冒頭にも同じリンクを置いています。
※2026年10月時点の公開情報に基づきます。項目の区分や修正方法は php.net の移行ガイドと php-src の UPGRADING をもとに整理したもので、「要確認」の印がある項目は一次情報の本文で裏取りを続けています。
このページはソースコードの改修を扱っています。PHP2Shell(CVE-2026-109618 / CVE-2026-109619)の仕組みと、サーバー側で先に済ませる更新は特集トップで解説しました。7.3 系には今回の脆弱性の修正がまったく出ていないので、移行が終わるまでの暫定的な守り方もトップを見てください。起点ごとのページへはトップから飛べるので、ブックマークはトップにしておくと便利です。
本特集に掲載したコード・コマンド・設定例・エージェント向けプロンプトは、あくまで説明のための例です。お使いのソースコードや環境でそのまま動くこと、改修が漏れなく完了することを保証するものではありません。各変更点の最終確認は、php.net の公式 ChangeLog・移行ガイド、php-src の UPGRADING など一次情報で必ず行ってください。
本特集の内容は現状有姿で提供するものであり、正確性・完全性・有用性について、瑕疵担保責任・契約不適合責任を含め、当方(野口真一)はいかなる保証も行いません。本特集の内容やコードを利用したこと、または利用できなかったことにより生じた損害について、当方は一切の責任を負いません。
移行先の選び方、影響範囲の見積もり、Rector やコーディングエージェントを使った進め方まで、
30分の無料相談で承ります。