PHP アップグレード特集 | 起点 7.4 系

PHP 7.4.33 から
8.4.27 / 8.3.36 / 8.5.12 へ

7.4.0〜7.4.33 の全パッチ版に共通です。越える山は 8.0 の 1 つだけで、8.0 への直行で済みます。そのかわり 8.1〜8.4 で入る Deprecated が多く、ログを集めて潰す作業が中心になります。

848.4.27 までの変更
688.3.36 までの変更
1068.5.12 までの変更
5経由する版(8.4 まで)

🤝 対応中の同僚と共有してくださいPHP2Shell 対応でコード改修を分担している人に、このページをそのまま渡せます。

𝕏 X で共有 Facebook LINE はてなブックマーク LinkedIn Threads Bluesky Teams メール

このページが当てはまるバージョン

PHP 7.4 系のすべてのパッチ版、7.4.0 から 7.4.33 までの 34 版が対象です。パッチ版の間では後方互換性が保たれるので、7.4.3 で動いていても 7.4.33 で動いていても、8.x へ移すときに直す箇所は同じです。一覧は 7.4 系の最終版 7.4.33(2022-11-03 リリース)を起点に数えています。7.4.34 以降の番号はリリースされていません。

移行先 4 版ごとの項目数は次のとおりです。

  • PHP 7.4.33 → PHP 8.4.27:84 項目(削除 18・非推奨 26・挙動変更 40)
  • PHP 7.4.33 → PHP 8.3.36:68 項目(削除 16・非推奨 19・挙動変更 33)
  • PHP 7.4.33 → PHP 8.5.12:106 項目(削除 19・非推奨 38・挙動変更 49)
  • PHP 7.4.33 → PHP 8.2.35:54 項目(削除 15・非推奨 15・挙動変更 24)
7.4 系の全パッチ版(34 版)を表示

PHP 7.4.0 / PHP 7.4.1 / PHP 7.4.2 / PHP 7.4.3 / PHP 7.4.4 / PHP 7.4.5 / PHP 7.4.6 / PHP 7.4.7 / PHP 7.4.8 / PHP 7.4.9 / PHP 7.4.10 / PHP 7.4.11 / PHP 7.4.12 / PHP 7.4.13 / PHP 7.4.14 / PHP 7.4.15 / PHP 7.4.16 / PHP 7.4.17 / PHP 7.4.18 / PHP 7.4.19 / PHP 7.4.20 / PHP 7.4.21 / PHP 7.4.22 / PHP 7.4.23 / PHP 7.4.24 / PHP 7.4.25 / PHP 7.4.26 / PHP 7.4.27 / PHP 7.4.28 / PHP 7.4.29 / PHP 7.4.30 / PHP 7.4.31 / PHP 7.4.32 / PHP 7.4.33

7.4.11 で入ったセキュリティ修正(bug #79699)により、PHP は受け取った Cookie の名前を URL デコードしなくなりました。%20 などを含む名前の Cookie は、7.4.10 以前とは別の名前として $_COOKIE に入ります。7.4.0〜7.4.10 から移す場合は、下の一覧に加えて、setcookie() に渡している名前と $_COOKIE で読んでいるキーに % を含むものがないかを確認してください。7.4.11〜7.4.33 で動いているサーバーは、すでにこの挙動です(ChangeLog 7.4.11)。

まず結論:どこへ移すか

7.4 系からは 8.4.27 へ直行します。7.4 は 8.0 の直前の版なので、7.x の途中の版を経由する必要はありません。8.0 で削除された書き方の多くは 7.4 ですでに Deprecated になっていて、今の 7.4 のサーバーで E_DEPRECATED を出していれば、8.0 で止まる箇所の大半を移行前に見つけられます。

作業量を左右するのは、8.0 の後ろに続く Deprecated の量です。7.4.33 から 8.4.27 までの 84 項目のうち 26 項目が非推奨で、そのうち 24 項目は 8.1〜8.4 で入ったものです。8.5.12 まで進めると非推奨は 38 項目に増えます。どれも動作は止まりませんが、放っておくと次の版で削除されます。ログを集めて件数の多い順に片付ける段取りを、最初から計画に入れてください。

移行先7.4.33 からの項目数サポート期限向いている場合
8.4.27推奨84(削除 18・非推奨 26・挙動変更 40)2028-12-31基本はこれ。8.0 の山と 8.1〜8.4 の Deprecated を一度に片付ける
8.5.12106(削除 19・非推奨 38・挙動変更 49)2029-12-317.4 起点は 8.0 の作業が比較的軽いので、余力があればここまで進めてサポート期間を延ばす選択肢もある。依存ライブラリの 8.5 対応を先に確認する
8.3.3668(削除 16・非推奨 19・挙動変更 33)2027-12-31IMAP 拡張を使っている、暗黙の nullable の Deprecated を今回は避けたい、といった事情がある場合(8.3.36 までの表示)
8.2.35選ばない54(削除 15・非推奨 15・挙動変更 24)2026-12-312026年10月の時点で残りは約 2 か月

7.4 には PHP2Shell の修正版が正式なリリースとしては出ていません。PHP のセキュリティチームが公開したのは、ソースからビルドする人向けの最小限のバックポートです。サーバー側の当面の対処は特集トップにまとめました。コードの移行は、その対処とは別に進める必要があります。

この起点で特に危ない変更

7.4 から上げるときに、エラーを出さずに結果が変わるもの、1 か所で処理が止まるものを 12 件選びました。7.4 の Deprecated を消していても残る、8.0 以降で新しく入った変更が中心です。8.5.12 まで進める場合に効く項目も 2 件入れています。

  1. 8.0== の比較と switch

    数値と数値でない文字列の == は、文字列どうしの比較に変わりました。switch (0) に case "foo": が並ぶ分岐や、in_array("abc", [0]) のような検索も同じ規則で結果が変わります。

    → 一覧の該当項目へ
  2. 8.0未定義の定数

    $arr[key] のように引用符のないキーは、7.4 では警告を出しつつ文字列として動いていました。8.0 では Error で止まります。ログに警告が出ていたのに放置されていた箇所が、そのまま障害になります。

    → 一覧の該当項目へ
  3. 8.07.4 の非推奨が削除に

    波括弧のオフセット、括弧なしのネスト三項、(real) キャスト、array_key_exists() へのオブジェクトなど、7.4 で Deprecated だったものは 8.0 でまとめて削除されました。7.4 のうちに Deprecated をゼロにしておけば、ここで止まりません。

    → 一覧の該当項目へ
  4. 8.0ソートが安定に

    同じ値の要素の並び順が、入力の順に固定されるようになりました。比較関数が true / false を返していると Deprecated も出ます。並び順を画面や CSV にそのまま出しているところで差が見えます。

    → 一覧の該当項目へ
  5. 8.0substr() などの戻り値

    substr() は範囲外のオフセットで false ではなく空文字列を返します。parse_url() の空のクエリ、pg_fetch_all() の 0 件なども変わりました。=== false で分岐していた箇所が素通りになります。

    → 一覧の該当項目へ
  6. 8.1null 渡しの Deprecated

    7.4 起点で最も件数が出やすい項目です。strlen(null) や str_replace() の subject に null を渡す呼び出しが、DB の NULL 列の数だけ出てきます。?? '' で正規化する修正は単純なので、件数に驚かずに順に片付けます。

    → 一覧の該当項目へ
  7. 8.1継承メソッドの static 変数

    親クラスのメソッドの中の static $cache は、子クラスと共有されるようになりました。子クラスごとにキャッシュが分かれている前提のコードは、エラーなしで別のクラスの値を返します。

    → 一覧の該当項目へ
  8. 8.2"${var}" の補間

    "${name}" の形の文字列補間が非推奨になりました。7.4 までのコードで普通に使われてきた書き方で、テンプレートの多いアプリでは件数がまとまって出ます。

    → 一覧の該当項目へ
  9. 8.3負のキーの次のキー

    空の配列に $a[-5] = 'x' と入れたあと $a[] = 'y' とすると、次のキーが 0 ではなく -4 になります。キーの値を後で使っている処理だけが黙って変わります。

    → 一覧の該当項目へ
  10. 8.4exit() が関数に

    exit と die が関数として扱われ、配列など不正な型を渡すと TypeError になります。strict_types を宣言したファイルでは、引数の型の扱いも厳しくなります。

    → 一覧の該当項目へ
  11. 8.5Opcache の読み込み行

    8.5 では Opcache が PHP 本体に組み込まれ、php.ini の zend_extension=opcache.so は警告になります。7.4 の設定ファイルを持ち込むと起動時に警告が出るので、8.5.12 を選ぶ場合は削除します。

    → 一覧の該当項目へ
  12. 8.5PDO::FETCH_* の値

    PDO::FETCH_GROUP などの定数の値が変わりました。定数名で書いていれば影響はありませんが、数値を直接書いている古いコードは、8.5.12 で別のフェッチモードとして動きます。

    → 一覧の該当項目へ

起点から移行先までに入った変更の全一覧

7.4 系から上げるときに確認する変更を、PHP 8.0 から 8.5 まで 6 つの版の順に並べました。累積の一覧なので、移行先が 8.4.27 なら 8.0〜8.4 のブロック(84 項目)、8.3.36 なら 8.0〜8.3 のブロック(68 項目)が対象です。7.4 で入った変更は手元ですでに動いているので含めていません。

区分は 3 つです。削除は Fatal error・Parse error・Error 例外になるか、機能や拡張そのものがなくなるもの。非推奨は E_DEPRECATED が出るものの動作は続くもの。挙動変更はエラーを出さずに結果やエラーの種類が変わるもので、テストで差を見つけるしかありません。「要確認」の印は、一次情報の本文で裏取りを続けている項目です。項目ごとの v80-01 のような id は、エージェントへの指示や報告にそのまま使えます。

※以下の一覧に載せたコード・コマンド・プロンプトはあくまで例です。最終確認は php.net の公式 ChangeLog・移行ガイドで行ってください。内容の利用により生じた損害について当方は責任を負いません(詳しくはページ末尾の免責事項)。

移行先を選ぶと、その版より先の変更は折りたたまれます

移行先 8.4.27 まで

[8.0] PHP 8.0 で入った変更 26項目:削除 12・非推奨 2・挙動変更 12

  1. 挙動変更

    文字列と数値の緩い比較:数値と非数値文字列の == は数値を文字列化して比較する。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))を全件確認。移行先で最も多く静かに壊れる変更

  2. 削除

    数値文字列の扱い:非数値文字列("abc")との算術・ビット演算は TypeError、先頭のみ数値("5 apples")は E_WARNING「A non-numeric value」で 5 として計算。「Illegal string offset」も TypeError。末尾の空白を含む数値文字列("123 ")は数値扱い

    必要な修正入力を (int) / (float) で明示変換、is_numeric() で検証。計算前の null / '' は 0 に正規化

  3. 削除

    算術・ビット演算子(+ - * / ** % << >> & |(ビット和) ^ ~ ++ --)を配列・リソース・(演算子オーバーロードのない)オブジェクトに適用すると TypeError。配列 + 配列(結合)だけは可

    必要な修正型を確認して演算する。$count + $arr のような誤りを修正

  4. 削除

    未定義定数の裸参照($arr[key]、echo FOO)は Error「Undefined constant」(以前は文字列として解釈して警告)

    必要な修正$arr['key'] のように引用符を付ける。定数は define() / const

  5. 削除

    警告から 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; を明示

  6. 挙動変更

    Notice から Warning に昇格:未定義変数・未定義プロパティ・未定義配列キーの読み取り、非オブジェクトのプロパティ読み取り、非配列への配列アクセス、配列→文字列変換、リソースをキーに使用、null / bool / float の文字列オフセット、範囲外の文字列オフセット、空文字列の文字列オフセット代入。複数バイトの文字列オフセット代入は警告

    必要な修正isset() / ?? / array_key_exists() で防御。ログ量が激増するので display_errors=Off・log_errors=On を確認

  7. 挙動変更

    @ 演算子で致命的エラー(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; に書き換える

  8. 挙動変更

    error_reporting の既定が E_ALL(以前は E_NOTICE と E_DEPRECATED を除外)。display_startup_errors が既定で有効

    必要な修正本番は display_errors=Off、log_errors=On、error_reporting を明示。画面に Deprecated が出て崩れる事故を防ぐ

  9. 削除

    言語機能を削除:(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()、インスタンス経由、括弧で明示

  10. 削除

    内部関数の引数型検査が厳格化:型不一致で TypeError(strlen([])、array_merge(null) 等)、値が不正で ValueError(str_repeat($s, -1)、array_fill の負の個数 等)。以前は警告+null

    必要な修正呼び出し前に型を保証する。null を渡しているなら [8.1] の非推奨と合わせて ?? '' / ?? [] で正規化

  11. 削除

    match / mixed が予約語に。#[ はコメントではなくアトリビュート開始(#[ で始まる行コメントが構文エラー)。名前空間名に空白不可(Foo \ Bar)、予約語を名前空間セグメントに使用可(new\x の解釈が変わる)

    必要な修正#[ で始まるコメントを # [ や // に修正、クラス名・関数名を変更

  12. 挙動変更

    連結演算子 . の優先順位が + - << >> より低くなった。"Sum: " . $a + $b は ("Sum: " . $a) + $b から "Sum: " . ($a + $b) に

    必要な修正括弧で明示(7.4 で E_DEPRECATED が出ていた箇所)

  13. 削除

    定数で null に解決されるデフォルト値(function f(int $x = CONST_NULL))が暗黙に nullable にならない

    必要な修正?int $x = CONST_NULL か int $x = null に

  14. 挙動変更

    assert() は既定で AssertionError 例外を投げる(assert.exception=1)。文字列引数を評価しない。assert.quiet_eval / ASSERT_QUIET_EVAL を削除。名前空間内で assert() を定義不可

    必要な修正assert('$a == $b') は assert($a == $b) に。本番は zend.assertions=-1。旧挙動が必要なら assert.exception=0

  15. 削除

    マジックメソッドのシグネチャ検査(__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)) に

  16. 削除

    継承時のシグネチャ非互換(LSP 違反)は常に Fatal(以前は一部警告)。トレイトの抽象メソッドのシグネチャ検査。トレイトのエイリアス(use T1, T2 { func as other; })で曖昧な参照は Fatal。private メソッドへの final は警告

    必要な修正子クラスのメソッド宣言を親に合わせる(引数の追加・型の狭め・戻り値の広げに注意)

  17. 挙動変更

    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() を明示的に呼ぶ

  18. 挙動変更

    ソートが安定化(同値要素の順序が入力順に固定されるため、以前と結果が変わりうる)。コールバックが参照引数を取る場合は警告(array_filter / array_reduce 等)。ソート比較関数が bool を返すと Deprecated

    必要な修正比較関数は <=> で -1 / 0 / 1 を返す

  19. 挙動変更

    リソースからオブジェクトへ移行: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() を使うコードを修正

  20. 挙動変更

    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])

  21. 削除

    関数・定数を削除: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

  22. 削除

    ext/xmlrpc を本体から削除(PECL へ)

    必要な修正PECL xmlrpc(メンテナンス縮小)か、純 PHP の XML-RPC ライブラリ / JSON-RPC に移行

  23. 挙動変更

    標準関数の挙動変更: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 を継承したクラスを確認

  24. 非推奨

    必須引数の後にデフォルト値付き引数を置く function f($a = [], $b) を非推奨($a は必須扱いになりデフォルト値は無視される。Type $a = null の形だけは暗黙 nullable として許容、ただし [8.4] で非推奨)

    必要な修正引数順を入れ替えるか、デフォルト値を外す

  25. 非推奨

    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

  26. 挙動変更

    新規組み込み関数の追加(str_contains / str_starts_with / str_ends_with / fdiv / get_debug_type / get_resource_id / preg_last_error_msg)。同名の自作関数(str_contains のポリフィルが典型)があると Fatal

    必要な修正function_exists() ガード

[8.1] PHP 8.1 で入った変更 15項目:削除 2・非推奨 7・挙動変更 6

  1. 非推奨

    内部関数の非 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 を集計して潰す

  2. 挙動変更

    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)

  3. 挙動変更

    htmlspecialchars() / htmlentities() / htmlspecialchars_decode() / html_entity_decode() / get_html_translation_table() の既定フラグが ENT_COMPAT|ENT_HTML401 から ENT_QUOTES|ENT_SUBSTITUTE|ENT_HTML401 に。シングルクォートが &#039; に変換され、不正なバイト列は空文字列ではなく U+FFFD に置換される

    必要な修正旧挙動が必要なら ENT_COMPAT を明示。出力の期待値(テスト・スナップショット・属性値内のクォート処理)を見直す

  4. 挙動変更

    PDO MySQL(エミュレートプリペアド有効時)と PDO SQLite が整数・浮動小数点の列を文字列ではなく int / float で返す(ネイティブ型)

    必要な修正=== '1' のような厳密比較、json_encode() の出力("1" → 1)、文字列連結前提のコードを修正。旧挙動は PDO::ATTR_STRINGIFY_FETCHES => true。mysqli でも mysqlnd はネイティブ型を返す点に注意

  5. 削除

    $GLOBALS 全体への書き込み($GLOBALS = [])、参照取得(&$GLOBALS、foreach ($GLOBALS as &$v))、配列関数による破壊的操作(array_pop($GLOBALS))はコンパイルエラー。$GLOBALS['x'] = 1 や $GLOBALS の読み取り・コピーは従来どおり

    必要な修正個別キーの操作に書き換える

  6. 挙動変更

    継承したメソッド内の static 変数が親クラスと子クラスで共有される(以前は子クラスごとに独立)

    必要な修正子クラスごとの状態が必要なら static プロパティや配列に切り替える

  7. 挙動変更

    リソースからオブジェクトへ移行: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 に

  8. 非推奨

    内部クラス・インターフェースのメソッドをオーバーライドする際の戻り値型の非互換(ArrayAccess::offsetGet() / IteratorAggregate::getIterator() / Countable::count() / JsonSerializable::jsonSerialize() / Iterator::current() などを戻り値型なしで実装)

    必要な修正戻り値型(: mixed / : Iterator / : int 等)を宣言するか、7.x 互換を保つなら #[\ReturnTypeWillChange] を付ける

  9. 非推奨

    Serializable インターフェースを __serialize() / __unserialize() なしで実装することを非推奨

    必要な修正両マジックメソッドを追加実装する(Serializable は PHP 9 で削除予定)

  10. 非推奨

    精度を失う float から int への暗黙変換($arr[1.5]、"1.5" % 2、int 型引数への 1.5、1.5 << 1)を非推奨

    必要な修正明示的に (int) キャスト

  11. 非推奨

    false からの自動配列化($a = false; $a[] = 1;)を非推奨(null / 未定義からは引き続き可)

    必要な修正$a = []; で初期化する。関数が失敗時に false を返し、それに [] 代入するパターンに注意

  12. 非推奨

    トレイトの static メソッド・static プロパティへの直接アクセス(MyTrait::method() / MyTrait::$prop)を非推奨

    必要な修正トレイトを使用するクラス経由でアクセス

  13. 非推奨

    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) キャスト、接続を明示、ファイル読み込みで改行を正規化

  14. 挙動変更

    mysqli_fetch_fields() / mysqli_fetch_field_direct() の max_length が常に 0。version_compare() が未文書化の演算子省略形を拒否

    必要な修正最大長が必要なら mysqli_result::fetch_field() 前に store_result、version_compare の第 3 引数は 'lt' / 'ge' 等の正式表記

  15. 削除

    readonly / never が予約語に(クラス名・関数名に使えない)。enum が宣言構文のキーワードになり、enum の直後に識別子が続くコードの解釈が変わる。新規組み込み関数の追加(array_is_list / enum_exists / fsync / fdatasync)で同名の自作関数は Fatal

    必要な修正リネーム、function_exists() ガード

[8.2] PHP 8.2 で入った変更 13項目:削除 1・非推奨 6・挙動変更 6

  1. 非推奨

    動的プロパティの生成(宣言していないプロパティへの $obj->foo = 1)を非推奨(stdClass と __get() / __set() 実装クラスは対象外)。PHP 9 で Error 予定

    必要な修正プロパティを宣言する(推奨)。古いクラスを一括で延命するなら #[\AllowDynamicProperties] をクラスに付与(子クラスにも適用)。他人のオブジェクトにデータを付けるなら WeakMap

  2. 非推奨

    "${var}" / "${expr}" 形式の文字列補間を非推奨

    必要な修正"$var" / "{$var}" / "{${expr}}" に書き換える。テンプレート系の古いコードは sed で一括置換しやすい

  3. 非推奨

    utf8_encode() / utf8_decode() を非推奨([9.0] で削除予定)

    必要な修正mb_convert_encoding($s, 'UTF-8', 'ISO-8859-1') / iconv()。Shift_JIS 変換の意図で utf8_encode を誤用しているコード(Latin-1 専用)は根本から直す

  4. 非推奨

    相対的なコールアブル:"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(...))に置換

  5. 非推奨

    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() に置換

  6. 非推奨

    SplFileInfo::_bad_state_ex() を非推奨

    必要な修正呼ばない(内部用)

  7. 挙動変更

    strtolower() / strtoupper() / stripos() / strripos() / lcfirst() / ucfirst() / ucwords() / str_ireplace() がロケール非依存(ASCII のみ変換)に

    必要な修正マルチバイトやロケール依存の変換は mb_strtolower() / mb_strtoupper() / mb_convert_case() に

  8. 挙動変更

    str_split('') が [''] ではなく [] を返す

    必要な修正空配列を前提に修正(foreach は影響なし、count() や [0] 参照に注意)

  9. 挙動変更

    ksort() / krsort() の SORT_REGULAR が数値文字列を PHP 8 の比較規則(文字列と数値の比較変更)で並べる

    必要な修正順序依存のコードを確認。数値キーなら SORT_NUMERIC を明示

  10. 挙動変更

    var_export() がクラス名を先頭 \ 付きの完全修飾名で出力(\Foo\Bar::__set_state(...))。strcmp() / strcasecmp() / strncmp() / strncasecmp() / substr_compare() が差分値ではなく -1 / 0 / 1 を返す

    必要な修正var_export の出力を文字列比較・保存しているコードを修正。strcmp の戻り値を差分値として使うコード(=== 2 など)を < 0 / > 0 判定に

  11. 挙動変更

    glob() は open_basedir で全ファイルが除外されると false ではなく [] を返す。FilesystemIterator のコンストラクタに flags を明示指定した場合、SKIP_DOTS が自動では付かなくなった(. と .. が列挙される)。ODBC / PDO_ODBC が接続文字列中のユーザー名・パスワードをエスケープする

    必要な修正戻り値判定の修正、flags を渡す場合は FilesystemIterator::SKIP_DOTS を含める、DSN にユーザー名を埋め込んでいる場合は引数と二重指定しない

  12. 削除

    mysqli が libmysqlclient でビルドできなくなり mysqlnd 必須。mysqli.reconnect INI と自動再接続機能を削除

    必要な修正接続切れは明示的に再接続する([8.4] で mysqli_ping() も非推奨)。ディストリビューションのパッケージ構成を確認

  13. 挙動変更

    新規組み込み関数の追加(mysqli_execute_query / openssl_cipher_key_length / curl_upkeep / memory_reset_peak_usage / ini_parse_quantity / libxml_get_external_entity_loader 等)。同名の自作関数は Fatal

    必要な修正function_exists() ガード

[8.3] PHP 8.3 で入った変更 14項目:削除 1・非推奨 4・挙動変更 9

  1. 挙動変更

    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) キャスト

  2. 挙動変更

    空配列に負のインデックス $n を代入すると次の暗黙キーが $n + 1 になる(以前は 0)。例:$a = []; $a[-5] = 'x'; $a[] = 'y'; → キーは -5, -4

    必要な修正負のキーの後に [] 追加するコードを確認

  3. 挙動変更

    トレイトの static プロパティが親クラスから継承したものを再宣言する(クラスごとに独立したストレージ)。クラス定数の可視性分散をインターフェース継承でも検査

    必要な修正共有を期待していたコードを修正

  4. 挙動変更

    Date 拡張が DateError / DateException 階層の例外・エラーを投げる(new DateTime('不正') は DateMalformedStringException、new DateTimeZone('不正') は DateInvalidTimeZoneException、DateInterval の不正文字列は DateMalformedIntervalStringException など。以前は警告や汎用 Exception)

    必要な修正catch (Exception $e) は引き続き捕捉できる。例外クラス名で分岐しているコードとメッセージのスナップショットを確認

  5. 挙動変更

    proc_get_status() を複数回呼んでも正しい値を返す(結果をキャッシュ、"cached" キー追加)。呼び出しスタックの上限に近いコードは Error 例外(zend.max_allowed_stack_size)。WeakMap の自己参照キーが GC で回収される。ZTS ビルド(Linux)で Zend Max Execution Timers が既定で有効

    必要な修正深い再帰のコードはスタック使用量を見直す

  6. 挙動変更

    DOM:親のないノードへの after() / before() / replaceWith() が例外ではなく no-op、DOMParentNode / DOMChildNode のメソッドが document なしでも動作、createAttributeNS() の既定名前空間・接頭辞衝突の修正、DOM クラスに新メソッド・プロパティが追加されたため継承クラスの同名宣言は互換が必要

    必要な修正DOMDocument 等を継承しているクラスの宣言を確認

  7. 削除

    opcache.consistency_checks INI を削除。FFI の void 関数は CData ではなく null を返す。Phar / SNMP のクラス定数に型を宣言

    必要な修正php.ini から削除、FFI の戻り値判定を修正

  8. 挙動変更

    number_format() が負の decimals を解釈する(num を abs(decimals) 桁で整数部側に丸める。以前は無視)。file() が無効なフラグ(FILE_APPEND など)を拒否

    必要な修正負の decimals を渡しているなら 0 に、file() のフラグを確認

  9. 挙動変更

    unserialize() のエラーが E_NOTICE から E_WARNING に。array_sum() / array_product() が + / * 演算子と同じ型変換規則で計算し、対応しない型(配列・オブジェクト)には E_WARNING を出す。highlight_file() / highlight_string() の HTML 出力形式が変更(<pre><code> でラップ、改行を \n に)

    必要な修正エラーハンドラの扱いを確認、配列の合計に非数値が混ざっていないか確認、highlight 系の出力に CSS を当てている場合は調整

  10. 非推奨

    空文字列・非数値文字列・英数字以外の文字列への ++、空文字列・非数値文字列への -- を非推奨(Perl 風の文字列インクリメント "a"++ → "b" はソフト非推奨)

    必要な修正数値は (int) キャストしてから演算、文字列の連番は str_increment() / str_decrement()

  11. 非推奨

    get_class() / get_parent_class() の引数なし呼び出しを非推奨

    必要な修正static::class / self::class / get_class($this) / get_parent_class($this)

  12. 非推奨

    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) 等の呼び出しを削除

  13. 非推奨

    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 は例外ベースのエラー処理に移行

  14. 挙動変更

    新規組み込み関数の追加(json_validate / str_increment / str_decrement / mb_str_pad / stream_context_set_options / ldap_exop_sync 等)。同名の自作関数(json_validate のポリフィルが典型)は Fatal

    必要な修正function_exists() ガード

[8.4] PHP 8.4 で入った変更 16項目:削除 2・非推奨 7・挙動変更 7

  1. 削除

    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 に移行

  2. 非推奨

    暗黙の 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)で一括変換可

  3. 非推奨

    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() を使う

  4. 挙動変更

    exit() / die() が言語構造から関数相当に変更。exit; の形も引き続き可だが、配列など不正な型の引数は TypeError、strict_types や名前付き引数の影響を受ける

    必要な修正exit(array) 等の誤用を修正

  5. 挙動変更

    round() の内部実装を書き換え(事前丸めを廃止、0.49999999999999994 の丸めが 1 から 0 になるなど)。一部の境界値で結果が以前と変わる。無効な mode は ValueError

    必要な修正金額計算など厳密な丸めは BCMath(bcround は 8.4 で追加)や intdiv を検討

  6. 挙動変更

    password_hash() の bcrypt(PASSWORD_BCRYPT / PASSWORD_DEFAULT)の既定コストが 10 から 12 に。既存のハッシュに対して password_needs_rehash() が true を返し、ログイン時の再ハッシュで CPU 負荷が約 4 倍になる

    必要な修正負荷を据え置くなら ['cost' => 10] を明示。再ハッシュ処理の実装を確認

  7. 挙動変更

    リソースからオブジェクトへ移行:dba_open() / dba_popen() → Dba\Connection、odbc_connect() / odbc_pconnect() → Odbc\Connection、odbc_exec() 等 → Odbc\Result、SoapClient::$httpurl / $sdl / $typemap → 内部オブジェクト

    必要な修正is_resource() 判定を修正

  8. 削除

    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 を継承したクラスの修正、定数の削除

  9. 挙動変更

    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} にエスケープ

  10. 挙動変更

    不正な引数が警告や 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() の一部引数)

    必要な修正呼び出し前に引数を検証する

  11. 非推奨

    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 で実行

  12. 非推奨

    セッション: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 のみに

  13. 非推奨

    fputcsv() / fgetcsv() / str_getcsv() および SplFileObject::fputcsv() / fgetcsv() / setCsvControl() の escape 引数の省略(既定 "\\")を非推奨

    必要な修正escape: '' を明示する(RFC 4180 準拠にするなら空文字列)。既存 CSV がバックスラッシュを含む場合は "\\" を明示して互換維持

  14. 非推奨

    xml_set_object() と xml_set_*() ハンドラ登録への非 callable 文字列(メソッド名だけの文字列)を非推奨

    必要な修正[$this, 'startElement'] のような callable 配列かクロージャを渡す

  15. 非推奨

    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 を非推奨

    必要な修正括弧内の代替へ

  16. 挙動変更

    新規組み込み関数の追加(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() ガード

[8.5] PHP 8.5 で入った変更 22項目:削除 1・非推奨 12・挙動変更 9

  1. 非推奨

    バッククォート演算子 `command`(shell_exec のエイリアス)を非推奨

    必要な修正shell_exec() に置換(さらに escapeshellarg() を確認)

  2. 非推奨

    非正規のキャスト名 (boolean) / (integer) / (double) / (binary) を非推奨

    必要な修正(bool) / (int) / (float) / (string) に一括置換

  3. 非推奨

    case 文をコロンではなくセミコロンで終える case 1; を非推奨

    必要な修正case 1: に修正

  4. 非推奨

    null を配列オフセットに使うこと($arr[null]、isset($arr[$undefined]) の $undefined が null)と array_key_exists(null, $arr) を非推奨

    必要な修正キーを '' に正規化するか、null にならないよう事前チェック。未初期化変数をキーに使う古いコードが典型

  5. 非推奨

    非数値文字列のインクリメント("a"++ → "b")を非推奨([8.3] のソフト非推奨が正式な E_DEPRECATED に)

    必要な修正str_increment() に置換

  6. 非推奨

    $http_response_header(file_get_contents('http://...') 後に自動定義されるローカル変数)を非推奨

    必要な修正http_get_last_response_headers()(8.4 で追加)に置換

  7. 非推奨

    register_argc_argv による非 CLI SAPI での $_SERVER['argv'] / argc のクエリ文字列からの導出を非推奨

    必要な修正register_argc_argv=0 に設定し $_GET / $_SERVER['QUERY_STRING'] を使う

  8. 非推奨

    __sleep() / __wakeup() をソフト非推奨(E_DEPRECATED なし)。__debugInfo() の null 返却、定数の再宣言、report_memleaks INI、出力ハンドラ内での出力、不正なクロージャのバインド(static クロージャへのインスタンス束縛、無関係なクラスへのメソッド束縛、$this のアンバインド、内部クラスのスコープへの束縛、関数由来クロージャのスコープ変更)を非推奨

    必要な修正__serialize() / __unserialize() を実装、空配列を返す、定数の二重定義を解消、Closure::bind の使い方を見直す

  9. 非推奨

    curl_close() / curl_share_close()、finfo_close()(と finfo_buffer() の context 引数)、imagedestroy()、xml_parser_free() を非推奨([8.0] / [8.1] 以降すでに no-op)

    必要な修正呼び出し行を削除するだけでよい

  10. 非推奨

    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) のように明示

  11. 非推奨

    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' を書いている箇所が典型

  12. 非推奨

    Reflection*::setAccessible()(8.1 以降は無効)、存在しない定数への ReflectionClass::getConstant()、デフォルト値のないプロパティへの ReflectionProperty::getDefaultValue()、spl_autoload_unregister(spl_autoload_call(...)) による全解除、SplObjectStorage::contains() / attach() / detach()(→ offsetExists() / offsetSet() / offsetUnset())、ArrayObject / ArrayIterator へのオブジェクト渡しを非推奨

    必要な修正括弧内の代替へ。setAccessible() は行ごと削除

  13. 削除

    disable_classes INI を削除

    必要な修正php.ini から削除。クラス単位の無効化が必要なら別の手段(オートローダーで弾く等)

  14. 挙動変更

    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 オプションも削除

  15. 挙動変更

    class_alias() で "array" / "callable" を別名に使えない。比較不能なオブジェクト(enum、CurlHandle 等の内部クラス)と bool の緩い比較が常に (bool)$object の結果に統一。トレイトが親クラスより先にバインドされる。コンパイル時・クラスリンク時のエラーが常に遅延処理される。#[\Attribute] を抽象クラス・enum・インターフェース・トレイトに付けるとコンパイルエラー(#[\DelayedTargetValidation] で実行時に遅延可)。gc_collect_cycles() の戻り値に間接回収分を含まない。tick ハンドラの停止タイミングが shutdown 関数・デストラクタ・出力ハンドラ処理の後に

    必要な修正該当コードを確認

  16. 挙動変更

    null 以外の非配列(int / string / object)を [] や list() で分割代入すると E_WARNING。int で表現できない float(や float に見える文字列)の int キャストで警告、NAN のキャストで警告

    必要な修正分割代入の前に is_array() で検査、桁あふれの計算を見直す

  17. 挙動変更

    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 バイトを含む文字列で例外 / 警告

    必要な修正定数名で参照する、コンストラクタ引数の配列を整数キーに

  18. 挙動変更

    mysqli:構築済みオブジェクトでの mysqli コンストラクタ再呼び出し($db->__construct(...) による再接続)は Error。セッション:$_SESSION のキーにパイプ文字(|)を含むと警告(以前は保存を静かに失敗)、session_start() の options が連想配列でないと ValueError、read_and_close が int 互換でないと TypeError。SimpleXMLElement::xpath() がノード集合以外を返す式で警告+false(以前は空配列)

    必要な修正再接続は新しい mysqli を生成、セッションキーからパイプ文字を除去、xpath の戻り値判定を修正

  19. 挙動変更

    Intl は ICU 57.1 以上が必須。IntlDateFormatter::setTimeZone() が未初期化時に IntlException。Locale メソッドへの NUL バイトで ValueError。Collator::SORT_REGULAR の数値文字列の扱いを ext/standard の SORT_REGULAR に統一。mbstring の Unicode データが 17.0 に更新

    必要な修正ビルド環境の ICU を確認、ソート順の期待値を確認

  20. 挙動変更

    不正な引数で 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 のカーソル名長

    必要な修正呼び出し前に引数を検証

  21. 挙動変更

    その他: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 宣言を確認

  22. 挙動変更

    新規組み込み関数の追加(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.4 起点向けに具体化しました。7.4 起点がほかの 7.x と違うのは、Rector と PHPStan の現行版が 7.4 の上でも動くことです(どちらも PHP 7.4 以上が要件)。とはいえ、移行先の版を前提にした解析は移行先の PHP で動かすほうが確実なので、ここでも下の「任せる前に用意しておくもの」で作る php-upgrade:8.4.27 のコンテナを主に使います。

1. 7.4 のうちに Deprecated をゼロにする

最初の作業は、まだ 7.4 で動いている環境でできます。error_reporting=E_ALL で動かしてログに出る Deprecated は、8.0 で削除されたものとほぼ重なります(v80-09)。ここを 7.4 の上で直しておくと、8.0 に上げたときに Parse error や Fatal error で止まる箇所が大きく減り、しかも 7.4 のままリリースできます。

7.4.33 のコンテナで Deprecated を集める
mkdir -p docs/php-upgrade
docker run --rm -it -p 8080:8080 -v "$PWD":/app -w /app php:7.4.33-cli \
  php -d error_reporting=E_ALL -d display_errors=Off -d log_errors=On \
      -d error_log=/app/docs/php-upgrade/php74-errors.log -S 0.0.0.0:8080 -t public
# 主要な画面を操作したあと、Deprecated だけを種類ごとに数える
grep 'PHP Deprecated' docs/php-upgrade/php74-errors.log \
  | sed -E 's/^\[[^]]*\] //; s/ in \/app\/.* on line [0-9]+$//' | sort | uniq -c | sort -rn

同じコンテナで主要な処理の出力を docs/php-upgrade/smoke-7.4.33.txt に保存しておくと、移行後に php:8.4.27-cli で動かした結果と diff で比べられます。

2. 拡張と依存パッケージを確かめる

7.4 のサーバーで実行
php -m > docs/php-upgrade/php74-modules.txt
# 8.0 で外れた xmlrpc、8.4 で外れた IMAP / OCI8 / PSpell を呼んでいないか
grep -rnE '\b(xmlrpc_|imap_|oci_|pspell_)' --include='*.php' --exclude-dir=vendor .
composer why-not php 8.4.27
composer outdated --direct

8.0 に上げると、curl や GD などの関数が返すものがリソースからオブジェクトに変わります(v80-19)。is_resource($ch) で接続を確かめているコードは false になるので、grep で is_resource を探しておきます。

3. PHPCompatibility と Rector

7.4 起点では、PHPCompatibility の指摘は 8.0 の削除と 8.1 以降の非推奨が中心になります。Rector は withPhpSets() を php80 と php84 の 2 回に分けると、8.0 の山と 8.1〜8.4 の Deprecated 対応がコミットで分かれ、レビューしやすくなります。

php-upgrade:8.4.27 のコンテナで実行
vendor/bin/phpcs -ps . --standard=PHPCompatibility --runtime-set testVersion 7.4-8.4 \
  --ignore=*/vendor/* --report=summary
# rector.php を ->withPhpSets(php80: true) にして
vendor/bin/rector process --dry-run > docs/php-upgrade/rector-80.diff
vendor/bin/rector process && git commit -am "Rector: PHP 8.0 まで"
# 次に ->withPhpSets(php84: true) にして同じ手順(暗黙の nullable などはここで直る)

4. 8.1〜8.4 の Deprecated を件数の多い順に潰す

移行先のコンテナで同じようにログを取り、今度は Deprecated の件数を数えます。7.4 起点では null 渡し、動的プロパティ、暗黙の nullable、"${var}" の補間 が上位に来やすい項目です。メッセージの種類ごとに数えて、件数の多いものから 1 種類ずつ直し、コミットも種類ごとに分けます。

8.4.27 での残件を数える
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/php84-errors.log -S 0.0.0.0:8080 -t public
# 種類ごとの件数(Deprecated 以外の Warning も含めて見る)
sed -E 's/^\[[^]]*\] //; s/ in \/app\/.* on line [0-9]+$//' docs/php-upgrade/php84-errors.log \
  | sort | uniq -c | sort -rn | head -40
# 0 件になったことの確認
grep -c 'PHP Deprecated' docs/php-upgrade/php84-errors.log

@ 演算子や error_reporting から E_DEPRECATED を外して黙らせる対応は、今回の移行では避けてください。非推奨の多くは次の版で削除される予定のもので、隠しておくと次の移行のときに同じ作業がまとめて戻ってきます。

5. ステージングで切り替える

ステージングに php:8.4.27-fpm などの移行先を用意し、本番と同じ php.ini で動かします。8.0 から error_reporting の既定が E_ALL になった(v80-08)ので、本番の設定では display_errors=Off と log_errors=On を明示します。手順 1 で保存した 7.4.33 の出力と移行先の出力を diff で比べ、差が出た箇所を項目 id と対応付けてから本番に切り替えます。

コーディングエージェントに一括で任せる

このページの一覧は、人が読むチェックリストであると同時に、Claude Code や Codex にそのまま渡せる作業指示の材料でもあります。7.4 系から 8.4.27 までの変更は項目数が多いので、全部を手で追うより、機械で拾えるものは静的解析と Rector に任せ、エージェントには「一覧と突き合わせて残りを探し、直した理由を項目 id で説明する」ところまでを頼むのが現実的です。

ただし、エラーを出さずに結果だけが変わる種類の変更は、ツールでもエージェントでも見落としが出ます。この節の最後にある「検収で人が見るところ」は、エージェントの報告を受け取ったあとに必ず人が確認してください。

※ツール名・設定の書き方・コマンドは、2026年10月時点の各公式ドキュメントで確認したものです。

任せる前に用意しておくもの

  • 作業ブランチ。git switch -c php-upgrade-8.4 のように移行専用のブランチを切り、エージェントには main を直接触らせない。
  • 今の動きを記録したテスト。テストが無いなら、移行前(7.4.33 のサーバーやステージング)で主要な画面・API・バッチの出力を保存しておく。移行後に同じ入力で比べられれば十分で、立派なテストでなくて構いません。
  • 移行先の PHP を動かすコンテナ。Docker の公式イメージには php:8.4.27-cli のようにパッチ版まで固定したタグがあります。ホストの PHP ではなく、このコンテナで解析とテストを動かします。
  • 参照資料の保存。このページを docs/php-upgrade/reference.html として保存しておくと、エージェントがネットワークに出られない設定でも一覧を読めます。
docker/php-upgrade/Dockerfile
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 /app
準備のコマンド
git 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.4.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

機械的に洗い出す道具

エージェントに頼む前に、決まった規則で拾える変更はツールで拾っておきます。エージェントにもこれらを実行させ、その結果を作業の起点にさせると、読み落としが減ります。

ツール何をするか公式
PHPCompatibilityPHP_CodeSniffer の規則集。指定した PHP の版で削除・非推奨になった関数や構文を静的に検出する。testVersion に移行先の版を渡す。GitHub
RectorPHP の版ごとの書き換え規則を持つ自動リファクタリングツール。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
Composercomposer 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 を作って入れてください。

PHPCompatibility
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.4-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 ずつ上げる方法も公式に用意されています。

rector.php
<?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);
Rector の実行
composer require rector/rector --dev
vendor/bin/rector process --dry-run   # 差分を見るだけ
vendor/bin/rector process             # 確認してから適用

PHPStan は、phpVersion を指定しないと PHPStan を動かしている PHP の版が前提になります。移行先のコンテナで動かすならそのままでも合いますが、設定ファイルに書いておくほうが取り違えがありません。古いコードにいきなり高いレベルを当てると指摘が多すぎて読めないので、0 から始めて上げていきます。

phpstan.neon
parameters:
    level: 0
    phpVersion: 80400 # PHP 8.4。8.5 なら 80500
    paths:
        - src
        - public

Claude Code に頼む

Claude Code は、リポジトリ直下の CLAUDE.md を毎回読み込みます。移行の間だけ守らせたい約束をここに書いておくと、指示のたびに繰り返さずに済みます。

CLAUDE.md に追記する移行ルール
## PHP 移行のルール(7.4.33 → 8.4.27)

- 参照資料は https://shinichi.noguchi.jp.net/feature/phpupgrade/7.4.html(保存版: docs/php-upgrade/reference.html)。7.4 系から 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.4.33(7.4 系)から 8.4.27 へ移行します。CLAUDE.md(Codex の場合は AGENTS.md)の移行ルールに従ってください。

参照資料: https://shinichi.noguchi.jp.net/feature/phpupgrade/7.4.html(保存版: docs/php-upgrade/reference.html)
参照資料の「起点から移行先までに入った変更の全一覧」が、7.4 系から 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.4.33 から 8.4.27 への移行作業の差分です。git diff main...HEAD を読み、レビューしてください。参照資料: https://shinichi.noguchi.jp.net/feature/phpupgrade/7.4.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 の例
# ③の突き合わせだけを、コードを変更させずに実行する
claude -p "docs/php-upgrade/reference.html の変更一覧(7.4 系から 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/ に置いたファイルはチームで共有できます。

.claude/agents/php-upgrade-reviewer.md
---
name: php-upgrade-reviewer
description: PHP 7.4.33 から 8.4.27 への移行差分をレビューする。移行作業のコミット後や PR 作成前に使う。
tools: Read, Grep, Glob, Bash
---

あなたは PHP の版移行のレビュー担当です。git diff main...HEAD を読み、docs/php-upgrade/reference.html の変更一覧(7.4 系から 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 に渡されます(編集そのものは取り消されません)。

.claude/settings.json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/phpstan-changed.sh"
          }
        ]
      }
    ]
  }
}
.claude/hooks/phpstan-changed.sh(chmod +x する)
#!/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 0

Codex に頼む

Codex は AGENTS.md を読みます。リポジトリのルートから作業ディレクトリまでの各階層の AGENTS.md が順に結合され、作業場所に近いものが優先されます。内容は CLAUDE.md の移行ルールと同じで構いません。

AGENTS.md
# AGENTS.md

## このリポジトリで進めている作業
PHP を 7.4.33(7.4 系)から 8.4.27 へ移行している。
参照資料: https://shinichi.noguchi.jp.net/feature/phpupgrade/7.4.html(保存版: docs/php-upgrade/reference.html)。7.4 系から 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 に保存し、段階を指定して流します。

codex exec の例
# ①インベントリだけ(読み取り専用のまま。最後の報告を -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 -

MCP サーバーとプラグイン

必須ではありませんが、次のものは移行作業で役に立ちます。どれも公式のリポジトリかドキュメントで存在と導入方法を確認できたものだけを載せています。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@latest
Codex: 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() の結果。金額や件数の端数処理、ページ送りの範囲を、移行前の出力と突き合わせる。
  • PDO で取得した値の型。文字列で返っていた数値が int や float で返るようになると、=== の比較や JSON の出力が変わる。
  • 内部関数に null を渡している箇所。キャストで警告を消しただけになっていないか。
  • 動的プロパティ。#[\AllowDynamicProperties] で逃がした箇所が、本当にそのままでよいか。
  • E_DEPRECATED がゼロであること。エージェントの報告ではなく、移行先のコンテナで error_reporting=E_ALL にして取ったログの実物で確認する。

エージェントに任せてはいけないこと

  • 業務ロジックの意味が変わる修正の判断。比較や丸めの結果が変わったとき、新しい結果を正とするか、旧来の結果を保つように書き直すかは、業務を知る人が決める。
  • DB の文字コードの移行。接続の charset や照合順序の変更は、既存データの変換と切り戻しの計画が要る。エージェントには調査と手順書の下書きまでを頼む。
  • 本番環境の切り替え。PHP の入れ替え、FPM や Web サーバーの設定変更、リリースの判断と切り戻しは人が行う。

公式の移行ガイド

7.4 系から上げるときに経由する版の、php.net の移行ガイドと php-src の UPGRADING です。上の一覧の各ブロックの冒頭にも同じリンクを置いています。

※2026年10月時点の公開情報に基づきます。項目の区分や修正方法は php.net の移行ガイドと php-src の UPGRADING をもとに整理したもので、「要確認」の印がある項目は一次情報の本文で裏取りを続けています。

このページはソースコードの改修を扱っています。PHP2Shell(CVE-2026-109618 / CVE-2026-109619)の仕組みと、サーバー側で先に済ませる更新は特集トップで解説しました。7.4 向けにはソースビルド用の最小限のバックポートしか出ていないので、移行が終わるまでの暫定的な守り方もトップを見てください。起点ごとのページへはトップから飛べるので、ブックマークはトップにしておくと便利です。

PHP アップグレード特集のトップへ戻る

免責事項(瑕疵担保・保証はありません)

本特集に掲載したコード・コマンド・設定例・エージェント向けプロンプトは、あくまで説明のための例です。お使いのソースコードや環境でそのまま動くこと、改修が漏れなく完了することを保証するものではありません。各変更点の最終確認は、php.net の公式 ChangeLog・移行ガイド、php-src の UPGRADING など一次情報で必ず行ってください。

本特集の内容は現状有姿で提供するものであり、正確性・完全性・有用性について、瑕疵担保責任・契約不適合責任を含め、当方(野口真一)はいかなる保証も行いません。本特集の内容やコードを利用したこと、または利用できなかったことにより生じた損害について、当方は一切の責任を負いません。

PHP 7.4 からの移行をご相談ください

移行先の選び方、影響範囲の見積もり、Rector やコーディングエージェントを使った進め方まで、
30分の無料相談で承ります。

無料相談を予約する →