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 8.0を使用し、新しいバージョンがリリースされるとすぐに更新できるはずですが、常にそうであるとは限りません。 PHPコードのトランスパイルについて知っておくべきことはすべてここで学んでくださいクリックしてツイート

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の使用
バージョン別のWordPress使用統計。 画像ソース:WordPress

その結果、世界中の視聴者を対象とした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.2objectタイプ
–パラメータタイプの拡大
preg_matchPREG_UNMATCHED_AS_NULLフラグ
7.3list() /配列の破棄での参照割り当て( 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は変換前後のコードの「差分」(緑の追加、赤の削除)を出力します。

Rectorからの「diff」出力
Rectorからの「diff」出力

トランスパイルする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 関数:
  • spl_object_id
  • utf8_encode
  • utf8_decode

定数:

  • PHP_FLOAT_*
  • PHP_OS_FAMILY
7.3 関数:
  • array_key_first
  • array_key_last
  • hrtime
  • is_countable

例外:

  • JsonException
7.4 関数:
  • get_mangled_object_vars
  • mb_str_split
  • password_algos
8.0 インターフェース:
  • Stringable

クラス:

  • ValueError
  • UnhandledMatchError

定数:

  • FILTER_VALIDATE_BOOL

関数:

  • fdiv
  • get_debug_type
  • preg_last_error_msg
  • str_contains
  • str_starts_with
  • str_ends_with
  • get_resource_id

トランスパイルされた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」出力はどのルールをまだ適用できるかを示します。 これは、最初の実行が完了しておらず、一部の連鎖ルールが処理されていないことを意味します。

--dry-runフラグを指定して実行中のRector
–dry-runフラグを使用してRectorを実行しています

適用されていない連鎖ルール(または複数のルール)を特定したら、別の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.jsonrequire-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つの解決策があります。

  1. Rector構成で、そのコードを参照するファイルの処理をスキップします。
 return static function (ContainerConfigurator $containerConfigurator): void { // ... $parameters->set(Option::SKIP, [ __DIR__ . '/vendor/symfony/cache/Messenger/EarlyExpirationHandler.php', ]); };
  1. 不足しているライブラリをダウンロードし、Rectorによって自動ロードされるパスを追加します。
 return static function (ContainerConfigurator $containerConfigurator): void { // ... $parameters->set(Option::AUTOLOAD_PATHS, [ __DIR__ . '/vendor/symfony/messenger', ]); };
  1. プロジェクトを本番用に不足しているライブラリに依存させます。
 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

まとめると、このワークフローは次の手順を実行します。

  1. PHP7.4で記述されたWordPressプラグインのソースコードをリポジトリからチェックアウトします
  2. Composerの依存関係をインストールします
  3. コードをPHP7.4から7.1にトランスパイルします
  4. プラグインのメインファイルのヘッダーにある「RequiresPHP」エントリを"7.4"から"7.1"に変更します
  5. 開発に必要な依存関係を削除します
  6. プラグインの.zipファイルを作成します。不要なファイルはすべて除外します。
  7. .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にトランスパイルしてテストするために実行する手順は、次のとおりです。

  1. ソースコードをチェックしてください
  2. 環境にPHP8.0を実行させて、Rectorがソースコードを解釈できるようにします
  3. コードをPHP7.1にトランスパイルする
  4. PHPリンターツールをインストールします
  5. 環境のPHPバージョンを7.1に切り替えます
  6. トランスパイルされたコードでリンターを実行します

この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'; }

WordPressのパブリックプラグインを作成している場合でも、レガシーコードを更新している場合でも、最新のPHPバージョンを使用できない理由はたくさんあります。このガイドでトランスパイルがどのように役立つかをご覧ください。 クリックしてツイート

概要

この記事では、PHPコードをトランスパイルして、ソースコードでPHP 8.0を使用し、PHP7.1で動作するリリースを作成する方法を説明しました。 トランスパイルは、PHPリコンストラクターツールであるRectorを介して行われます。

コードをトランスパイルすると、開発中のバグをより適切にキャッチし、自然に読みやすく理解しやすいコードを生成できるため、開発者が向上します。

トランスパイルを使用すると、コードをCMSから特定のPHP要件に分離することもできます。 ユーザーベースを厳しく制限することなく、最新バージョンのPHPを使用して、公開されているWordPressプラグインまたはDrupalモジュールを作成したい場合は、これを行うことができます。

PHPのトランスパイルについて質問がありますか? コメント欄でお知らせください!