PHP アップグレード特集 | 起点 8.1 系
8.1.0〜8.1.34 の全 35 版に共通。この特集でいちばん距離の短い起点で、山と呼べる版はありません。とはいえ 8.2〜8.5 の 4 版ぶんの非推奨と、php.ini 側の変更、通知なしに結果が変わる関数がいくつか待っています。
※2026年10月時点の公開情報に基づきます。項目数と内容は、この特集の変更カタログ(全 213 項目)と php.net の移行ガイドから作っています。
PHP 8.1 系のすべて、8.1.0 から 8.1.34 までの 35 版が対象です。パッチ版(3 つ目の数字)の間では後方互換性が保たれるため、8.1.2 のままのサーバーでも、8.1.27 まで上げてあるサーバーでも、新しい版へ移すときに直す箇所は変わりません。8.1 系のパッチ版の途中で入った変更のうち、コードの修正が要るものは、この特集の資料では把握していません。細かなセキュリティ修正は ChangeLog の各パッチの項を見てください。
PHP 8.1.0 / PHP 8.1.1 / PHP 8.1.2 / PHP 8.1.3 / PHP 8.1.4 / PHP 8.1.5 / PHP 8.1.6 / PHP 8.1.7 / PHP 8.1.8 / PHP 8.1.9 / PHP 8.1.10 / PHP 8.1.11 / PHP 8.1.12 / PHP 8.1.13 / PHP 8.1.14 / PHP 8.1.15 / PHP 8.1.16 / PHP 8.1.17 / PHP 8.1.18 / PHP 8.1.19 / PHP 8.1.20 / PHP 8.1.21 / PHP 8.1.22 / PHP 8.1.23 / PHP 8.1.24 / PHP 8.1.25 / PHP 8.1.26 / PHP 8.1.27 / PHP 8.1.28 / PHP 8.1.29 / PHP 8.1.30 / PHP 8.1.31 / PHP 8.1.32 / PHP 8.1.33 / PHP 8.1.34
8.1 系の最後のリリースは 8.1.34(2025-12-18)で、8.1 系は 2025-12-31 にセキュリティサポートを終えています。8.1.35 以降の番号は出ておらず、php-src にもタグはありません(2026-10-23 に確認)。8.1.35 や 8.1.36 という番号で探してここに来た場合も、8.1 系であればこのページがそのまま当てはまります。
移行先は 2026-10-21 に公開されたセキュリティ修正版の 4 つで、組み合わせごとの項目数は次のとおりです。
この特集の起点ページは 8.1 系までです。8.2.x・8.3.x・8.4.x で動かしている場合、今回の脆弱性への対応は同じ系列の 8.2.35 / 8.3.36 / 8.4.27 へのパッチ更新で済み、パッチ更新ではコードの修正は原則要りません。そのうえで次のマイナー版へ上げるときは、このページの一覧のうち、自分の版より後のブロックだけを読めば足ります。下の一覧は、変更が入った版ごとにブロックが分かれているからです。
| 今の版 | 8.4.27 へ移す | 8.5.12 へ移す |
|---|---|---|
| 8.2.x | [8.3] と [8.4]:30 項目 | [8.3]〜[8.5]:52 項目 |
| 8.3.x | [8.4]:16 項目 | [8.4] と [8.5]:38 項目 |
| 8.4.x | (同じ系列。パッチ更新のみ) | [8.5]:22 項目 |
読み方は 2 段階です。まず「変更の全一覧」の上にある移行先のボタンで、移す先の版を選びます。選んだ版より後のブロックは折りたたまれます。次に、自分の版と同じか、それより前のブロックは読み飛ばします。8.3.x から 8.5.12 へ移すなら、8.5.12 を選んだうえで [8.2] と [8.3] を飛ばし、[8.4] と [8.5] だけを確認します。ボタンの横に出る合計の件数は [8.2] から数えた値なので、上の表の件数と食い違っても気にしなくて構いません。8.2.x を使っている場合は、8.2 系のサポートが 2026-12-31 で終わる点も、移行を急ぐ理由になります。
下の「コーディングエージェントに一括で任せる」のプロンプトは 8.1.34 起点で書いてあります。8.2〜8.4 から使うときは、プロンプトの「8.1.34(8.1 系)」を自分の版に、「8.1 系から」を「8.3 系から」のように書き換え、対象外のブロックを読まないよう一文足してください。
8.1 起点では、8.4.27 と 8.5.12 のどちらを選んでも筋が通ります。差は 22 項目で、そのうち 12 項目は 8.5 の非推奨、残りは php.ini の扱い(disable_classes の削除、Opcache の組み込み)と、PDO・Intl などの細かな挙動変更です。ここでは 8.4.27 を第一候補として手順を書いていますが、次の条件がそろうなら 8.5.12 へ直行するほうが、次の移行までの時間を 1 年長く取れます。
composer why-not php 8.5.12 が何も返さない)(integer) などのキャスト名、$http_response_header など)を、今回の作業でまとめて片付ける時間がある| 移行先 | 項目数 | サポート期限 | 向いている場合 |
|---|---|---|---|
| 8.4.27推奨 | 43 | 2028-12-31 | 依存ライブラリの 8.5 対応を待ちたい場合や、php.ini を事業者が管理している場合。8.4 の暗黙の nullable の非推奨と bcrypt の既定コストを片付ければ、残りは少ない |
| 8.5.12条件付きで推奨 | 65 | 2029-12-31 | 上の 3 条件がそろう場合。8.4 の項目に加えて、8.5 の非推奨 12 項目と php.ini の整理を一度に済ませる |
| 8.3.36 | 27 | 2027-12-31 | IMAP・OCI8・PSpell の拡張を使っていて、8.4 での削除に代替が間に合わない場合に限った、一時的な行き先 |
| 8.2.35選ばない | 13 | 2026-12-31 | 項目は最も少ないが、サポートが 2 か月余りで終わる。移した直後にまた EOL を迎える |
8.5.12 を選ぶ場合、下の「コーディングエージェントに一括で任せる」の設定例とプロンプトは 8.4.27 を前提に書いてあるので、次の箇所を読み替えてください。プロンプトと Dockerfile の 8.4.27 を 8.5.12 に(FROM php:8.5.12-cli)、8.4 を 8.5 に。PHPStan の設定の phpVersion: 80400 を phpVersion: 80500 に。Rector の設定でコメントにしてある ->withPhpSets(php84: true) を使う場合は php85: true に。PHPCompatibility の testVersion も 8.5 にしますが、PHPCompatibility が 8.5 の変更をどこまで検出できるかは、使う版のリリースノートで確かめてください。
2026-10-21 の php.net の勧告は、8.1 以前について「no longer receive updates from the PHP project」と明記しています。そのうえで、深刻度を理由に、PHP セキュリティチームが 7.4・8.0・8.1 向けに「minimal source-backports」を出しました。勧告が想定している利用者は、ソースから自分でビルドする人と、LTS ベンダーを使えない人です。ディストリビューションと LTS ベンダーには事前にパッチが渡っていたとも書かれているので、OS のパッケージで PHP を入れている場合は、まずベンダーの更新を確かめます。
8.1 向けは php-src の PHP-8.1-CVE-2026-109618-109619 ブランチです。2026-10-23 に確認したところ、8.1.34 のタグから 4 コミット進んだだけのブランチで、修正のコミットには「Minimal mitigation for EOL version.」とあり、main/php_version.h は 8.1.34 のままでした。8.1.35 というリリースは存在しません。言い換えると、手元でビルドして入れ替える人のための当て布で、8.1 系の正式な修正版は今後も出ません。
8.1 系は、セキュリティサポートが終わってから 10 か月足らずで今回の勧告を迎えました。次に何か見つかったときにも同じ手当てがあるとは、どこにも約束されていません。今回はバックポートやベンダーの修正で乗り切れたとしても、サポートのある 8.4 か 8.5 へ移しておく必要があります。
8.1 起点の変更は、半分近くが非推奨の通知です。通知はログを見れば見つかるので、ここではそれ以外を中心に選びました。通知も出さずに結果が変わるもの、PHP の外側(php.ini やパスワードのハッシュ、DB の接続)で効いてくるもの、そして件数が多すぎて他の通知を隠してしまうものの 10 件です。
宣言していないプロパティへの代入が非推奨になりました。stdClass と __get() / __set() を実装したクラスは対象外なので、まず通知が出たクラスを一覧にし、宣言を足すのか、#[\AllowDynamicProperties] で当面しのぐのかをクラスごとに決めます。属性で逃がしても PHP 9 で Error になる予定は変わりません。
strcmp() や strcasecmp() は、これまで差分の値(たとえば 2 や -5)を返すことがありました。8.2 からは -1・0・1 のどれかだけです。< 0 や > 0 で判定していれば影響はありませんが、戻り値を === 2 のように比べたり、差の大きさを使ったりしているコードは黙って判定が変わります。var_export() がクラス名の先頭に \ を付けるようになった点も同じ項目です。
片方が数字の文字列の場合、もう片方も文字として扱われるようになり、range('9', 'A') は数字の並びではなく文字の範囲を返します。フォームやクエリ文字列から受け取った値をそのまま渡していると、型の違いで結果が変わります。step に 0 を渡すと ValueError になる点もあわせて確認します。
空の配列に $a[-5] = 'x' と入れてから $a[] = 'y' とすると、これまでは 0 番に入っていた値が -4 番に入ります。キーを番号として後から参照しているコードでは、値が見つからなくなるだけで、エラーは出ません。
function f(Foo $x = null) の書き方が非推奨になりました。8.1 時代のコードやライブラリにはごく普通にある書き方なので、件数ではこの起点で最も多くなりやすい通知です。?Foo $x = null への書き換えは Rector の ExplicitNullableParamTypeRector でほぼ機械的にできます。
password_hash() を既定のまま使っていると、既存のハッシュすべてに対して password_needs_rehash() が true を返します。ログインのたびに再ハッシュが走り、1 回あたりの計算量はおよそ 4 倍です。朝の始業時刻のようにログインが集中する時間帯の CPU 使用率を、切り替え前に見積もっておきます。
MySQL 8.0.24 以降のサーバーで wait_timeout を超えて切断されたとき、エラーコードが 2006 ではなく 4031 になります。長時間動くバッチやワーカーで、2006 を見て再接続する処理を書いていると、その分岐に入らなくなります。
8.5 で disable_classes の INI 設定そのものが削除されました。php.ini に残した行で特定のクラスを使えなくしていた場合、その制限は 8.5 では働きません。8.5.12 を選ぶなら、php.ini から行を消すだけでなく、制限が必要だった理由に合った別の手段を用意します。
8.5 では Opcache が常に組み込まれた状態で動きます。8.1 時代の php.ini や conf.d にある zend_extension=opcache.so の行は警告の原因になるので削除します。opcache.enable などの設定はこれまでどおり効きます。
file_get_contents('http://...') の後に自動で作られる $http_response_header が非推奨になりました。8.4 で追加された http_get_last_response_headers() に置き換えます。同じ 8.5 では、バッククォート演算子と、(integer) や (boolean) などのキャスト名も非推奨です。
8.1 系から 8.5 までに入った削除・非推奨・挙動変更を、入った版ごとの 4 つのブロック([8.2]〜[8.5])で並べています。一覧は累積です。8.4.27 へ移すなら [8.2]〜[8.4]、8.5.12 へ移すなら 4 つすべてを確認します。8.2〜8.4 を使っている人は、上の表のとおり自分の版より後のブロックだけを読んでください。
区分の意味は次のとおりです。削除は Fatal error や Error 例外になるもの、または機能・拡張そのものが無くなるもの。非推奨は E_DEPRECATED の通知が出るものの動作は続くもの。挙動変更はエラーを出さずに結果やエラーの種類が変わるもの。各項目の id(v84-02 など)は、エージェントへの指示や作業記録で使えます。
※以下の一覧に載せたコード・コマンド・プロンプトはあくまで例です。最終確認は php.net の公式 ChangeLog・移行ガイドで行ってください。内容の利用により生じた損害について当方は責任を負いません(詳しくはページ末尾の免責事項)。
移行先 8.4.27 まで
公式: 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() ガードか名前空間付き関数に
8.1 起点では、コードの書き換えそのものより「8.4 と 8.5 のどちらで動かすか」を早く決め、設定ファイルと通知の後始末を確実に終えることが作業の中心になります。以下はリポジトリの直下で、Docker が使える端末から実行する前提で書いています。
移行先を決める材料として、最初に両方の版で構文チェックと依存関係の確認をします。php -l は実行せずに構文だけを見るので、DB や拡張が無いコンテナでも動きます。8.1 で書いたコードが構文で落ちることはまれですが、8.4・8.5 で増えた組み込み関数と同じ名前の自作関数(array_find() や array_first() など)は、ここではなく実行時に Fatal error になる点に注意してください。
for v in 8.4.27 8.5.12; do
echo "== PHP $v"
docker run --rm -v "$PWD":/app -w /app php:$v-cli sh -c \
"find . -path ./vendor -prune -o -name '*.php' -print0 | xargs -0 -n1 php -l | grep -v '^No syntax errors'"
docker run --rm -v "$PWD":/app -w /app composer:2 composer why-not php $v
done
# 8.4 / 8.5 で追加された関数と同じ名前の自作関数が無いか
grep -rnE 'function\s+(array_find|array_find_key|array_any|array_all|array_first|array_last|mb_trim|mb_ucfirst)\s*\(' \
--include='*.php' --exclude-dir=vendor .composer why-not php 8.5.12 がフレームワークや主要なライブラリの名前を返したら、今回は 8.4.27 にしておくのが現実的です。8.4.27 でも名前が返るなら、そのパッケージの更新が移行の前提作業になります。
8.1 起点の変更のうち、コードではなく設定ファイルで効くものがいくつかあります。8.5.12 へ移すなら disable_classes と Opcache の読み込み行、8.4 以降ではセッション ID の長さなどの INI が非推奨です。サーバーの設定ディレクトリと、リポジトリに入れている設定の両方を探します。
grep -rnE '^\s*(disable_classes|zend_extension\s*=\s*.*opcache|session\.(sid_length|sid_bits_per_character|use_trans_sid|use_only_cookies))' \
/etc/php* /usr/local/etc/php docker/ 2>/dev/null見つかった disable_classes は、何のために置いたのかを先に確かめます。8.5 では行が残っていても何も起きず、制限だけが消えるからです。Opcache の行は 8.5 では消すだけで構いません。設定は 8.1 のファイルを持ち込まず、移行先の php.ini-production から作り直して、必要な値だけを書き写すほうが漏れが出にくくなります。
Rector の PHP 8.4 向けのセットには、暗黙の nullable を直す ExplicitNullableParamTypeRector のほかに、foreach を array_find() などへ置き換える規則も入っています。後者は動作を直す変更ではないので、最初は暗黙の nullable の規則だけを当て、差分を小さく保ってコミットします。セット全体を当てるかどうかは、その後で決めれば十分です。
<?php
declare(strict_types=1);
use Rector\Config\RectorConfig;
use Rector\Php84\Rector\Param\ExplicitNullableParamTypeRector;
return RectorConfig::configure()
->withPaths([__DIR__ . '/src', __DIR__ . '/app'])
->withRules([ExplicitNullableParamTypeRector::class]);composer require --dev rector/rector phpcompatibility/php-compatibility
vendor/bin/rector process --dry-run
vendor/bin/rector process
git commit -am "PHP 8.4: 暗黙の nullable を明示的な ?T に書き換え"
# 8.1 でも 8.4 でも動く書き方かを確認
vendor/bin/phpcs -ps . --standard=PHPCompatibility --runtime-set testVersion 8.1-8.4 --ignore=*/vendor/*8.5.12 へ進む場合、Rector の PHP 8.5 向けセットには、バッククォートを shell_exec() に、(integer) を (int) に、case 1; を case 1: に直す規則が含まれています。8.5 の非推奨のうち機械的に直せるものは、このセットでまとめて片付けられます。
このページの非推奨は、8.4.27 まででも 17 項目あります。テストが十分にあれば CLI で流してログを取れますが、画面からしか通らない処理が多いなら、ステージングの PHP-FPM のプール設定で通知をすべてログへ出し、普段の操作を一通り流すほうが確実です。php_admin_value で設定すると、アプリケーション側の ini_set() や error_reporting() で上書きされません。
; -1 はすべての通知を報告する指定(E_ALL を含む)
php_admin_value[error_reporting] = -1
php_admin_flag[display_errors] = off
php_admin_flag[log_errors] = on
php_admin_value[error_log] = /var/log/php/upgrade-deprecated.logLOG=/var/log/php/upgrade-deprecated.log
# vendor/ の外(自分たちで直す分)を、通知の種類ごとに
grep 'PHP Deprecated' "$LOG" | grep -v '/vendor/' \
| sed -E 's/^\[[^]]*\] PHP Deprecated: +//; s/ in \/.* on line [0-9]+$//' \
| sort | uniq -c | sort -rn | head -30
# vendor/ の中(パッケージの更新で消す分)を、パッケージごとに
grep 'PHP Deprecated' "$LOG" | grep -oE '/vendor/[^/]+/[^/]+/' \
| sort | uniq -c | sort -rnvendor/ の中から出ている通知は、手で直さずにパッケージの更新で消します。ログに通知が 1 件も出ないときは、設定を足したプールを再起動したか、phpinfo() の error_reporting と error_log にプール設定の値が出ているかを確かめてください。
8.4 の bcrypt の既定コストの変更は、コードの差分には現れず、負荷として現れます。コスト 10 と 12 で 1 回のハッシュにかかる時間を移行先のコンテナで測り、ログインが集中する時間帯の件数を掛けて、CPU に余裕があるかを見積もります。余裕が無ければ、password_hash() と password_needs_rehash() の両方に ['cost' => 10] を明示して据え置きます。
docker run --rm php:8.4.27-cli php -r '
foreach ([10, 12] as $c) {
$t = microtime(true);
for ($i = 0; $i < 20; $i++) { password_hash("test-password", PASSWORD_BCRYPT, ["cost" => $c]); }
printf("cost %d: %.1f ms/回\n", $c, (microtime(true) - $t) / 20 * 1000);
}'MySQL の切断エラーのコード(2006 と 4031)と、exit() の関数化は、grep で該当箇所を探して目で確かめます。exit() は 8.4 で関数と同じ扱いになったため、配列などを渡している箇所や、declare(strict_types=1) のファイルで文字列以外を渡している箇所が TypeError になります。
grep -rnE '\b2006\b|CR_SERVER_GONE_ERROR|server has gone away' --include='*.php' --exclude-dir=vendor .
grep -rnE '\b(exit|die)\s*\(\s*\$' --include='*.php' --exclude-dir=vendor .
grep -rnE "\brange\s*\(\s*['\"\$]" --include='*.php' --exclude-dir=vendor .ステージングで 4. のログが 0 件(vendor/ の分は更新で消えた状態)になり、5. の見積もりに問題が無ければ本番を切り替えます。8.1 系は修正版が出ない状態なので、切り替えを先延ばしにする理由はあまりありません。切り替え後もしばらくは log_errors を有効にしたまま、E_DEPRECATED と E_WARNING の件数を日ごとに見ておくと、テストで通らなかった経路の問題を拾えます。
このページの一覧は、人が読むチェックリストであると同時に、Claude Code や Codex にそのまま渡せる作業指示の材料でもあります。8.1 系から 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/8.1.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 8.1-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 移行のルール(8.1.34 → 8.4.27) - 参照資料は https://shinichi.noguchi.jp.net/feature/phpupgrade/8.1.html(保存版: docs/php-upgrade/reference.html)。8.1 系から 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 を 8.1.34(8.1 系)から 8.4.27 へ移行します。CLAUDE.md(Codex の場合は AGENTS.md)の移行ルールに従ってください。 参照資料: https://shinichi.noguchi.jp.net/feature/phpupgrade/8.1.html(保存版: docs/php-upgrade/reference.html) 参照資料の「起点から移行先までに入った変更の全一覧」が、8.1 系から 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 8.1.34 から 8.4.27 への移行作業の差分です。git diff main...HEAD を読み、レビューしてください。参照資料: https://shinichi.noguchi.jp.net/feature/phpupgrade/8.1.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 の変更一覧(8.1 系から 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 8.1.34 から 8.4.27 への移行差分をレビューする。移行作業のコミット後や PR 作成前に使う。 tools: Read, Grep, Glob, Bash --- あなたは PHP の版移行のレビュー担当です。git diff main...HEAD を読み、docs/php-upgrade/reference.html の変更一覧(8.1 系から 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 を 8.1.34(8.1 系)から 8.4.27 へ移行している。 参照資料: https://shinichi.noguchi.jp.net/feature/phpupgrade/8.1.html(保存版: docs/php-upgrade/reference.html)。8.1 系から 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 にして取ったログの実物で確認する。8.1 系から 8.5 までに経由する各版の、php.net の移行ガイドと php-src の UPGRADING です。8.2〜8.4 から上げる場合は、自分の版より後のものだけを見れば足ります。
※2026年10月時点の公開情報に基づきます。バックポートのブランチの中身(版番号、コミット)と Rector のセットの中身は、2026-10-23 に各リポジトリで確認しました。
今回の脆弱性(PHP2Shell、CVE-2026-109618 / CVE-2026-109619)の仕組みと、サーバー側で先に済ませる更新・緩和策は特集トップで解説しています。8.1 系のサーバーをバックポートやベンダーのパッケージで守っている場合、8.2〜8.4 のサーバーをパッチ更新した場合のどちらも、確認の手順はそちらにあります。起点ごとのページへもすべて特集トップから移動できるので、ブックマークは特集トップを 1 つ保存しておいてください。
本特集に掲載したコード・コマンド・設定例・エージェント向けプロンプトは、あくまで説明のための例です。お使いのソースコードや環境でそのまま動くこと、改修が漏れなく完了することを保証するものではありません。各変更点の最終確認は、php.net の公式 ChangeLog・移行ガイド、php-src の UPGRADING など一次情報で必ず行ってください。
本特集の内容は現状有姿で提供するものであり、正確性・完全性・有用性について、瑕疵担保責任・契約不適合責任を含め、当方(野口真一)はいかなる保証も行いません。本特集の内容やコードを利用したこと、または利用できなかったことにより生じた損害について、当方は一切の責任を負いません。
「8.4 と 8.5 のどちらに移すか」「php.ini やサーバー構成をどう替えるか」といった段階から、
移行の計画づくりまで、30 分の無料相談で承ります。