PHP依存関係管理


PHPアプリケーションまたはライブラリを作成する場合、通常、次の3種類の依存関係があります。



これらの依存関係を管理する方法は?


強い依存関係:


{ "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


問題と制限


依存関係が多すぎる


パッケージマネージャーとの依存関係は素晴らしいです。 これは、コードの再利用と簡単な更新のための優れたメカニズムです。 ただし、依存関係と含める方法については、ユーザーが責任を負います。 エラーまたは脆弱性を含む可能性のあるコードを入力しています。 あなたは他の誰かによって書かれたものとあなたがコントロールさえできないかもしれないものに依存し始めます。 サードパーティの問題の被害者になる危険性があるという事実は言うまでもありません。 PackagistGitHubはこのようなリスクを大幅に削減できますが、完全に排除するわけではありません。 JavaScriptコミュニティの左パッドの大失敗は、物事がうまくいかない状況の良い例です。そのため、パッケージを追加すると、不快な結果を招くことがあります。


依存関係の2番目の欠点は、互換性が必要であることです。 これはComposerにとっての挑戦です。 しかし、 Composerがどれほど優れていても、一緒に使用できない依存関係があり、追加する依存関係が多いほど、競合が発生する可能性が高くなります。


まとめ


依存関係を賢明に選択し、その数を制限してください。


厳しい対立


例を考えてみましょう:


 { "require-dev": { "phpstan/phpstan": "^1.0@dev", "phpmetrics/phpmetrics": "^2.0@dev" } } 

これらの2つのパッケージは静的分析ツールです; PHP-Parserの異なる互換性のないバージョンに依存する可能性があるため、一緒にインストールすると競合する場合があります。


これは、「愚かな」競合の変形です。アプリケーションと互換性のない依存関係を含めようとした場合にのみ発生します。 パッケージは互換性がある必要はなく、アプリケーションはパッケージを直接使用せず、コードを実行しません。


ライブラリにSymfonyLaravelを操作するためのブリッジが提供されている別の例を次に示します。 ブリッジをテストするには、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 --version PhpMetrics, version 1.9.0 # or if you want to keep the PHAR close and do not mind the .phar # extension: $ phpmetrics.phar --version PhpMetrics, version 1.9.0 

重要: 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はPhpStanPhpMetricsなどの静的分析ツールには最適ですが、依存関係の衝突に応じてコードを実行するため( 現時点では)信頼性がありません(少なくとも現時点では)。


PHARについてもう1つ注意すべき点があります。



複数のリポジトリを使用する


最も一般的な手法の1つ。 単一のcomposer.jsonファイルですべてのブリッジの依存関係を要求する代わりに、パッケージを複数のリポジトリに分割します。


前のライブラリの例を見てください。 それをacme/fooと呼び、Symfonyのacme/foo-bundleパッケージとLaravelのacme/foo-providerを作成します。


まだすべてを1つのリポジトリに入れることができ、Symfonyのような他のパッケージでは、読み取り専用リポジトリを使用することに注意してください。


このアプローチの主な利点は、Symfony、Laravel、PhpBBで使用されるsplitshなどのリポジトリ区切り文字を除き、比較的シンプルで追加のツールを必要としないことです。 欠点は、1つのパッケージの代わりに複数のパッケージをサポートする必要があることです。


構成設定


別の方法で、より高度なインストールおよびテストスクリプトを選択できます。 前の例では、次のようなものを使用できます。


 #!/usr/bin/env bash # bin/tests.sh # Test the core library vendor/bin/phpunit --exclude-group=laravel,symfony # Test the Symfony bridge composer require symfony/framework-bundle:^4.0 vendor/bin/phpunit --group=symfony composer remove symfony/framework-bundle # Test the Laravel bridge composer require laravel/framework:~5.5.0 vendor/bin/phpunit --group=symfony composer remove laravel/framework 

それは機能しますが、私の経験では、テストスクリプトは肥大化しており、比較的遅く、保守が難しく、サードパーティのプログラマーにとって非常に簡単ではありません。


複数のcomposer.jsonを使用する


このアプローチは非常に新鮮です(PHPの場合)。主に以前は必要なツールがなかったため、もう少し説明します。


アイデアはシンプルです。 代わりに


 { "autoload": {...}, "autoload-dev": {...}, "require": {...}, "require-dev": { "phpunit/phpunit": "^6.0", "phpstan/phpstan": "^1.0@dev", "phpmetrics/phpmetrics": "^2.0@dev" } } 

phpstan/phpstanphpmetrics/phpmetricsを異なるcomposer.jsonファイルにインストールします。 しかし、ここで最初の困難が生じます:それらをどこに置くか? 構造を作成する方法は?


composer-bin-pluginがここで役立ちます。 これは非常にシンプルなcomposer.jsonプラグインで、異なるフォルダーでcomposer.jsonとやり取りできます。 ルートcomposer.jsonファイルがあるとします:


 { "autoload": {...}, "autoload-dev": {...}, "require": {...}, "require-dev": { "phpunit/phpunit": "^6.0" } } 

プラグインをインストールします。


 $ composer require --dev bamarni/composer-bin-plugin 

その後、composer bin acme smthを実行すると、 composer smthvendor-bin/acmeで実行されます。 次に、PhpStanとPhpMetricsをインストールします。


 $ composer bin phpstan require phpstan/phpstan:^1.0@dev $ composer bin phpmetrics require phpmetrics/phpmetrics:^2.0@dev 

次のディレクトリ構造が作成されます。


 ... # projects files/directories composer.json composer.lock vendor/ vendor-bin/ phpstan/ composer.json composer.lock vendor/ phpmetrics/ composer.json composer.lock vendor/ 

ここで、 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/phpstanvendor-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の代替として、このアプローチをいくつかのプライベートプロジェクトに適用しました。


おわりに


私は誰かが方法のいくつかを奇妙であるか、推奨されないと思うと確信しています。 私は評価を与えたり、具体的な何かをアドバイスしたりするつもりはありませんでしたが、依存関係を管理する可能な方法、その長所と短所を説明したかっただけです。 あなたの仕事と個人的な好みに焦点を合わせて、あなたに最適なものを選択してください。 誰かが言ったように、解決策はなく、妥協しかありません。



Source: https://habr.com/ru/post/J344498/


All Articles