7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

リリースを止めずにPHP7.0から8.4へ!Rectorで実現するバージョンアップ戦略

Last updated at Posted at 2025-05-12

なぜ「リリースを止めない」ことが重要なのか

リリースが止まると、ただ作業が遅れるだけではありません。新規開発が完全にストップすると、事業に影響し、ひいては売上にも関わってくる可能性があります。特にWeb系企業では、突発的なリリースが大きな売上に繋がるケースも珍しくありません。

弊社の場合、ほとんどがtoC向けシステムで構成されており、リリースを止められるのはせいぜい1〜2日が限界です。

そこで今回は、「とにかくリリースを止めないこと」に注力した、Rectorを活用したPHPバージョンアップ手法について解説します。

既存のバージョンアップ手法の問題点

Rectorについて解説する前に、そもそも今までのPHPバージョンアップ手法について確認しましょう。

運用と止めない前提だと「リリースブランチ」のようなとても大きいブランチにを作成して、そこにどんどん変更内容をコミットする手法が一般的です。

ただこの方法の問題点としては以下のようなものが考えられます。

  • リリースブランチが大きくなりすぎることで、影響範囲が不透明に、またレビューもコード量から事実上不可能に
  • 他の人がリリースした内容をマージする際に以下のような問題が発生する
    • 取り込む際にコンフリクトが発生する
    • リリースした内容をPHP8.4の内容にリファクタリングしてしまい、リリースが発生するごとに開発コストが増える

これらの問題をRectorで解決します。

Rectorとは

Rectorは、PHPコードを抽象構文木(AST)レベルで自動変換できるリファクタリングツールです。バージョンアップに伴う非推奨APIの置き換えやコードスタイルの一括更新などを、手作業ではなく設定したルールに沿ってCI上で自動化できます。

これにより、手動での変更漏れやブランチの長期化によるリリース遅延を防ぎ、運用を継続しながら迅速にPHPのバージョンアップを進めることが可能になります。

  • AST: ソースコードを構文的に解析した中間表現。人間が書いたコードを構造として扱いやすくするための形式

今回やったこと

  • PHP7.0からPHP8.4へのバージョンアップ
  • Laravel5.4からLaravel12へのバージョンアップ

具体的なバージョンアップフロー

PHP7.0環境とPHP8.4検証環境を同時並行で運用できるようにする ことが大前提です。
そのため、リリースブランチのような大きなブランチは作成しません(ブランチの寿命は短いほどよい)。

RectorとDockerを使い、CI上でPHP7.0環境からPHP8.4環境を毎回自動生成します。
イメージとしては、JavaScriptのトランスパイルのようにPHP8.4用のコードを毎回変換して動かす、という発想です。

PHP8.4環境の生成手順(ざっくり)

  1. Dockerfile内で、PHP7.0用のcomposer.jsonをPHP8.4用に書き換えた上でcomposer installを実行
  2. Rectorで、PHP7.0向けコードをPHP8.4向けに変換
  3. 検証環境へデプロイ!

コードについては、できるだけPHP7.0とPHP8.4の両方で同じ挙動になるように事前対応をこまめにリリースします。

それでもどうしても書き分けが必要な部分や、互換が保てない箇所については、Rectorで毎回自動変換させています。

この手順を踏めば、直前までPHP7.0で開発していたとしても、比較的スムーズにPHP8.4対応が可能です。

並行稼働時のCI環境

PHP7.0とPHP8.4それぞれに対して、コミットごとにPHPStanとPHPUnitを実行しています。
どちらか一方の環境が壊れることはありませんでした。

PHP8.4に移行したタイミングでの挙動変化や破損を防ぐためにも、開発した箇所にテストコードを書くのはマストです。

今回利用したルール

Rectorでの自動変換において利用したルールは下記の通りです。
極力公式ルールやOSSのルールで対応したかったのですが、所々対応できておらずカスタムルールとして自作しています。

先行してリリース可能なルールはは、どんどんリリースしてしまっていて、最後まで残ってCIで稼働していたのが下記のルールです。

【公式ルール】RemovePhpVersionIdCheckRector

-        if (PHP_VERSION_ID < 80000) {
-            openssl_free_key($private_key);
-        }

if (PHP_VERSION_ID < 80000) { ... } のような条件分岐を、指定バージョンに応じて削除してくれるルールです。

このルールでメソッド単位の挙動変更は殆ど対応可能です

例えば上記のように openssl_free_key のようにPHP8.4で非推奨になった関数を使っている場合、
PHP_VERSION_ID の分岐を入れておけば、PHP7.0では実行され、PHP8.4では該当コードが削除されます。

【公式ルール】CoversAnnotationWithValueToAttributeRector / DataProviderAnnotationToAttributeRector

 use PHPUnit\Framework\TestCase;
+use PHPUnit\Framework\Attributes\CoversClass;
+use PHPUnit\Framework\Attributes\CoversFunction;

-/**
- * @covers SomeClass
- */
+#[CoversClass(SomeClass::class)]
+#[CoversFunction('someFunction')]
 final class SomeTest extends TestCase
 {
-    /**
-     * @covers ::someFunction()
-     */
     public function test()
     {
     }
 }

PHPUnitでPHPDocをAttributeに変換するルールです。
PHPUnit11以降ではPHPDocでのメタデータ記述が非推奨になりました。
ただPHP7.0ではAttribute構文が使えないので、上記ルールを使って一括変換しています。

【カスタムルール】PHPUnitのsetUp() / tearDown() に戻り値として void をつける

  • 対象: PHPUnitのsetUp() / tearDown()メソッド
  • 背景: PHPUnitのバージョンによっては親クラスに: voidが付与されているため、PHP8.4では同様の戻り値が必要です。 PHP7.0では: voidが書けないため、RectorでPHP8.4時のみ自動で付与します

【カスタムルール】Exception → Throwableへの置換

-    public function report(Exception $exception)
+    public function report(\Throwable $e)
{
-        parent::report($exception);
+        parent::report($e);
}

  • 対象: App\Exceptions\Handler クラスのメソッド引数
  • 背景: Laravel 7以降、例外ハンドラの引数がExceptionからThrowableに変更されました。 Laravelの公式アップグレードガイドに従い、引数を一括で変換します

【カスタムルール】デフォルトnull引数の明示的nullable化

-    function __construct(int $hoge_id = null, string $fuga)
+    function __construct(?int $hoge_id, string $fuga)
  • 対象: デフォルト値がnullのメソッド引数
  • 背景: PHP8.4では、デフォルトでnullが設定されている引数は明示的にnullable型として定義する必要があります。
    既存の ExplicitNullableParamTypeRector では一部のケースで変換が不十分だったため(後続の引数にデフォルト引数が存在しない場合はデフォルト引数nullを消すロジックがない)、引数の順序も考慮した独自ロジックを追加しています

カスタムルールを書くためのコツ

正直なところ、情報はそこまで多くないです。
なので実際にルールを書く際は、デバッグ中心+他人のルールを読むのが基本スタイルになります。

  1. 他の人が同じルールを作っていないか確認する
    大体ほしいなと思ったルールは既に作られているものです。
    車輪の再開発をしても仕方がないので、既存のルールが存在しないか確認しましょう。
    find-rule
    公式になくても、rector-laravelのように有志の方が作っているルールも存在します

  2. ASTの構造を理解する
    Rector公式サイトでは、PHPコードをASTに変換して可視化できるツールが提供されています。
    自分のコードがどんなノード構成になっているか、まずはこれで確認してみましょう。
    ASTに変換して可視化できる公式ツール

  3. Node検索や置き換えの仕組みを知る
    Rectorは、親ノードの検索を直接はサポートしていません。
    その代わりにScoped Traverseという機構で、ノードの探索を行います。
    NodeFinder API や NodeVisitor でスコープを意識した処理を行いましょう。
    スコープ付きトラバースについての公式記事

  4. 公式ルールの構造に従ってカスタムルールを作成する
    既存ルールのコードを参考にして、自分のプロジェクト要件に合わせたクラスを作成します。
    詳しくは公式ドキュメントを参考にしてください。
    カスタムルールの作成についての公式記事

おまけ

実は、コロプラさんでも同じような記事があって、内容としては二番煎じだったりするので、こちらの記事をよければどうぞ。(この記事がきっかけでRectorを知りました)

Rectorではじめる "運用を止めない" PHPアップグレード

コロプラさんの記事では、PHP8.0からの曖昧比較(==)の挙動変更に対応するためPHPの独自拡張を作っていますが、小規模プロダクトなら最初から厳密比較(===)に置き換えておく方が早く、曖昧比較の挙動変更に関してはこちらで対応しました。

この辺の「どこまで自動化するか」「どこまで書き換えるか」はプロダクトの規模や実装次第で、この柔軟に手段を入れ替えられるかどうかが、エンジニアの腕の見せどころかなと思いました。

7
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?