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

PHP 5.5.38 から
8.4.27 / 8.3.36 / 8.5.12 へ

5.5.0〜5.5.38 のどのパッチ版にも同じ内容が当てはまります。ext/mysql の非推奨はもうログに出ているはずの起点です。残るのは 7.0 と 8.0 の山と、数値・文字列・正規表現の細かな挙動変更で、7.4 を中継点にして 2 回に分けて越えます。

1728.4.27 まで
1568.3.36 まで
1948.5.12 まで
118.4 まで経由するマイナー版

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

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

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

このページは PHP 5.5 系のすべて、5.5.0 から最終版の 5.5.38(2016-07-21 公開)までの 39 版を対象にしています。パッチ版の間では後方互換性が保たれるため、5.5.9 から移す場合も 5.5.38 から移す場合も、確かめる変更は同じです。Ubuntu 14.04 LTS の標準パッケージは 5.5.9 で、ディストリビューションが修正を取り込みながら番号を据え置いていたので、php -v に 5.5.9 と出るサーバーもこのページの対象です。

移行先は 2026-10-21 公開の 4 つのセキュリティ修正版で、組み合わせごとの項目数は次のとおりです。

  • PHP 5.5.38 → PHP 8.4.27:172 項目(削除 44・非推奨 43・挙動変更 85)。経由するマイナー版は 5.6〜8.4 の 11 版
  • PHP 5.5.38 → PHP 8.5.12:194 項目(削除 45・非推奨 55・挙動変更 94)。5.6〜8.5 の 12 版
  • PHP 5.5.38 → PHP 8.3.36:156 項目(削除 42・非推奨 36・挙動変更 78)。5.6〜8.3 の 10 版
  • PHP 5.5.38 → PHP 8.2.35:142 項目(削除 41・非推奨 32・挙動変更 69)。5.6〜8.2 の 9 版

5.5 系の最終版は 5.5.38 で、5.5.39 以降の番号はありません。「PHP 5.5.39」「PHP 5.5.40」のような番号で探してここに来た場合も、php -v の先頭が 5.5 であれば、このページの内容が当てはまります。5.5 系のパッチ版の間で、コードの修正を要する挙動変更は資料の範囲では見つかっていません。セキュリティ修正に伴う細かな変更は ChangeLog の各パッチの項を見てください。

5.5 系の全パッチ版(39 版)を表示

PHP 5.5.0 / PHP 5.5.1 / PHP 5.5.2 / PHP 5.5.3 / PHP 5.5.4 / PHP 5.5.5 / PHP 5.5.6 / PHP 5.5.7 / PHP 5.5.8 / PHP 5.5.9 / PHP 5.5.10 / PHP 5.5.11 / PHP 5.5.12 / PHP 5.5.13 / PHP 5.5.14 / PHP 5.5.15 / PHP 5.5.16 / PHP 5.5.17 / PHP 5.5.18 / PHP 5.5.19 / PHP 5.5.20 / PHP 5.5.21 / PHP 5.5.22 / PHP 5.5.23 / PHP 5.5.24 / PHP 5.5.25 / PHP 5.5.26 / PHP 5.5.27 / PHP 5.5.28 / PHP 5.5.29 / PHP 5.5.30 / PHP 5.5.31 / PHP 5.5.32 / PHP 5.5.33 / PHP 5.5.34 / PHP 5.5.35 / PHP 5.5.36 / PHP 5.5.37 / PHP 5.5.38

まず結論:どこへ移すか

移行先は 8.4.27 を第一候補にし、7.4 を中継点にした 2 段階で進めます。5.5 起点は 5.x の中では手前の版で片付いていることが多い一方、7.0 の 32 項目と 8.0 の 26 項目はそのまま残っています。加えて、7.0〜7.3 には数値文字列の解釈、ソート順、正規表現エンジンといった「エラーにならずに結果が変わる」変更が続くので、段階を分けて差分を見比べやすくしておく意味が大きい起点です。

  • 段階 1:5.5.38 → 7.4.33。5.6〜7.4 の 6 版で 88 項目(削除 26・非推奨 17・挙動変更 45)。ext/mysql と ext/ereg の削除、5.6 の文字コードと SSL の既定値、7.0 の構文とエラー処理をここで扱います。
  • 段階 2:7.4 → 8.4.27。8.0〜8.4 の 5 版で 84 項目(削除 18・非推奨 26・挙動変更 40)。8.0 の比較と型検査、8.1 以降の Deprecated、8.4 の round() などを扱います。

7.4 はサポートが終わった版で、今回の PHP2Shell も公式には 7.4 向けにソースのバックポートが出ているだけです。段階 1 の 7.4 は、開発環境と検証環境で使う中継点と考えてください。

移行先項目数サポート期限向いている場合
8.4.27推奨172(44/43/85)2028-12-31第一候補。サポート期限と、フレームワーク・ライブラリの対応状況の釣り合いが取れている版
8.5.12194(45/55/94)2029-12-31依存パッケージが 8.5 対応を明記していて、サポート期限を 1 年延ばしたい場合。8.5 だけで 22 項目あり、PDO の FETCH_* 定数の値の変更なども加わる
8.3.36156(42/36/78)2027-12-318.4 で増える非推奨や round() の変更を今回は避けたい場合の妥協点。1 年余りで次の移行が要る
8.2.35選ばない142(41/32/69)2026-12-312 か月あまりでセキュリティサポートが終わる

項目数は「合計(削除/非推奨/挙動変更)」。※2026年10月時点の公開情報に基づきます。

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

5.5 起点で特に気をつけたいのは、数値や文字列の扱いが版ごとに少しずつ変わり、エラーを出さずに計算結果や並び順が変わる種類の変更です。止まる変更より見つけにくいので、テストで比べる対象を決めるときの手がかりにしてください。

  1. 7.0非推奨を消していた
    mysql_* が消える

    5.5 では ext/mysql に接続するたびに E_DEPRECATED が出ます。error_reporting から E_DEPRECATED を外して黙らせていたなら、ログを見ても件数は分かりません。7.0 では未定義関数の Fatal になります。

    → 一覧の該当項目へ
  2. 5.6文字コードの既定が
    UTF-8 に寄る

    default_charset の既定が UTF-8 になり、レスポンスに charset=UTF-8 が付きます。5.6 からは htmlspecialchars() の既定もこの値に従うので、Shift_JIS のサイトは php.ini の default_charset を明示すれば両方が旧来の結果に揃います。

    → 一覧の該当項目へ
  3. 7.0"0x1A" が
    数値でなくなる

    16 進表記の文字列は、7.0 から数値として扱われません。is_numeric("0x1A") は false、算術では 0 です。外部ファイルや機器から 16 進の文字列を受け取って計算している箇所は、エラーを出さずに 0 で計算を続けます。

    → 一覧の該当項目へ
  4. 7.0list() の代入順が
    逆になる

    list() は左から順に代入するようになりました。list($a[], $a[]) = [1, 2] の結果が逆順になるほか、list() で文字列を分解する書き方は使えなくなります。どちらもエラーにはなりにくい変更です。

    → 一覧の該当項目へ
  5. 7.0同じ値の並び順が
    2 回変わる

    7.0 でソートの実装が変わり、同じ値を持つ要素の並び順が以前と変わることがあります。さらに 8.0 でソートが安定化して、もう一度変わります([8.0] の項目)。一覧画面の並びや CSV の行順を比べるテストでは、この 2 回を分けて確かめます。

    → 一覧の該当項目へ
  6. 7.1(int)"1e3" が
    1 から 1000 に

    数値文字列を整数に変えるとき、指数表記を解釈するようになりました。商品コードや伝票番号のように、数字と e が混ざる文字列を (int) や intval() に通している箇所は、値が黙って変わります。

    → 一覧の該当項目へ
  7. 7.3正規表現が
    マッチしなくなる

    正規表現エンジンが PCRE2 に変わり、文字クラスの中のエスケープしていない - のような曖昧なパターンが、警告とマッチ失敗になります。preg_match() の戻り値を真偽値でしか見ていないと、「一致しなかった」と区別できません。

    → 一覧の該当項目へ
  8. 8.0文字列と数値の
    == の判定が変わる

    数値と数値でない文字列の == は、8.0 から文字列どうしの比較です。0 == "" が false になるなど、空文字を 0 と同じに扱っていた入力チェックで分岐の向きが変わります。

    → 一覧の該当項目へ
  9. 8.0. と + の
    優先順位が変わる

    連結演算子 . の優先順位が + や - より低くなりました。"合計: " . $a + $b は、8.0 から "合計: " . ($a + $b) の意味になります。7.4 では E_DEPRECATED が出るので、段階 1 のログで拾えます。

    → 一覧の該当項目へ
  10. 8.1PDO の数値が
    文字列でなくなる

    PDO MySQL でエミュレートしたプリペアドを使っている場合も、整数や小数の列が int / float で返るようになりました。=== '1' の比較や、json_encode() の出力("1" が 1 に)が変わります。

    → 一覧の該当項目へ
  11. 8.4round() の結果が
    境界で変わる

    round() の内部実装が書き換えられ、一部の境界値で結果が以前と変わります。請求額や税額のように丸めの結果が金額になる計算は、段階 2 で旧来の出力と突き合わせて確かめます。

    → 一覧の該当項目へ

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

5.5 系を起点に、5.6 から 8.5 までの各版で入った変更を、版ごとに古い順で並べています。一覧は累積で、8.4.27 へ移すなら 5.6 から 8.4 までのブロックすべてが対象です。区分は 削除(Fatal・Parse error・Error 例外になる、または機能がなくなる)、非推奨(E_DEPRECATED が出るが動く)、挙動変更(エラーなしで結果やエラーの種類が変わる)の 3 つです。「要確認」の印が付いた項目は、一次情報の本文で裏取りしてから判断してください。

移行先を選ぶと、その版より新しいブロックが折りたたまれ、合計の項目数が切り替わります。段階 1 では 5.6〜7.4 の 6 ブロックを見れば足ります。

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

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

移行先 8.4.27 まで

[5.6] PHP 5.6 で入った変更 10項目:削除 0・非推奨 3・挙動変更 7

  1. 挙動変更

    default_charset の既定値が空から UTF-8 に。HTTP レスポンスの Content-Type に charset=UTF-8 が自動付与され、htmlspecialchars() 等の既定文字コードもこれに従う

    必要な修正Shift_JIS / EUC-JP のサイトは php.ini で default_charset を明示するか、header('Content-Type: text/html; charset=Shift_JIS') を出力前に実行する

  2. 挙動変更

    https:// や ssl:// ストリーム(file_get_contents、fopen、fsockopen、SoapClient、ストリーム版 HTTP クライアント)でピア証明書の検証が既定で有効(verify_peer / verify_peer_name = true)

    必要な修正CA 証明書を正しく配置する(openssl.cafile / openssl.capath)。自己署名証明書やホスト名不一致の外部 API 連携は失敗する。ストリームコンテキストで verify_peer=false にするのは最終手段

  3. 挙動変更

    cURL:CURLOPT_SAFE_UPLOAD の既定が true になり、CURLOPT_POSTFIELDS の '@/path/file' 形式のファイルアップロードが無効

    必要な修正new CURLFile('/path/file') に置換

  4. 挙動変更

    json_decode() が TRUE / False / NULL など小文字でないリテラルを拒否して null を返す

    必要な修正入力 JSON を正規化する(外部システムが大文字リテラルを送っていないか確認)

  5. 挙動変更

    GMP の値がリソースからオブジェクト(GMP クラス)に。is_resource() 判定が false になる

    必要な修正$x instanceof GMP で判定

  6. 挙動変更

    mcrypt 系関数が不正な長さの鍵・IV を拒否する(短い鍵の自動ゼロ埋めが廃止)

    必要な修正鍵長をアルゴリズムに合わせる。既存暗号文を復号できなくなる場合は、旧環境で復号して再暗号化する移行手順が必要

  7. 挙動変更

    クラスプロパティのデフォルト配列で整数キーと同値の数値文字列キー(1 と '1')が混在する場合、後の要素が前を上書きするよう修正

    必要な修正通常は影響なし

  8. 非推奨

    互換性のないコンテキストからの非静的メソッド呼び出し(別クラスのインスタンスメソッド内から Foo::bar() を呼び $this が引き継がれる挙動)を非推奨([7.0] で $this が未定義、[8.0] で Error)

    必要な修正静的メソッドにするか、インスタンスを生成して呼び出す

  9. 非推奨

    $HTTP_RAW_POST_DATA と always_populate_raw_post_data INI を非推奨([7.0] で削除)

    必要な修正file_get_contents('php://input') に置換(5.6 以降は複数回読める)

  10. 非推奨

    iconv.input_encoding / iconv.output_encoding / iconv.internal_encoding、mbstring.http_input / mbstring.http_output / mbstring.internal_encoding INI を非推奨

    必要な修正default_charset と mb_internal_encoding() / mb_http_output() に統一する

[7.0] PHP 7.0 で入った変更 32項目:削除 15・非推奨 3・挙動変更 14

  1. 削除

    ext/mysql(mysql_* 関数すべて)を削除。呼び出すと「Call to undefined function mysql_connect()」の Fatal

    必要な修正mysqli か PDO_MySQL へ。SQL 文字列連結+mysql_real_escape_string() の箇所はプリペアドステートメントに置き換える。暫定措置として mysql_* を mysqli でラップする互換シムもあるが、mysql_result() / mysql_db_query() / 接続省略呼び出しは手直しが要る

  2. 削除

    ext/ereg(ereg / eregi / ereg_replace / eregi_replace / split / spliti / sql_regcase)を削除

    必要な修正preg_match() / preg_replace() / preg_split() に置換。パターンにデリミタ(/.../)を付け、特殊文字のエスケープを見直す。大文字小文字無視は i 修飾子

  3. 削除

    ext/mssql(mssql_* 関数)と ext/sybase_ct を削除

    必要な修正Microsoft 提供の sqlsrv / PDO_SQLSRV、または PDO_DBLIB に移行

  4. 削除

    preg_replace() の /e 修飾子を削除(使うと警告+null)

    必要な修正preg_replace_callback() に書き換える

  5. 削除

    SAPI を削除:apache(1.x)、apache_hooks、apache2filter、aolserver、caudium、continuity、isapi、milter、nsapi、phttpd、pi3web、roxen、thttpd、tux、webjames

    必要な修正Apache 2.4 の apache2handler(mod_php)か PHP-FPM(推奨)に移行。IIS は FastCGI 経由

  6. 削除

    ASP タグ <% %> / <%= %> と <script language="php"> を削除(asp_tags INI も削除)

    必要な修正<?php ?> / <?= ?> に置換。放置すると PHP コードがそのままブラウザに出力される(情報漏えい)

  7. 削除

    $a =& new Foo(new の参照代入)が Parse error

    必要な修正$a = new Foo に

  8. 挙動変更

    変数構文の統一(Uniform Variable Syntax):間接変数・プロパティ・メソッド参照を常に左から右に評価する。$$foo['bar']['baz'] は ${$foo['bar']['baz']} の意味から ($$foo)['bar']['baz'] の意味に変わる。$foo->$bar['baz'] は ($foo->$bar)['baz']、Foo::$bar['baz']() は (Foo::$bar)['baz']() に

    必要な修正旧来の意味を保つには波括弧で明示する:${$foo['bar']['baz']}、$foo->{$bar['baz']}、Foo::{$bar['baz']}()。エラーにならず別の変数を参照するため、$$ と -> $ を grep して全件確認する

  9. 挙動変更

    list() が左から順に代入する(以前は右から)。list($a[], $a[]) = [1, 2] の結果が [2, 1] から [1, 2] に。空の list() は Fatal。list() で文字列を分解できない

    必要な修正代入順に依存するコードを修正、文字列は str_split() を使う

  10. 挙動変更

    foreach が配列の内部ポインタを動かさなくなった。値渡しの foreach は配列のコピーを反復するため、ループ内で元の配列を変更しても反復に反映されない。参照渡しは変更を追跡する

    必要な修正ループ内で current() / key() / next() / each() に依存するコードを書き直す。ループ中に要素を追加して「最後まで回す」コードは while ループに

  11. 挙動変更

    エンジン例外:多くの Fatal Error が Error 例外(Error / TypeError / ParseError / ArithmeticError / DivisionByZeroError)に。set_exception_handler() に Exception 以外(Error)が渡る。内部クラスのコンストラクタ失敗は常に例外を投げる

    必要な修正例外ハンドラの型宣言 function h(Exception $e) を Throwable に。catch (Exception $e) では Error を捕捉できないので、必要なら catch (Throwable $e) を追加

  12. 挙動変更

    E_STRICT の警告を E_DEPRECATED / E_NOTICE / E_WARNING に再分類(非静的メソッドの静的呼び出しは E_DEPRECATED、継承時のシグネチャ不一致は E_WARNING、参照代入は E_NOTICE など)

    必要な修正ログの種別が変わる。error_reporting で E_STRICT を除外して抑えていた警告が表示されるようになる

  13. 削除

    不正な 8 進リテラル(0128)は Parse error。負のビットシフトは ArithmeticError。範囲外のシフトは 0 または -1。% のゼロ除算は DivisionByZeroError(/ は警告+INF/NAN、[8.0] で DivisionByZeroError)

    必要な修正除数の事前チェック、intdiv() の利用

  14. 挙動変更

    16 進文字列("0x1A")を数値として扱わない。is_numeric("0x1A") は false、算術では 0

    必要な修正hexdec() か filter_var($s, FILTER_VALIDATE_INT, FILTER_FLAG_ALLOW_HEX) を使う

  15. 削除

    "\u{" で始まる不正な Unicode エスケープが Parse error("\u{xyz}" など)

    必要な修正リテラルとして出力したい場合は "\\u{" とエスケープ

  16. 削除

    関数を削除:call_user_method() / call_user_method_array()、mcrypt_generic_end()、mcrypt_ecb() / mcrypt_cbc() / mcrypt_cfb() / mcrypt_ofb()、datefmt_set_timezone_id() / IntlDateFormatter::setTimeZoneID()、set_magic_quotes_runtime() / magic_quotes_runtime()、set_socket_blocking()、PHP-FPM 上の dl()、GD の Type1 フォント関数(imagepsbbox / imagepsloadfont / imagepstext 等)

    必要な修正call_user_func() / call_user_func_array()、mcrypt_generic_deinit()、setTimeZone()、削除、stream_set_blocking()、TrueType(imagettftext)に置換

  17. 削除

    $HTTP_RAW_POST_DATA を削除(always_populate_raw_post_data INI も削除)

    必要な修正file_get_contents('php://input') に置換

  18. 削除

    INI ファイルの # コメントを削除(; のみ)。xsl.security_prefs INI を削除

    必要な修正php.ini / .user.ini の # を ; に置換。XSLT のセキュリティ設定は XSLTProcessor::setSecurityPrefs() で

  19. 削除

    int / float / bool / string / null / true / false(および resource / object / mixed / numeric)をクラス・インターフェース・トレイト名に使用不可

    必要な修正リネーム

  20. 削除

    同名の仮引数(function f($a, $a))は Fatal。switch の複数 default は Fatal。ループ外の break / continue はコンパイルエラー。global に複雑な式(global $$foo->bar)を指定できない

    必要な修正修正。global ${$foo->bar} のように波括弧で明示

  21. 挙動変更

    func_get_args() / func_get_arg() が引数の「現在値」を返す(関数内で引数変数を書き換えた後はその値)

    必要な修正元の値が必要なら関数冒頭で退避する

  22. 挙動変更

    JSON 拡張が jsond 実装に置き換え。"34." や空文字列など不正な JSON を拒否し、json_decode('') は null。大きな整数・浮動小数点の扱いが厳密に

    必要な修正入力を検証、json_last_error() を確認

  23. 挙動変更

    setlocale() のカテゴリに文字列("LC_ALL")を渡せない

    必要な修正定数 LC_ALL を使う

  24. 削除

    mktime() / gmmktime() の第 7 引数 is_dst を削除(渡すと警告)

    必要な修正引数を削除。DST は date.timezone から自動判定

  25. 挙動変更

    date.timezone 未設定時の警告が廃止(内部的に UTC を使う)

    必要な修正警告が出なくなる代わりに日時が UTC で計算される。php.ini で date.timezone = Asia/Tokyo を明示する

  26. 挙動変更

    同値要素のソート順が変わることがある(sort 系は不安定ソート。[8.0] で安定化)

    必要な修正同値要素の順序に依存するコードは usort の比較関数で副キーを使う

  27. 挙動変更

    yield が右結合に(yield $a or die の優先順位変更)

    必要な修正括弧で明示

  28. 挙動変更要確認

    ユーザー定義セッションハンドラ(session_set_save_handler)の各コールバックの戻り値検査が厳格化(read() は文字列、他は bool を返す必要があり、false を返すと致命的エラー)

    必要な修正ハンドラの戻り値を bool に統一する

  29. 非推奨

    PHP 4 形式のコンストラクタ(クラス名と同名のメソッド)を非推奨([8.0] でコンストラクタとして扱われなくなる)

    必要な修正__construct() にリネーム。旧名を残す必要があれば __construct() から呼ぶ

  30. 非推奨

    非静的メソッドの静的呼び出し Foo::bar() を非推奨([8.0] で Error)

    必要な修正インスタンスを生成して呼ぶか、メソッドを static にする

  31. 非推奨

    password_hash() の salt オプション、ldap_sort()、SSL コンテキストの capture_session_meta オプションを非推奨

    必要な修正salt は自動生成に任せる、ldap_sort は PHP 側で usort、crypto 情報は stream_get_meta_data() の crypto キー(7.2 以降)

  32. 挙動変更

    新規組み込み関数の追加(random_bytes / random_int / intdiv / error_clear_last / preg_replace_callback_array 等)。同名の自作関数(random_int のポリフィルが典型)があると「Cannot redeclare」で Fatal

    必要な修正自作関数は if (!function_exists('random_int')) で囲む

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

  1. 削除

    ユーザー定義関数に必要な引数より少ない数で呼ぶと ArgumentCountError(以前は Warning で null 扱い)

    必要な修正呼び出し側の引数を揃えるか、関数定義にデフォルト値を付ける

  2. 削除

    文字列への空インデックス代入 $str[] = 'x' が Fatal。空文字列へのオフセット代入($s = ''; $s[3] = 'x';)は配列化せず文字列として処理(空白埋め)

    必要な修正配列として使う変数は $a = []; で初期化する($a = ''; で初期化して $a[] = ... する古いコードが典型)

  3. 挙動変更

    算術演算で非数値文字列に E_WARNING「A non-numeric value encountered」、先頭のみ数値の文字列("5 apples")に E_NOTICE「A non well formed numeric value encountered」([8.0] でそれぞれ TypeError / E_WARNING に昇格)

    必要な修正入力値を (int) / (float) で明示変換し、数値以外は事前に弾く

  4. 挙動変更

    数値文字列の変換が指数表記を解釈する((int)"1e3" と intval("1e3") が 1 から 1000 に)

    必要な修正期待値が変わる箇所を確認

  5. 削除

    $this の再代入・仮引数名への使用・static / global 宣言・unset()・extract() / parse_str() 経由の上書きを禁止(Fatal)

    必要な修正変数名を変更する

  6. 削除

    compact() / extract() / func_get_args() / func_get_arg() / func_num_args() / get_defined_vars() / parse_str()(1 引数)/ mb_parse_str()(1 引数)/ assert()(文字列)を可変関数名や call_user_func() で動的に呼べない(Warning+null)

    必要な修正直接呼び出しに書き換える

  7. 削除

    void / iterable をクラス名に使用不可。識別子に ASCII DEL(0x7F)を使用不可

    必要な修正リネーム

  8. 挙動変更

    rand() / srand() が mt_rand() / mt_srand() のエイリアスに。mt_rand() の剰余バイアスを修正したため、同じシードでも 7.0 以前と乱数列が異なる(MT_RAND_PHP で旧実装を選べるが [8.3] で非推奨)。shuffle() / str_shuffle() / array_rand() も MT を使う

    必要な修正シード固定の再現性に依存するテストやトークン生成を見直す。セキュリティ用途は random_int() / random_bytes()

  9. 挙動変更

    serialize_precision の既定が -1(最短往復表現)に。json_encode(0.1 + 0.2) が 0.3 ではなく 0.30000000000000004 を出力、var_export() / serialize() も同様

    必要な修正表示用の値は出力前に round() する。API レスポンスの数値表現が変わる点を周知

  10. 挙動変更

    DateTime / DateTimeImmutable のコンストラクタがマイクロ秒を保持する。new DateTime() == new DateTime() が false に、diff() の結果に f(マイクロ秒)が入る

    必要な修正秒精度で比較するなら format('U') や setTime() で切り捨ててから比較

  11. 削除

    INI を削除:session.entropy_file、session.entropy_length、session.hash_function、session.hash_bits_per_character(→ session.sid_length / session.sid_bits_per_character。ただし [8.4] で変更が非推奨)

    必要な修正php.ini から削除。セッション ID の形式が変わるので、ID の長さや文字種を検証している自作コードに注意

  12. 挙動変更

    E_RECOVERABLE_ERROR のメッセージ接頭辞が「Catchable fatal error」から「Recoverable fatal error」に。unserialize() の $options が配列でないと警告。参照代入で自動生成される配列要素の順序が変化

    必要な修正ログの文字列マッチや順序依存コードを確認

  13. 非推奨

    ext/mcrypt を非推奨([7.2] で削除)。mb_ereg_replace() / mb_eregi_replace() の e オプションを非推奨([8.0] で削除)

    必要な修正openssl_encrypt() / openssl_decrypt() か sodium に移行、mb_ereg_replace_callback() に置換

  14. 挙動変更

    新規組み込み関数の追加(is_iterable / sapi_windows_cp_* 等)。同名の自作関数があると Fatal

    必要な修正function_exists() ガード

[7.2] PHP 7.2 で入った変更 9項目:削除 2・非推奨 3・挙動変更 4

  1. 削除

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

    必要な修正openssl_encrypt() / openssl_decrypt() か sodium へ。MCRYPT_RIJNDAEL_128 は AES-128-CBC 相当だが、パディング(mcrypt はゼロ埋め、openssl は PKCS#7)が異なるため既存暗号文の復号には OPENSSL_ZERO_PADDING と手動パディング除去が必要

  2. 非推奨

    create_function()、each()、__autoload() を非推奨([8.0] で削除)

    必要な修正無名関数 function() {}、foreach(または key() / current() / next())、spl_autoload_register() に置換

  3. 非推奨

    未定義定数の裸参照($arr[key]、echo CONST_NAME)が E_NOTICE から E_WARNING に([8.0] で Error)

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

  4. 非推奨

    parse_str() の第 2 引数省略、assert() の文字列引数、track_errors / $php_errormsg、(unset) キャスト、mbstring.func_overload、gmp_random()、read_exif_data()、png2wbmp() / jpeg2wbmp()、INTL_IDNA_VARIANT_2003、set_error_handler コールバックの第 5 引数 $errcontext を非推奨(すべて [8.0] で削除)

    必要な修正parse_str($s, $out)、assert(式)、error_get_last()、= null か unset()、mb_* を明示呼び出し、gmp_random_bits() / gmp_random_range()、exif_read_data()、imagewbmp()、INTL_IDNA_VARIANT_UTS46、第 5 引数を使わない

  5. 挙動変更

    count() / sizeof() に Countable でない値(null、スカラー、Countable 未実装のオブジェクト)を渡すと E_WARNING([8.0] で TypeError)

    必要な修正is_countable() で判定するか count($x ?? [])。null で 0 が返る前提のコードを修正

  6. 挙動変更

    get_class(null) は警告+false([8.0] で TypeError)。number_format() が -0 を返さない。オブジェクト⇔配列のキャストで数値文字列キーが整数キーに正規化((array)$obj の '1' キーが 1 に、(object)['1' => ...] に $o->{'1'} でアクセス可能に)

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

  7. 削除

    object が予約語に(クラス名に使用不可)。sql.safe_mode INI を削除。hash_init() 等がリソースではなく HashContext オブジェクトを返す

    必要な修正リネーム、is_resource() 判定を修正

  8. 挙動変更

    STREAM_CRYPTO_METHOD_TLS_CLIENT / _SERVER 定数が TLS 1.0〜1.2 すべてを意味するように(以前は TLS 1.0 のみ)

    必要な修正通常は改善方向。特定バージョンに固定したい場合は STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT 等を明示

  9. 挙動変更

    新規組み込み関数の追加(mb_ord / mb_chr / mb_scrub / spl_object_id / stream_isatty / sodium_* 等)。同名の自作関数(mb_ord / mb_chr のポリフィルが典型)があると Fatal

    必要な修正function_exists() ガード

[7.3] PHP 7.3 で入った変更 10項目:削除 1・非推奨 1・挙動変更 8

  1. 挙動変更

    ヒアドキュメント / Nowdoc の柔軟な構文:終端ラベルのインデントを許可した副作用で、本文の行頭(インデント込み)に終端ラベルと同じ文字列があると、そこで終端と解釈され Parse error や内容の欠落が起きる

    必要な修正本文に現れない終端ラベル名(EOT_HTML_MAIL など)に変更する。テンプレートを大量に埋め込んだ古いコードは grep で確認

  2. 挙動変更

    switch を対象にした continue に E_WARNING(continue は break と同じ動作)

    必要な修正意図どおり break か continue 2 に書き換える

  3. 挙動変更

    ArrayAccess 実装に対する $obj["123"] のような整数文字列オフセットが int に変換されず文字列のまま offsetGet() に渡される

    必要な修正offsetGet() / offsetSet() 側で型を吸収

  4. 削除

    文字列キーを持つ Traversable(ジェネレータ等)の引数展開 f(...$gen) は Error

    必要な修正整数キーに変換するか iterator_to_array($gen, false)

  5. 挙動変更

    mb_strtoupper() / mb_strtolower() / mb_convert_case() が Unicode のフルケースマッピングに(ß → SS など、文字数が変わり得る)。mb_check_encoding() が厳格化

    必要な修正文字列長に依存するコード、全角半角変換の期待値を確認

  6. 挙動変更

    正規表現エンジンを PCRE から PCRE2 に移行。文字クラス内の未エスケープの - や不正な後方参照など、曖昧なパターンが警告やマッチ失敗になる。preg_quote() が # もエスケープする

    必要な修正preg_last_error() とログで洗い出し、パターンを修正

  7. 挙動変更

    compact() に存在しない変数名を渡すと E_NOTICE。BCMath 関数が指数表記("1e3")を拒否して警告

    必要な修正変数名を確認、数値を通常表記に

  8. 挙動変更

    Cookie 名が URL デコードされなくなった(7.3.23 / 7.4.11 のセキュリティ修正、bug #79699)。%20 などを含む Cookie 名が別名として扱われる

    必要な修正Cookie 名を英数字・アンダースコアに限定する

  9. 非推奨

    大文字小文字を区別しない定数(define() の第 3 引数 true)、FILTER_FLAG_SCHEME_REQUIRED / FILTER_FLAG_HOST_REQUIRED、fgetss() / gzgetss() / SplFileObject::fgetss() / string.strip_tags フィルタ、image2wbmp()、mbstring の未文書化エイリアス(mbereg() / mbsplit() 等)、文字列検索関数(strpos 等)への非文字列 needle、Normalizer::NONE を非推奨(すべて [8.0] で削除)

    必要な修正定数名の大文字小文字を統一、フラグを削除(常に必須)、fgets()+strip_tags()、imagewbmp()、mb_ereg() / mb_split()、needle は chr() で文字列化

  10. 挙動変更

    新規組み込み関数の追加(is_countable / array_key_first / array_key_last / hrtime / net_get_interfaces 等)。同名の自作関数があると Fatal

    必要な修正function_exists() ガード

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

  1. 削除

    ext/interbase、ext/recode、ext/wddx を本体から削除(PECL へ)

    必要な修正PDO_Firebird、iconv() / mb_convert_encoding()、json_encode() / serialize() に移行

  2. 非推奨

    括弧なしのネストした三項演算子 $a ? $b : $c ? $d : $e を非推奨([8.0] で Fatal)

    必要な修正括弧で結合順を明示する。PHP の三項は左結合なので旧来の意味は ($a ? $b : $c) ? $d : $e

  3. 非推奨

    波括弧による文字列・配列オフセットアクセス $str{0} / $arr{'k'} を非推奨([8.0] で Parse error)

    必要な修正$str[0] / $arr['k'] に置換

  4. 非推奨

    (real) キャストと is_real() を非推奨([8.0] で削除)

    必要な修正(float) と is_float()

  5. 非推奨

    array_key_exists() にオブジェクトを渡すことを非推奨([8.0] で TypeError)

    必要な修正isset($obj->prop) か property_exists()

  6. 非推奨

    implode() の引数逆順 implode($pieces, $glue) を非推奨([8.0] で TypeError)

    必要な修正implode($glue, $pieces) に統一(join() も同様)

  7. 非推奨

    get_magic_quotes_gpc() / get_magic_quotes_runtime()、money_format()、hebrevc()、convert_cyr_string()、ezmlm_hash()、restore_include_path()、FILTER_SANITIZE_MAGIC_QUOTES、allow_url_include INI、mb_strrpos() の第 3 引数へのエンコーディング指定、親クラスのないクラス内での parent::、ldap_control_paged_result()、Reflection*::export()、クロージャからの $this のアンバインド、base_convert() / bindec() / octdec() / hexdec() への不正文字 を非推奨(多くは [8.0] で削除)

    必要な修正呼び出しを削除(常に false)、NumberFormatter::formatCurrency()、hebrev()、iconv()、FILTER_SANITIZE_ADD_SLASHES、オフセットを明示して第 4 引数にエンコーディング、parent:: を削除、ldap_search() の controls 引数、(string)$reflection、入力文字の検証

  8. 挙動変更

    null / bool / int / float / リソースに配列アクセス($null['key'])すると E_NOTICE「Trying to access array offset on value of type null」([8.0] で E_WARNING)

    必要な修正isset() / ?? で防御する。DB の取得結果が false のときに $row['id'] する典型パターンに注意

  9. 挙動変更

    PASSWORD_DEFAULT が int 1 から null に、PASSWORD_BCRYPT が '2y' に(文字列化)。password_hash($p, 1) のような数値直書きは動くが === 比較は壊れる

    必要な修正定数を使う

  10. 挙動変更

    fread() / fwrite() が失敗時に false を返す(以前は '' / 0)。openssl_random_pseudo_bytes() は失敗時に例外

    必要な修正=== false で判定する

  11. 挙動変更

    DateInterval 同士の比較(== / <)は警告を出して常に false。get_object_vars(ArrayObject) が内部配列ではなく ArrayObject 自身のプロパティを返す

    必要な修正比較は days や秒に換算、getArrayCopy() を使う

  12. 削除

    fn が予約語に(function fn() や fn という名前のメソッド・クラスは Parse error)

    必要な修正リネーム

  13. 挙動変更

    新規組み込み関数の追加(mb_str_split / password_algos / get_mangled_object_vars 等)。同名の自作関数(mb_str_split のポリフィルが典型)があると Fatal

    必要な修正function_exists() ガード

[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() ガードか名前空間付き関数に

作業の進め方

5.5 起点には、移行の前に今のサーバーで拾える情報が一つ多くあります。ext/mysql と preg の /e は 5.5 で非推奨になっているので、今のサーバーで E_DEPRECATED をログに出すだけで、7.0 で消えるものの一部が分かります。ここではそこから始め、段階 1(7.4 まで)と段階 2(8.4.27 まで)の順に書きます。コマンドは移行対象のリポジトリの直下で動かす前提です。

1. 今のサーバーで Deprecated を出す

本番で設定を変えるのが難しければ、今のサーバーの複製や検証環境で構いません。error_reporting に E_DEPRECATED を含め、画面には出さずログにだけ出します。あわせて主要な画面・帳票・バッチの出力を保存し、移行後に比べる材料にします。

; 今の 5.5 の php.ini(検証環境)
error_reporting = E_ALL
display_errors = Off
log_errors = On
error_log = /var/log/php/php55-deprecated.log
# 数日動かしてから、非推奨の種類ごとに数える
grep 'PHP Deprecated' /var/log/php/php55-deprecated.log \
  | sed -E 's/^\[[^]]*\] //; s/ in \/.* on line [0-9]+$//' | sort | uniq -c | sort -rn
# 主要な画面の出力を保存する(URL は自分のサイトに置き換える)
mkdir -p baseline
curl -s 'https://example.jp/report.php?month=2026-09' -o baseline/report-2026-09.html

ログに出るのは、実際に通った箇所だけです。めったに動かない管理画面や月次のバッチの分は、次のコードの数え上げで補います。

2. コードで数える

G="grep -rnE --include=*.php --include=*.inc --exclude-dir=vendor"
$G '\bmysql_[a-z_]+\(' . | wc -l                                   # ext/mysql [7.0]
$G '\b(ereg|eregi|ereg_replace|eregi_replace|split|spliti)\(' . | wc -l   # ext/ereg [7.0]
$G 'mcrypt_' . | wc -l                                              # mcrypt [7.2]
$G '\blist\(' . | wc -l                                             # 代入順の変更 [7.0]
$G '\b(sort|usort|uasort|uksort|asort|arsort)\(' . | wc -l          # 並び順の変更 [7.0] [8.0]
$G '\b(intval|round)\(|\(int\)' . | wc -l                           # 数値変換と丸め [7.1] [8.4]

後ろの 3 行は直す箇所の数ではなく、「結果が変わっていないかをテストで確かめる候補」の数です。件数が多ければ、業務上の金額・数量・並び順に関わる箇所から優先して確かめます。

3. 段階 1:7.4 のコンテナで動かす

Docker の公式イメージ php:7.4-cli を使います。7.4 はサポートが終わっていて今回の脆弱性も直っていないので、手元と検証環境だけで使い、外部には公開しません。全ファイルの構文チェックで Parse error を出し切り、PHPCompatibility を 7.4 向けにかけます。

# docker/php74/Dockerfile(段階 1 用)
FROM php:7.4-cli
RUN docker-php-ext-install mysqli pdo_mysql
WORKDIR /app
docker build -t php-stage1 docker/php74
docker run --rm -v "$PWD":/app php-stage1 sh -c \
  'find . -name "*.php" -not -path "./vendor/*" -print0 | xargs -0 -n1 php -l 2>&1 | grep -v "^No syntax errors"'
vendor/bin/phpcs -ps . --standard=PHPCompatibility --runtime-set testVersion 7.4 --ignore=*/vendor/*

Rector は 7.4 までの規則に絞って当てます(設定は withPhpSets(php74: true))。設定ファイルが名前付き引数を使うので、Rector そのものは移行先の 8.4.27 のコンテナで動かします。PHPStan は phpVersion: 70400、level: 0 から始めます。ext/mysql の置き換えは mysqli でも PDO でも構いませんが、置き換えたあとで 8.0(PDO)と 8.1(mysqli)にそれぞれ既定のエラー処理が例外に変わる点は、置き換えの時点で織り込んでおきます。

4. ereg と split を preg に書き換える

ext/ereg からの書き換えでは、デリミタを付けることと、split() を見落とさないことの 2 点で漏れが出がちです。書き換えたパターンは 7.4 の PCRE2 で動かすので、[7.3] で厳しくなった曖昧なパターンもここで一緒に直ります。

<?php
// 7.0 で削除される書き方
if (ereg('^[0-9]{3}-[0-9]{4}$', $zip)) { /* ... */ }
if (eregi('^[a-z0-9._-]+@', $mail)) { /* ... */ }
$parts = split('[/.-]', $date);

// preg に書き換えた形(大文字小文字を無視するのは i 修飾子)
if (preg_match('/^[0-9]{3}-[0-9]{4}$/', $zip)) { /* ... */ }
if (preg_match('/^[a-z0-9._-]+@/i', $mail)) { /* ... */ }
$parts = preg_split('#[/.-]#', $date);

// マッチしなかったのか、パターンが壊れているのかを区別する
if (preg_match($pattern, $s) === false) {
    error_log('preg error: ' . preg_last_error());
}

5. 数値・並び順・文字コードの結果を比べる

5.5 起点で時間をかけたいのは、エラーにならない変更の確認です。1. で保存した出力と、7.4 のコンテナで同じ入力から作った出力を比べます。帳票や CSV は、並び順の違いと値の違いを分けて見られるように、ソートしたものとしないものの両方で比べると原因を絞りやすくなります。

docker run --rm -p 8074:8000 -v "$PWD":/app -w /app php-stage1 \
  php -d error_reporting=E_ALL -d display_errors=0 -d log_errors=1 \
      -d error_log=/app/logs/php-stage1.log -S 0.0.0.0:8000 -t public
mkdir -p stage1
curl -s 'http://localhost:8074/report.php?month=2026-09' -o stage1/report-2026-09.html
diff baseline/report-2026-09.html stage1/report-2026-09.html
diff <(sort baseline/report-2026-09.html) <(sort stage1/report-2026-09.html)   # 並び順だけの差か

Shift_JIS や EUC-JP のサイトは、[5.6] の default_charset の変更で、何も指定していないページに charset=UTF-8 が付きます。当面は今の文字コードで動かすなら、php.ini に default_charset = "Shift_JIS" のように明示します。5.6 以降は htmlspecialchars() の既定もこの値に従うので、両方が旧来の結果に揃います。[5.6] で非推奨になった mbstring.internal_encoding は、mb_internal_encoding() の呼び出しか default_charset に置き換えます。

6. Deprecated と警告を 0 件にする

grep -E 'PHP (Deprecated|Warning|Notice)' logs/php-stage1.log \
  | sed -E 's/^\[[^]]*\] //' | sort | uniq -c | sort -rn | head -40

7.4 のログに出る Deprecated は、ほとんどが 8.0 で Error になるものです(each()、create_function()、$str{0}、. と + を括弧なしで混ぜた式など)。段階 1 の終わりにここを 0 件にしておくと、段階 2 は 8.0 以降の変更だけに集中できます。

7. 段階 2:8.4.27 へ、そして切り替え

段階 1 の差分をマージしてから、PHPCompatibility の testVersion を 8.4、Rector を withPhpSets(php84: true)、PHPStan の phpVersion を 80400 に替えて、同じ順で回します。5. の出力比較も、8.4.27 のコンテナでもう一度行います。8.0 のソート安定化と 8.4 の round() で、段階 1 で一致していた出力が再び変わることがあるためです。

下の「コーディングエージェントに一括で任せる」の手順とプロンプトは段階 2 の形で書いてあります。段階 1 にも使う場合は、プロンプト中の 8.4.27 を 7.4.33 に、8.4 を 7.4 に読み替えて渡してください。本番の切り替えは、検証環境の 8.4.27 で出力の一致とログの 0 件を確かめてからにします。5.5 から同梱されている OPcache は、8.5 で常に PHP 本体に組み込まれてロードされる形に変わるので、将来 8.5 へ進むときは php.ini の zend_extension=opcache の行も見直してください。

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

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

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

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

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

  • 作業ブランチ。git switch -c php-upgrade-8.4 のように移行専用のブランチを切り、エージェントには main を直接触らせない。
  • 今の動きを記録したテスト。テストが無いなら、移行前(5.5.38 のサーバーやステージング)で主要な画面・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/5.5.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 5.5-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 移行のルール(5.5.38 → 8.4.27)

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

参照資料: https://shinichi.noguchi.jp.net/feature/phpupgrade/5.5.html(保存版: docs/php-upgrade/reference.html)
参照資料の「起点から移行先までに入った変更の全一覧」が、5.5 系から 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 5.5.38 から 8.4.27 への移行作業の差分です。git diff main...HEAD を読み、レビューしてください。参照資料: https://shinichi.noguchi.jp.net/feature/phpupgrade/5.5.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 の変更一覧(5.5 系から 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 5.5.38 から 8.4.27 への移行差分をレビューする。移行作業のコミット後や PR 作成前に使う。
tools: Read, Grep, Glob, Bash
---

あなたは PHP の版移行のレビュー担当です。git diff main...HEAD を読み、docs/php-upgrade/reference.html の変更一覧(5.5 系から 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 を 5.5.38(5.5 系)から 8.4.27 へ移行している。
参照資料: https://shinichi.noguchi.jp.net/feature/phpupgrade/5.5.html(保存版: docs/php-upgrade/reference.html)。5.5 系から 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 サーバーの設定変更、リリースの判断と切り戻しは人が行う。

公式の移行ガイド

5.5 系から 8.5 までに経由する各版の、php.net の移行ガイドと ChangeLog、php-src の UPGRADING です。上の一覧の各ブロックの見出し直下にも、同じ版のリンクを置いています。

※2026年10月時点の公開情報に基づきます。項目の内容は php.net の移行ガイドと php-src の UPGRADING をもとに整理したもので、「要確認」の項目は一次情報の本文で裏取りしてから判断してください。

🔖 サーバー側の対応は特集トップで解説しています。 PHP2Shell(CVE-2026-109618 / CVE-2026-109619)がどういう仕組みの脆弱性で、どの構成が影響を受けるのか、版ごとにサーバーで先に済ませる更新と、すぐに更新できない場合の緩和策をまとめました。5.5 系向けの修正は公式からは出ていない(公式のソースバックポートは 7.4 / 8.0 / 8.1 向けのみ)ので、移行が終わるまでの守り方もトップで確かめてください。起点ごとのページはトップからすべてたどれるので、ブックマークは特集トップを 1 つ保存しておけば足ります。

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

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

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

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

影響範囲の調査、出力を比べるテストの組み立て、段階移行の計画づくりまで、
今のコードと構成を見ながら 30分の無料相談で承ります。

無料相談を予約する →