PHPコードをトランスパイルするための究極のガイド
公開: 2021-09-22理想的な状況では、すべてのサイトでPHP 8.0(これを書いている時点で最新バージョン)を使用し、新しいバージョンがリリースされたらすぐに更新する必要があります。 ただし、開発者は、WordPressのパブリックプラグインを作成したり、Webサーバーの環境のアップグレードを妨げるレガシーコードを操作したりする場合など、以前のPHPバージョンを操作する必要があることがよくあります。
このような状況では、最新のPHPコードを使用するという希望をあきらめる可能性があります。 しかし、より良い代替手段があります。PHP8.0を使用してソースコードを記述し、それを以前のPHPバージョンに(PHP 7.1にさえ)トランスパイルすることができます。
このガイドでは、PHPコードのトランスパイルについて知っておく必要のあるすべてのことを説明します。
トランスパイルとは何ですか?
トランスパイルは、ソースコードをプログラミング言語から同じまたは異なるプログラミング言語の同等のソースコードに変換します。
トランスパイリングはWeb開発内の新しい概念ではありません。クライアント側の開発者は、JavaScriptコードのトランスパイラーであるBabelに精通している可能性があります。
Babelは、JavaScriptコードを最新のECMAScript2015+バージョンから古いブラウザーと互換性のあるレガシーバージョンに変換します。 たとえば、ES2015の矢印関数が与えられた場合:
[2, 4, 6].map((n) => n * 2);
…BabelはそれをES5バージョンに変換します。
[2, 4, 6].map(function(n) { return n * 2; });
トランスパイルPHPとは何ですか?
Web開発で潜在的に新しいのは、サーバー側のコード、特にPHPをトランスパイルする可能性です。
PHPのトランスパイルは、JavaScriptのトランスパイルと同じように機能します。最新のPHPバージョンのソースコードは、古いバージョンのPHPと同等のコードに変換されます。
前と同じ例に従って、PHP7.4の矢印関数は次のようになります。
$nums = array_map(fn($n) => $n * 2, [2, 4, 6]);
…同等のPHP7.3バージョンにトランスパイルできます。
$nums = array_map( function ($n) { return $n * 2; }, [2, 4, 6] );
矢印関数は構文糖衣、つまり既存の動作を生成するための新しい構文であるため、トランスパイルできます。 これはぶら下がっている果物です。
ただし、新しい動作を作成する新機能もあるため、以前のバージョンのPHPに相当するコードはありません。 これは、PHP8.0で導入された共用体型の場合です。
function someFunction(float|int $param): string|float|int|null { // ... }
このような状況でも、新機能が開発には必要であるが本番には必要でない限り、トランスパイルを実行できます。 そうすれば、深刻な結果を招くことなく、トランスパイルされたコードから機能を完全に削除できます。
そのような例の1つは、共用体型です。 この機能は、入力タイプと提供された値の間に不一致がないことを確認するために使用され、バグの防止に役立ちます。 型との競合がある場合は、すでに開発中にエラーが発生するため、コードが本番環境に到達する前に、それをキャッチして修正する必要があります。
したがって、本番用のコードから機能を削除する余裕があります。
function someFunction($param) { // ... }
それでも本番環境でエラーが発生する場合、スローされるエラーメッセージは、共用体タイプの場合よりも精度が低くなります。 ただし、この潜在的な欠点は、そもそも共用体型を使用できることによって打ち負かされます。
PHPコードをトランスパイルする利点
トランスパイルを使用すると、最新バージョンのPHPを使用してアプリケーションをコーディングし、古いバージョンのPHPを実行している環境でも機能するリリースを作成できます。
これは、レガシーコンテンツ管理システム(CMS)用の製品を作成する開発者にとって特に便利です。 たとえば、WordPressはまだ公式にPHP 5.6をサポートしています(PHP 7.4以降を推奨していますが)。 PHPバージョン5.6から7.2を実行しているWordPressサイトの割合(すべてEnd-of-Life(EOL)であり、セキュリティ更新プログラムを受信していないことを意味します)は、かなりの34.8%であり、PHPバージョン以外で実行されているサイトです。 8.0はなんと99.5%に相当します:

その結果、世界中の視聴者を対象としたWordPressのテーマとプラグインは、リーチを拡大するために古いバージョンのPHPでコーディングされる可能性が非常に高くなります。 トランスパイルのおかげで、これらはPHP 8.0を使用してコーディングでき、古いバージョンのPHPでもリリースできるため、できるだけ多くのユーザーをターゲットにできます。
実際、最新バージョン以外のPHPバージョンをサポートする必要のあるアプリケーション(現在サポートされているPHPバージョンの範囲内であっても)は恩恵を受けることができます。
これは、PHP7.3を必要とするDrupalの場合です。 トランスパイルのおかげで、開発者はPHP 8.0を使用して公開されているDrupalモジュールを作成し、PHP7.3でリリースすることができます。
もう1つの例は、何らかの理由で環境でPHP8.0を実行できないクライアント用のカスタムコードを作成する場合です。 それでも、トランスパイルのおかげで、開発者はPHP 8.0を使用して成果物をコーディングし、それらのレガシー環境で実行することができます。
PHPをトランスパイルするタイミング
PHPコードは、以前のバージョンのPHPに相当するものがないPHP機能が含まれていない限り、いつでもトランスパイルできます。
これは、PHP8.0で導入された属性の場合である可能性があります。
#[SomeAttr] function someFunc() {} #[AnotherAttr] class SomeClass {}
矢印関数を使用する前の例では、矢印関数は構文糖衣であるため、コードをトランスパイルすることができます。 対照的に、属性は完全に新しい動作を作成します。 この動作は、PHP 7.4以下でも再現できますが、手動でコーディングする必要があります。つまり、ツールやプロセスに基づいて自動的に実行することはできません(AIは解決策を提供できますが、まだありません)。
#[Deprecated]
などの開発用の属性は、共用体タイプを削除するのと同じ方法で削除できます。 ただし、本番環境でのアプリケーションの動作を変更する属性は削除できず、直接トランスパイルすることもできません。
現在のところ、トランスパイラーはPHP 8.0属性を持つコードを取得して、同等のPHP7.4コードを自動的に生成することはできません。 したがって、PHPコードで属性を使用する必要がある場合、それをトランスパイルすることは困難または実行不可能になります。
トランスパイルできるPHP機能
これらは、現在トランスパイルできるPHP7.1以降の機能です。 コードでこれらの機能のみを使用している場合は、トランスパイルされたアプリケーションが確実に機能することを享受できます。 それ以外の場合は、トランスパイルされたコードが失敗するかどうかを評価する必要があります。
PHPバージョン | 特徴 |
---|---|
7.1 | すべての |
7.2 | – object タイプ–パラメータタイプの拡大 preg_match のPREG_UNMATCHED_AS_NULL フラグ |
7.3 | – list() /配列の破棄での参照割り当て( foreach 内を除く—#4376)–柔軟なヒアドキュメントとNowdocの構文 –関数呼び出しの末尾のコンマ – set(raw)cookie は$option引数を受け入れます |
7.4 | –型付きプロパティ –矢印関数 –null合体代入演算子 –アレイ内の解凍 –数値リテラルセパレータ –タグ名の配列を strip_tags() –共変リターンタイプと反変パラメータタイプ |
8.0 | –共用体タイプ – mixed 疑似タイプ– static リターンタイプ– ::class マジック定数– match 式–タイプごとにのみ例外を catch する–ヌルセーフ演算子 –クラスコンストラクタープロパティの昇格 –パラメーターリストおよびクロージャ use リストの末尾のコンマ |
PHPトランスパイラー
現在、PHPコードをトランスパイルするためのツールが1つあります。それはRectorです。
Rectorは、プログラム可能なルールに基づいてPHPコードを変換するPHP再構築ツールです。 実行するソースコードと一連のルールを入力すると、Rectorがコードを変換します。
Rectorは、Composerを介してプロジェクトにインストールされたコマンドラインを介して操作されます。 実行されると、Rectorは変換前後のコードの「差分」(緑の追加、赤の削除)を出力します。

トランスパイルするPHPのバージョン
PHPバージョン間でコードをトランスパイルするには、対応するルールを作成する必要があります。
現在、Rectorライブラリには、PHP8.0から7.1の範囲内でコードをトランスパイルするためのほとんどのルールが含まれています。 したがって、PHPコードをバージョン7.1まで確実にトランスパイルできます。
PHP 7.1から7.0および7.0から5.6にトランスパイルするためのルールもありますが、これらは網羅的ではありません。 それらを完成させるための作業が進行中であるため、最終的にPHPコードをバージョン5.6にトランスパイルする可能性があります。
トランスパイルとバックポート
バックポートはトランスパイルに似ていますが、より単純です。 コードのバックポートは、必ずしも言語の新機能に依存しているわけではありません。 代わりに、新しいバージョンの言語から対応するコードをコピー/貼り付け/適応するだけで、同じ機能を古いバージョンの言語に提供できます。
たとえば、関数str_contains
はPHP8.0で導入されました。 PHP 7.4以下の同じ関数は、次のように簡単に実装できます。
if (!defined('PHP_VERSION_ID') || (defined('PHP_VERSION_ID') && PHP_VERSION_ID < 80000)) { if (!function_exists('str_contains')) { /** * Checks if a string contains another * * @param string $haystack The string to search in * @param string $needle The string to search * @return boolean Returns TRUE if the needle was found in haystack, FALSE otherwise. */ function str_contains(string $haystack, string $needle): bool { return strpos($haystack, $needle) !== false; } } }
バックポートはトランスパイルよりも単純であるため、バックポートが機能する場合は常にこのソリューションを選択する必要があります。
PHP 8.0から7.1までの範囲に関しては、Symfonyのポリフィルライブラリを使用できます。
- ポリフィルPHP7.1
- ポリフィルPHP7.2
- ポリフィルPHP7.3
- ポリフィルPHP7.4
- ポリフィルPHP8.0
これらのライブラリは、次の関数、クラス、定数、およびインターフェイスをバックポートします。
PHPバージョン | 特徴 |
---|---|
7.2 | 関数:
定数:
|
7.3 | 関数:
例外:
|
7.4 | 関数:
|
8.0 | インターフェース:
クラス:
定数:
関数:
|
トランスパイルされたPHPの例
トランスパイルされたPHPコードのいくつかの例と、完全にトランスパイルされているいくつかのパッケージを調べてみましょう。
PHPコード
match
式はPHP8.0で導入されました。 このソースコード:
function getFieldValue(string $fieldName): ?string { return match($fieldName) { 'foo' => 'foofoo', 'bar' => 'barbar', 'baz' => 'bazbaz', default => null, }; }
… switch
演算子を使用して、同等のPHP7.4バージョンにトランスパイルされます。
function getFieldValue(string $fieldName): ?string { switch ($fieldName) { case 'foo': return 'foofoo'; case 'bar': return 'barbar'; case 'baz': return 'bazbaz'; default: return null; } }
nullsafe演算子は、PHP8.0でも導入されました。
public function getValue(TypeResolverInterface $typeResolver): ?string { return $this->getResolver($typeResolver)?->getValue(); }
トランスパイルされたコードは、操作を2回実行しないように、最初に操作の値を新しい変数に割り当てる必要があります。
public function getValue(TypeResolverInterface $typeResolver): ?string { return ($val = $this->getResolver($typeResolver)) ? $val->getValue() : null; }
PHP 8.0でも導入されたコンストラクタープロパティプロモーション機能により、開発者は以下のコードを記述できます。
class QueryResolver { function __construct(protected QueryFormatter $queryFormatter) { } }
PHP 7.4にトランスパイルすると、コード全体が生成されます。
class QueryResolver { protected QueryFormatter $queryFormatter; function __construct(QueryFormatter $queryFormatter) { $this->queryFormatter = $queryFormatter; } }
上記のトランスパイルされたコードには、PHP7.4で導入された型付きプロパティが含まれています。 そのコードをPHP7.3にトランスパイルすると、それらはdocblockに置き換えられます。
class QueryResolver { /** * @var QueryFormatter */ protected $queryFormatter; function __construct(QueryFormatter $queryFormatter) { $this->queryFormatter = $queryFormatter; } }
PHPパッケージ
次のライブラリは、本番用にトランスパイルされています。
ライブラリ/説明 | コード/メモ |
---|---|
学長 トランスパイルを可能にするPHPリコンストラクタツール | - ソースコード –トランスパイルされたコード - ノート |
簡単なコーディング標準 PHPコードを一連のルールに準拠させるためのツール | - ソースコード –トランスパイルされたコード - ノート |
WordPress用のGraphQLAPI WordPress用のGraphQLサーバーを提供するプラグイン | - ソースコード –トランスパイルされたコード - ノート |
PHPのトランスパイルの長所と短所
PHPをトランスパイルする利点については、すでに説明しました。これにより、ソースコードでPHP 8.0(つまり、最新バージョンのPHP)を使用できるようになります。これは、レガシーアプリケーションまたは環境で実行するためにPHPの下位バージョンに変換されます。
これにより、効果的に優れた開発者になり、より高品質のコードを作成できます。 これは、ソースコードで、PHP 8.0の共用体型、PHP 7.4の型付きプロパティ、および新しいバージョンのPHP(PHP 8.0からmixed
、PHP 7.2からのobject
)に追加されたさまざまな型と疑似型を使用できるためです。 PHPの他の最新機能。
これらの機能を使用すると、開発中のバグをより適切にキャッチし、読みやすいコードを記述できます。
それでは、欠点を見てみましょう。
コード化して維持する必要があります
Rectorはコードを自動的にトランスパイルできますが、特定のセットアップで機能させるには、プロセスで手動入力が必要になる可能性があります。
サードパーティのライブラリもトランスパイルする必要があります
これは、トランスパイルによってエラーが発生するたびに問題になります。これは、考えられる理由を見つけるためにソースコードを詳しく調べる必要があるためです。 問題を修正でき、プロジェクトがオープンソースである場合は、プルリクエストを送信する必要があります。 ライブラリがオープンソースでない場合、障害が発生する可能性があります。
コードをトランスパイルできない場合、レクターは通知しません
ソースコードにPHP8.0属性またはトランスパイルできないその他の機能が含まれている場合、続行できません。 ただし、Rectorはこの状態をチェックアウトしないため、手動でチェックアウトする必要があります。 これは、私たち自身のソースコードについてはすでによく知っているので大きな問題ではないかもしれませんが、サードパーティの依存関係に関する障害になる可能性があります。
デバッグ情報は、ソースコードではなく、トランスパイルされたコードを使用します
アプリケーションが本番環境でスタックトレースを含むエラーメッセージを生成する場合、行番号はトランスパイルされたコードを指します。 ソースコードで対応する行番号を見つけるには、トランスパイルされたコードから元のコードに変換し直す必要があります。
トランスパイルされたコードにもプレフィックスを付ける必要があります
トランスパイルされたプロジェクトと、実稼働環境にインストールされている他のライブラリも、同じサードパーティの依存関係を使用できます。 このサードパーティの依存関係は、プロジェクト用にトランスパイルされ、他のライブラリ用に元のソースコードを保持します。 したがって、潜在的な競合を回避するために、トランスパイルされたバージョンには、PHP-Scoper、Strauss、またはその他のツールを介してプレフィックスを付ける必要があります。
トランスパイルは継続的インテグレーション(CI)中に実行する必要があります
トランスパイルされたコードは当然ソースコードをオーバーライドするため、開発用コンピューターでトランスパイルプロセスを実行しないでください。実行すると、副作用が発生するリスクがあります。 CIの実行中にプロセスを実行する方が適切です(これについては以下で詳しく説明します)。
PHPをトランスパイルする方法
まず、開発用のプロジェクトにRectorをインストールする必要があります。
composer require rector/rector --dev
次に、必要なルールのセットを含むプロジェクトのルートディレクトリにrector.php
構成ファイルを作成します。 コードをPHP8.0から7.1にダウングレードするには、次の構成を使用します。
use Rector\Set\ValueObject\DowngradeSetList; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; return static function (ContainerConfigurator $containerConfigurator): void { $containerConfigurator->import(DowngradeSetList::PHP_80); $containerConfigurator->import(DowngradeSetList::PHP_74); $containerConfigurator->import(DowngradeSetList::PHP_73); $containerConfigurator->import(DowngradeSetList::PHP_72); };
プロセスが期待どおりに実行されることを確認するために、Rectorのprocess
コマンドをドライモードで実行し、処理する場所(この場合はsrc/
フォルダーの下のすべてのファイル)を渡すことができます。
vendor/bin/rector process src --dry-run
トランスパイルを実行するには、Rectorのprocess
コマンドを実行します。これにより、既存の場所にあるファイルが変更されます。
vendor/bin/rector process src
注意:開発用コンピューターでrector process
を実行すると、ソースコードがsrc/
の下に変換されます。 ただし、コードをダウングレードするときにソースコードを上書きしないように、変換されたコードを別の場所で生成する必要があります。 このため、継続的インテグレーションではプロセスの実行が最適です。

トランスパイルプロセスの最適化
本番用にトランスパイルされた成果物を生成するには、本番用のコードのみを変換する必要があります。 開発にのみ必要なコードはスキップできます。 つまり、すべてのテスト(プロジェクトとその依存関係の両方)とすべての依存関係を開発のためにトランスパイルすることを回避できます。
テストに関しては、プロジェクトのテストがどこにあるかはすでにわかっています。たとえば、 tests/
フォルダーの下にあります。 また、依存関係のあるものがどこにあるかを確認する必要があります。たとえば、サブフォルダーtests/
、 test/
、およびTest/
(異なるライブラリーの場合)の下にあります。 次に、これらのフォルダーの処理をスキップするようにRectorに指示します。
return static function (ContainerConfigurator $containerConfigurator): void { // ... $parameters->set(Option::SKIP, [ // Skip tests '*/tests/*', '*/test/*', '*/Test/*', ]); };
依存関係に関して、Composerは、開発用のもの( composer.json
のエントリrequire-dev
の下にあるもの)と本番用のもの(エントリrequire
の下にあるもの)を認識しています。
本番環境のすべての依存関係のパスをComposerから取得するには、次のコマンドを実行します。
composer info --path --no-dev
このコマンドは、次のように、名前とパスを含む依存関係のリストを生成します。
brain/cortex /Users/leo/GitHub/leoloso/PoP/vendor/brain/cortex composer/installers /Users/leo/GitHub/leoloso/PoP/vendor/composer/installers composer/semver /Users/leo/GitHub/leoloso/PoP/vendor/composer/semver guzzlehttp/guzzle /Users/leo/GitHub/leoloso/PoP/vendor/guzzlehttp/guzzle league/pipeline /Users/leo/GitHub/leoloso/PoP/vendor/league/pipeline
すべてのパスを抽出してRectorコマンドにフィードすると、プロジェクトのsrc/
フォルダーと本番環境のすべての依存関係を含むフォルダーが処理されます。
$ paths="$(composer info --path --no-dev | cut -d' ' -f2- | sed 's/ //g' | tr '\n' ' ')" $ vendor/bin/rector process src $paths
さらなる改善により、RectorがターゲットPHPバージョンをすでに使用しているこれらの依存関係を処理するのを防ぐことができます。 ライブラリがPHP7.1(または以下のバージョン)でコーディングされている場合は、ライブラリをPHP7.1にトランスパイルする必要はありません。
これを実現するために、PHP 7.2以降を必要とするライブラリのリストを取得し、それらのみを処理できます。 これらすべてのライブラリの名前は、次のように、Composerのwhy-not
コマンドを介して取得します。
composer why-not php "7.1.*" | grep -o "\S*\/\S*"
このコマンドは--no-dev
フラグでは機能しないため、本番環境の依存関係のみを含めるには、最初に開発の依存関係を削除してオートローダーを再生成し、コマンドを実行してから、それらを再度追加する必要があります。
$ composer install --no-dev $ packages=$(composer why-not php "7.1.*" | grep -o "\S*\/\S*") $ composer install
Composerのinfo --path
コマンドは、次の形式でパッケージのパスを取得します。
# Executing this command $ composer info psr/cache --path # Produces this response: psr/cache /Users/leo/GitHub/leoloso/PoP/vendor/psr/cache
リスト内のすべてのアイテムに対してこのコマンドを実行して、トランスパイルするすべてのパスを取得します。
for package in $packages do path=$(composer info $package --path | cut -d' ' -f2-) paths="$paths $path" done
最後に、このリストをRector(およびプロジェクトのsrc/
フォルダー)に提供します。
競争力を高めるホスティングソリューションが必要ですか? Kinstaは、信じられないほどの速度、最先端のセキュリティ、および自動スケーリングであなたをカバーします。 私たちの計画をチェックしてください
vendor/bin/rector process src $paths
コードをトランスパイルするときに避けるべき落とし穴
コードのトランスパイルは芸術と見なすことができ、プロジェクトに固有の微調整が必要になることがよくあります。 私たちが遭遇する可能性のあるいくつかの問題を見てみましょう。
連鎖ルールは常に処理されるとは限りません
連鎖ルールとは、ルールが前のルールによって生成されたコードを変換する必要がある場合です。
たとえば、ライブラリsymfony/cache
には次のコードが含まれています。
final class CacheItem implements ItemInterface { public function tag($tags): ItemInterface { // ... return $this; } }
PHP 7.4から7.3にトランスパイルする場合、関数tag
は2つの変更を行う必要があります。
- ルール
DowngradeCovariantReturnTypeRector
により、リターンタイプItemInterface
は最初にself
に変換する必要があります - 次に、ルール
DowngradeSelfTypeDeclarationRector
により、リターンタイプself
を削除する必要があります。
最終結果は次のようになります。
final class CacheItem implements ItemInterface { public function tag($tags) { // ... return $this; } }
ただし、Rectorは中間段階のみを出力します。
final class CacheItem implements ItemInterface { public function tag($tags): self { // ... return $this; } }
問題は、学長が規則が適用される順序を常に制御できるとは限らないということです。
解決策は、どの連鎖ルールが未処理のままになったかを識別し、それらを適用するために新しいRector実行を実行することです。
連鎖したルールを識別するために、次のようにソースコードでRectorを2回実行します。
$ vendor/bin/rector process src $ vendor/bin/rector process src --dry-run
初めて、期待どおりにRectorを実行して、トランスパイルを実行します。 2回目は、 --dry-run
フラグを使用して、まだ変更が必要かどうかを検出します。 存在する場合、コマンドはエラーコードで終了し、「diff」出力はどのルールをまだ適用できるかを示します。 これは、最初の実行が完了しておらず、一部の連鎖ルールが処理されていないことを意味します。

適用されていない連鎖ルール(または複数のルール)を特定したら、別のRector構成ファイルを作成できます。たとえば、 rector-chained-rule.php
は欠落しているルールを実行します。 今回は、 src/
の下にあるすべてのファイルのルールの完全なセットを処理する代わりに、適用する必要がある特定のファイルに対して特定の欠落しているルールを実行できます。
// rector-chained-rule.php use Rector\Core\Configuration\Option; use Rector\DowngradePhp74\Rector\ClassMethod\DowngradeSelfTypeDeclarationRector; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; return static function (ContainerConfigurator $containerConfigurator): void { $services = $containerConfigurator->services(); $services->set(DowngradeSelfTypeDeclarationRector::class); $parameters = $containerConfigurator->parameters(); $parameters->set(Option::PATHS, [ __DIR__ . '/vendor/symfony/cache/CacheItem.php', ]); };
最後に、2回目のパスで--config
にinput--configを介して新しい構成ファイルを使用するように指示します。
# First pass with all modifications $ vendor/bin/rector process src # Second pass to fix a specific problem $ vendor/bin/rector process --config=rector-chained-rule.php
Composerの依存関係に一貫性がない可能性があります
ライブラリは、開発が予定されている依存関係を宣言できますが(つまり、 composer.json
のrequire-dev
の下で)、それでも、本番用にライブラリからいくつかのコードを参照します(たとえば、 tests/
ではなくsrc/
の下の一部のファイル)。
通常、このコードは本番環境で読み込まれない可能性があるため、これは問題にはなりません。したがって、アプリケーションでエラーが発生することはありません。 ただし、Rectorがソースコードとその依存関係を処理するとき、参照されているすべてのコードをロードできることを検証します。 インストールされていないライブラリのコードをファイルが参照している場合、Rectorはエラーをスローします(開発にのみ必要であると宣言されているため)。
たとえば、SymfonyのCacheコンポーネントのクラスEarlyExpirationHandler
は、MessengerコンポーネントのインターフェースMessageHandlerInterface
を実装します。
class EarlyExpirationHandler implements MessageHandlerInterface { //... }
ただし、 symfony/cache
は、symfony symfony/messenger
が開発の依存関係であることを宣言しています。 次に、 symfony/cache
に依存するプロジェクトでRectorを実行すると、エラーがスローされます。
[ERROR] Could not process "vendor/symfony/cache/Messenger/EarlyExpirationHandler.php" file, due to: "Analyze error: "Class Symfony\Component\Messenger\Handler\MessageHandlerInterface not found.". Include your files in "$parameters->set(Option::AUTOLOAD_PATHS, [...]);" in "rector.php" config. See https://github.com/rectorphp/rector#configuration".
この問題には3つの解決策があります。
- Rector構成で、そのコードを参照するファイルの処理をスキップします。
return static function (ContainerConfigurator $containerConfigurator): void { // ... $parameters->set(Option::SKIP, [ __DIR__ . '/vendor/symfony/cache/Messenger/EarlyExpirationHandler.php', ]); };
- 不足しているライブラリをダウンロードし、Rectorによって自動ロードされるパスを追加します。
return static function (ContainerConfigurator $containerConfigurator): void { // ... $parameters->set(Option::AUTOLOAD_PATHS, [ __DIR__ . '/vendor/symfony/messenger', ]); };
- プロジェクトを本番用に不足しているライブラリに依存させます。
composer require symfony/messenger
トランスパイルと継続的インテグレーション
前述のように、開発用コンピューターでは、Rectorを実行するときに--dry-run
フラグを使用する必要があります。そうしないと、ソースコードがトランスパイルされたコードで上書きされます。 このため、継続的インテグレーション(CI)中に実際のトランスパイルプロセスを実行する方が適切です。この場合、一時的なランナーを起動してプロセスを実行できます。
トランスパイルプロセスを実行する理想的なタイミングは、プロジェクトのリリースを生成するときです。 たとえば、以下のコードはGitHub Actionsのワークフローであり、WordPressプラグインのリリースを作成します。
name: Generate Installable Plugin and Upload as Release Asset on: release: types: [published] jobs: build: name: Build, Downgrade and Upload Release runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/[email protected] - name: Downgrade code for production (to PHP 7.1) run: | composer install vendor/bin/rector process sed -i 's/Requires PHP: 7.4/Requires PHP: 7.1/' graphql-api.php - name: Build project for production run: | composer install --no-dev --optimize-autoloader mkdir build - name: Create artifact uses: montudor/[email protected] with: args: zip -X -r build/graphql-api.zip . -x *.git* node_modules/\* .* "*/\.*" CODE_OF_CONDUCT.md CONTRIBUTING.md ISSUE_TEMPLATE.md PULL_REQUEST_TEMPLATE.md rector.php *.dist composer.* dev-helpers** build** - name: Upload artifact uses: actions/[email protected] with: name: graphql-api path: build/graphql-api.zip - name: Upload to release uses: JasonEtco/[email protected] with: args: build/graphql-api.zip application/zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
このワークフローには、GitHubアクションを介してWordPressプラグインをリリースするための標準的な手順が含まれています。 プラグインのコードをPHP7.4から7.1にトランスパイルするための新しい追加は、次のステップで行われます。
- name: Downgrade code for production (to PHP 7.1) run: | vendor/bin/rector process sed -i 's/Requires PHP: 7.4/Requires PHP: 7.1/' graphql-api.php
まとめると、このワークフローは次の手順を実行します。
- PHP7.4で記述されたWordPressプラグインのソースコードをリポジトリからチェックアウトします
- Composerの依存関係をインストールします
- コードをPHP7.4から7.1にトランスパイルします
- プラグインのメインファイルのヘッダーにある「RequiresPHP」エントリを
"7.4"
から"7.1"
に変更します - 開発に必要な依存関係を削除します
- プラグインの.zipファイルを作成します。不要なファイルはすべて除外します。
- .zipファイルをリリースアセットとして(さらに、GitHubアクションのアーティファクトとして)アップロードします
トランスパイルされたコードのテスト
コードがPHP7.1にトランスパイルされたら、それがうまく機能していることをどうやって知ることができますか? または、言い換えると、完全に変換され、PHPコードの上位バージョンの残骸が残されていないことをどのようにして知ることができますか?
コードのトランスパイルと同様に、CIプロセス内にソリューションを実装できます。 アイデアは、PHP 7.1を使用してランナーの環境をセットアップし、トランスパイルされたコードでリンターを実行することです。 コードのいずれかがPHP7.1と互換性がない場合(変換されなかったPHP 7.4の型付きプロパティなど)、リンターはエラーをスローします。
うまく機能するPHPのリンターは、PHPParallelLintです。 このライブラリをプロジェクトの開発の依存関係としてインストールするか、CIプロセスにスタンドアロンのComposerプロジェクトとしてインストールさせることができます。
composer create-project php-parallel-lint/php-parallel-lint
コードにPHP7.2以降が含まれている場合は常に、PHPParallelLintは次のようなエラーをスローします。
Run php-parallel-lint/parallel-lint layers/ vendor/ --exclude vendor/symfony/polyfill-ctype/bootstrap80.php --exclude vendor/symfony/polyfill-intl-grapheme/bootstrap80.php --exclude vendor/symfony/polyfill-intl-idn/bootstrap80.php --exclude vendor/symfony/polyfill-intl-normalizer/bootstrap80.php --exclude vendor/symfony/polyfill-mbstring/bootstrap80.php PHP 7.1.33 | 10 parallel jobs ............................................................ 60/2870 (2 %) ............................................................ 120/2870 (4 %) ... ............................................................ 660/2870 (22 %) .............X.............................................. 720/2870 (25 %) ............................................................ 780/2870 (27 %) ... ............................................................ 2820/2870 (98 %) .................................................. 2870/2870 (100 %) Checked 2870 files in 15.4 seconds Syntax error found in 1 file ------------------------------------------------------------ Parse error: layers/GraphQLAPIForWP/plugins/graphql-api-for-wp/graphql-api.php:55 53| '0.8.0', 54| \__('GraphQL API for WordPress', 'graphql-api'), > 55| ))) { 56| $plugin->setup(); 57| } Unexpected ')' in layers/GraphQLAPIForWP/plugins/graphql-api-for-wp/graphql-api.php on line 55 Error: Process completed with exit code 1.
CIのワークフローにリンターを追加しましょう。 コードをPHP8.0から7.1にトランスパイルしてテストするために実行する手順は、次のとおりです。
- ソースコードをチェックしてください
- 環境にPHP8.0を実行させて、Rectorがソースコードを解釈できるようにします
- コードをPHP7.1にトランスパイルする
- PHPリンターツールをインストールします
- 環境のPHPバージョンを7.1に切り替えます
- トランスパイルされたコードでリンターを実行します
このGitHubActionワークフローは次の役割を果たします。
name: Downgrade PHP tests jobs: main: name: Downgrade code to PHP 7.1 via Rector, and execute tests runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/[email protected] - name: Set-up PHP uses: shivammathur/[email protected] with: php-version: 8.0 coverage: none - name: Local packages - Downgrade PHP code via Rector run: | composer install vendor/bin/rector process # Prepare for testing on PHP 7.1 - name: Install PHP Parallel Lint run: composer create-project php-parallel-lint/php-parallel-lint --ansi - name: Switch to PHP 7.1 uses: shivammathur/[email protected] with: php-version: 7.1 coverage: none # Lint the transpiled code - name: Run PHP Parallel Lint on PHP 7.1 run: php-parallel-lint/parallel-lint src/ vendor/ --exclude vendor/symfony/polyfill-ctype/bootstrap80.php --exclude vendor/symfony/polyfill-intl-grapheme/bootstrap80.php --exclude vendor/symfony/polyfill-intl-idn/bootstrap80.php --exclude vendor/symfony/polyfill-intl-normalizer/bootstrap80.php --exclude vendor/symfony/polyfill-mbstring/bootstrap80.php
Symfonyのポリフィルライブラリからのいくつかのbootstrap80.php
ファイル(トランスパイルする必要はありません)をリンターから除外する必要があることに注意してください。 これらのファイルにはPHP8.0が含まれているため、リンターはそれらを処理するときにエラーをスローします。 ただし、これらのファイルはPHP 8.0以降を実行している場合にのみ本番環境で読み込まれるため、これらのファイルを除外しても安全です。
if (\PHP_VERSION_ID >= 80000) { return require __DIR__.'/bootstrap80.php'; }
概要
この記事では、PHPコードをトランスパイルして、ソースコードでPHP 8.0を使用し、PHP7.1で動作するリリースを作成する方法を説明しました。 トランスパイルは、PHPリコンストラクターツールであるRectorを介して行われます。
コードをトランスパイルすると、開発中のバグをより適切にキャッチし、自然に読みやすく理解しやすいコードを生成できるため、開発者が向上します。
トランスパイルを使用すると、コードをCMSから特定のPHP要件に分離することもできます。 ユーザーベースを厳しく制限することなく、最新バージョンのPHPを使用して、公開されているWordPressプラグインまたはDrupalモジュールを作成したい場合は、これを行うことができます。
PHPのトランスパイルについて質問がありますか? コメント欄でお知らせください!