PHPアプリケーションまたはライブラリを作成する場合、通常、次の3種類の依存関係があります。
- ハード依存関係:アプリケーション/ライブラリを実行するために必要です。
- オプションの依存関係:たとえば、PHPライブラリはさまざまなフレームワークのブリッジを提供できます。
- 開発関連の依存関係:デバッグツール、テストフレームワーク...
これらの依存関係を管理する方法は?
強い依存関係:
{ "require": { "acme/foo": "^1.0" } }
オプションの依存関係:
{ "suggest": { "monolog/monolog": "Advanced logging library", "ext-xml": "Required to support XML" } }
オプションおよび開発に関連する依存関係:
{ "require-dev": { "monolog/monolog": "^1.0", "phpunit/phpunit": "^6.0" } }
などなど。 何が悪いのでしょうか? require-dev
固有の制限がすべてrequire-dev
。
問題と制限
依存関係が多すぎる
パッケージマネージャーとの依存関係は素晴らしいです。 これは、コードの再利用と簡単な更新のための優れたメカニズムです。 ただし、依存関係と含める方法については、ユーザーが責任を負います。 エラーまたは脆弱性を含む可能性のあるコードを入力しています。 あなたは他の誰かによって書かれたものとあなたがコントロールさえできないかもしれないものに依存し始めます。 サードパーティの問題の被害者になる危険性があるという事実は言うまでもありません。 PackagistとGitHubはこのようなリスクを大幅に削減できますが、完全に排除するわけではありません。 JavaScriptコミュニティの左パッドの大失敗は、物事がうまくいかない状況の良い例です。そのため、パッケージを追加すると、不快な結果を招くことがあります。
依存関係の2番目の欠点は、互換性が必要であることです。 これはComposerにとっての挑戦です。 しかし、 Composerがどれほど優れていても、一緒に使用できない依存関係があり、追加する依存関係が多いほど、競合が発生する可能性が高くなります。
まとめ
依存関係を賢明に選択し、その数を制限してください。
厳しい対立
例を考えてみましょう:
{ "require-dev": { "phpstan/phpstan": "^1.0@dev", "phpmetrics/phpmetrics": "^2.0@dev" } }
これらの2つのパッケージは静的分析ツールです; PHP-Parserの異なる互換性のないバージョンに依存する可能性があるため、一緒にインストールすると競合する場合があります。
これは、「愚かな」競合の変形です。アプリケーションと互換性のない依存関係を含めようとした場合にのみ発生します。 パッケージは互換性がある必要はなく、アプリケーションはパッケージを直接使用せず、コードを実行しません。
ライブラリにSymfonyとLaravelを操作するためのブリッジが提供されている別の例を次に示します。 ブリッジをテストするには、SymfonyとLaravelの両方を依存関係として含めることができます。
{ "require-dev": { "symfony/framework-bundle": "^4.0", "laravel/framework": "~5.5.0" # gentle reminder that Laravel # packages are not semver } }
場合によっては、これで十分に機能する可能性がありますが、破損する可能性があります。 一部のユーザーが両方のパッケージを同時に使用する可能性は非常に低いため、この例はやや難解であり、このシナリオを提供する必要はほとんどありません。
未チェックの依存関係
このcomposer.json:
見てくださいcomposer.json:
{ "require": { "symfony/yaml": "^2.8 || ^3.0" }, "require-dev": { "symfony/yaml": "^3.0" } }
ここで何かが起こります... Symfony YAMLコンポーネント( symfony/yaml
)をバージョン[3.0.0, 4.0.0[
symfony/yaml
[3.0.0, 4.0.0[
。
アプリケーションでは、おそらくこれを気にしないでしょう。 しかしライブラリでは、 symfony/yaml [2.8.0, 3.0.0[
ライブラリをテストすることは決してできないため、これは問題につながる可能性があります。
これが実際の問題になるかどうかは、特定の状況に大きく依存します。 このような制限が適用される可能性があり、それを識別するのはそれほど簡単ではないことを心に留めておく必要があります。 簡単な例を示しますが、 symfony/yaml: ^3.0
要件が依存関係ツリーの奥深くに隠れている場合、たとえば:
{ "require": { "symfony/yaml": "^2.8 || ^3.0" }, "require-dev": { "acme/foo": "^1.0" # requires symfony/yaml ^3.0 } }
少なくとも今のところ、あなたはそれについて知りません。
解決策
パッケージを使用しないでください
キス 。 すべて問題ありません。実際、このパッケージは必要ありません。
PHAR
PHAR(PHPアーカイブ)-アプリケーションを単一のファイルにパックする方法。 詳細については、 公式のPHPドキュメントをご覧ください。
静的分析ツールであるPhpMetricsの使用例:
$ wget https://url/to/download/phpmetrics/phar-file -o phpmetrics.phar $ chmod +x phpmetrics.phar $ mv phpmetrics.phar /usr/local/bin/phpmetrics $ phpmetrics
重要: PHARにパックされたコードは、たとえばJavaのJARとは異なり、分離されていません。
問題を説明しましょう。 コンソールアプリmyapp.phar
作成しました。これは、PHPスクリプトを実行するSymfony YAML 2.8.0に依存しています。
$ myapp.phar myscript.php
myscript.php
スクリプトはComposerを使用してSymfony YAML 4.0.0を使用します。
PHARがSymfony\Yaml\Yaml
などのSymfony YAMLクラスをロードし、スクリプトを実行するとどうなりますか? また、 Symfony\Yaml\Yaml
使用しますが、クラスはすでにロードされています! スクリプトの必要に応じて、 4.0.0
からではなく、 symfony/yaml 2.8.0
パッケージからロードされます。 APIが異なる場合、すべてが完全に壊れます。
まとめ
PHARはPhpStanやPhpMetricsなどの静的分析ツールには最適ですが、依存関係の衝突に応じてコードを実行するため( 現時点では)信頼性がありません(少なくとも現時点では)。
PHARについてもう1つ注意すべき点があります。
- Composerでネイティブにサポートされていないため、追跡が困難です。 ただし、Composerプラグイン
tooly-composer-script
やPHARインストーラーのPhiVeなど、いくつかのソリューションがあります。 - バージョン管理はプロジェクトに大きく依存しています。 一部のプロジェクトでは、異なる安定性チャネルを使用してla Composerの
self-update
コマンドを使用しています。 他のプロジェクトは、最新リリースを備えた独自のダウンロードエンドポイントを提供します。 3番目のプロジェクトはGitHubリリース機能を使用して、PHARなどの形式で各リリースを配信します。
複数のリポジトリを使用する
最も一般的な手法の1つ。 単一のcomposer.json
ファイルですべてのブリッジの依存関係を要求する代わりに、パッケージを複数のリポジトリに分割します。
前のライブラリの例を見てください。 それをacme/foo
と呼び、Symfonyのacme/foo-bundle
パッケージとLaravelのacme/foo-provider
を作成します。
まだすべてを1つのリポジトリに入れることができ、Symfonyのような他のパッケージでは、読み取り専用リポジトリを使用することに注意してください。
このアプローチの主な利点は、Symfony、Laravel、PhpBBで使用されるsplitshなどのリポジトリ区切り文字を除き、比較的シンプルで追加のツールを必要としないことです。 欠点は、1つのパッケージの代わりに複数のパッケージをサポートする必要があることです。
構成設定
別の方法で、より高度なインストールおよびテストスクリプトを選択できます。 前の例では、次のようなものを使用できます。
それは機能しますが、私の経験では、テストスクリプトは肥大化しており、比較的遅く、保守が難しく、サードパーティのプログラマーにとって非常に簡単ではありません。
複数のcomposer.jsonを使用する
このアプローチは非常に新鮮です(PHPの場合)。主に以前は必要なツールがなかったため、もう少し説明します。
アイデアはシンプルです。 代わりに
{ "autoload": {...}, "autoload-dev": {...}, "require": {...}, "require-dev": { "phpunit/phpunit": "^6.0", "phpstan/phpstan": "^1.0@dev", "phpmetrics/phpmetrics": "^2.0@dev" } }
phpstan/phpstan
とphpmetrics/phpmetrics
を異なるcomposer.json
ファイルにインストールします。 しかし、ここで最初の困難が生じます:それらをどこに置くか? 構造を作成する方法は?
composer-bin-plugin
がここで役立ちます。 これは非常にシンプルなcomposer.json
プラグインで、異なるフォルダーでcomposer.json
とやり取りできます。 ルートcomposer.json
ファイルがあるとします:
{ "autoload": {...}, "autoload-dev": {...}, "require": {...}, "require-dev": { "phpunit/phpunit": "^6.0" } }
プラグインをインストールします。
$ composer require
その後、composer bin acme smth
を実行すると、 composer smth
がvendor-bin/acme
で実行されます。 次に、PhpStanとPhpMetricsをインストールします。
$ composer bin phpstan require phpstan/phpstan:^1.0@dev $ composer bin phpmetrics require phpmetrics/phpmetrics:^2.0@dev
次のディレクトリ構造が作成されます。
...
ここで、 vendor-bin/phpstan/composer.json
は次のようになります。
{ "require": { "phpstan/phpstan": "^1.0" } }
そして、 vendor-bin/phpmetrics/composer.json
は次のようになります。
{ "require": { "phpmetrics/phpmetrics": "^2.0" } }
これで、単にvendor-bin/phpstan/vendor/bin/phpstan
とvendor-bin/phpmetrics/vendor/bin/phpstan
呼び出すだけでPhpStanとPhpMetricsを使用できます。
続けましょう。 さまざまなフレームワークのブリッジを備えたライブラリの例を見てみましょう。
{ "autoload": {...}, "autoload-dev": {...}, "require": {...}, "require-dev": { "phpunit/phpunit": "^6.0", "symfony/framework-bundle": "^4.0", "laravel/framework": "~5.5.0" } }
同じアプローチを適用して、Symfonyブリッジのvendor-bin/symfony/composer.json
ファイルを取得します。
{ "autoload": {...}, "autoload-dev": {...}, "require": {...}, "require-dev": { "phpunit/phpunit": "^6.0", "symfony/framework-bundle": "^4.0" } }
Laravelブリッジのvendor-bin/laravel/composer.json
ファイル:
{ "autoload": {...}, "autoload-dev": {...}, "require": {...}, "require-dev": { "phpunit/phpunit": "^6.0", "laravel/framework": "~5.5.0" } }
ルートcomposer.json
ファイルは次のようになります。
{ "autoload": {...}, "autoload-dev": {...}, "require": {...}, "require-dev": { "bamarni/composer-bin-plugin": "^1.0" "phpunit/phpunit": "^6.0" } }
メインライブラリとブリッジをテストするには、3つの異なるPHPUnitファイルを作成し、それぞれに対応するスタートアップファイル(たとえば、Symfonyブリッジのvendor-bin/symfony/vendor/autoload.php
)を作成する必要があります。
自分で試してみると、アプローチの主な欠点である構成の冗長性に気付くでしょう。 ルートのcomposer.json
構成を他の2つのvendor-bin/{symfony,laravel/composer.json
する必要があります。ファイルパスは変更される可能性があるため、新しい依存関係が必要な場合は、他のファイルにも登録する必要がありますcomposer.json
。 不都合なことに判明しましたが、 composer-inheritance-plugin
が助けになります。
これはcomposer-merge-plugin
小さなラッパーです。これにより、 vendor-bin/symfony/composer.json
コンテンツをルートcomposer.json
と組み合わせることができます。 代わりに
{ "autoload": {...}, "autoload-dev": {...}, "require": {...}, "require-dev": { "phpunit/phpunit": "^6.0", "symfony/framework-bundle": "^4.0" } }
成功します
{ "require-dev": { "symfony/framework-bundle": "^4.0", "theofidry/composer-inheritance-plugin": "^1.0" } }
これには、ルートcomposer.json
の構成、起動、および依存関係の残りが含まれます。 composer-inheritance-plugin
は事前構成のためのcomposer-merge-plugin
薄いラップであり、 composer-bin-plugin
で使用できるようにするため、何も構成する必要はありません。
必要に応じて、インストールされている依存関係を調べることができます
$ composer bin symfony show
PhpStanやPHP-CS-Fixerなどのさまざまなツールやフレームワークのブリッジなど、さまざまなプロジェクト、たとえばalice
にこのアプローチを適用しました。 別の例としてalice-data-fixtures
があります。これは、データストレージ層(Doctrine ORM、Doctrine ODM、Eloquent ORMなど)およびフレームワーク統合に多くの異なるORMブリッジを使用します。
また、さまざまなツールを使用したアプリケーションのPHARの代替として、このアプローチをいくつかのプライベートプロジェクトに適用しました。
おわりに
私は誰かが方法のいくつかを奇妙であるか、推奨されないと思うと確信しています。 私は評価を与えたり、具体的な何かをアドバイスしたりするつもりはありませんでしたが、依存関係を管理する可能な方法、その長所と短所を説明したかっただけです。 あなたの仕事と個人的な好みに焦点を合わせて、あなたに最適なものを選択してください。 誰かが言ったように、解決策はなく、妥協しかありません。