symfony/skeletonのcomposer.jsonを読んでみる
Symfony Advent Calendar 2023 の8日目の記事です!🎄✨
Twitter (X) でもちょいちょいSymfonyネタを呟いてます。よろしければ フォロー お願いします🤲
昨日は @77web さんの記事でした✨
はじめに
公式ドキュメント の手順に従ってSymfonyをインストールすると、初めから composer.json
に色々な設定が書かれている状態でプロジェクトが作られますよね。
しかし、この composer.json
に書かれている内容についてちゃんと把握して使っている人は意外と少ないのではないかと思います。
というわけで、今日はSymfonyをインストールしたときのデフォルトの composer.json
の内容を、一つひとつ意味を確認しながら読んでみたいと思います。
本稿執筆時点で、composer create-project symfony/skeleton
によって作成されるプロジェクトの composer.json
は以下の内容になります。
{
"type": "project",
"license": "proprietary",
"minimum-stability": "stable",
"prefer-stable": true,
"require": {
"php": ">=8.2",
"ext-ctype": "*",
"ext-iconv": "*",
"symfony/console": "7.0.*",
"symfony/dotenv": "7.0.*",
"symfony/flex": "^2",
"symfony/framework-bundle": "7.0.*",
"symfony/runtime": "7.0.*",
"symfony/yaml": "7.0.*"
},
"require-dev": {
},
"config": {
"allow-plugins": {
"php-http/discovery": true,
"symfony/flex": true,
"symfony/runtime": true
},
"sort-packages": true
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
},
"replace": {
"symfony/polyfill-ctype": "*",
"symfony/polyfill-iconv": "*",
"symfony/polyfill-php72": "*",
"symfony/polyfill-php73": "*",
"symfony/polyfill-php74": "*",
"symfony/polyfill-php80": "*",
"symfony/polyfill-php81": "*",
"symfony/polyfill-php82": "*"
},
"scripts": {
"auto-scripts": {
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd"
},
"post-install-cmd": [
"@auto-scripts"
],
"post-update-cmd": [
"@auto-scripts"
]
},
"conflict": {
"symfony/symfony": "*"
},
"extra": {
"symfony": {
"allow-contrib": false,
"require": "7.0.*"
}
}
}
この composer.json
の内容について、上から順に見ていきましょう!
type
license
minimum-stability
prefer-stable
type
プロパティはパッケージの種類を示すためのものです。Symfonyアプリケーションは project
です。
license
プロパティはパッケージのライセンスを示すためのものです。クローズドなプロジェクトにおいてはライセンス名の代わりに proprietary
という識別子が使用できます。Symfonyアプリケーションのデフォルトも proprietary
となっています。
minimum-stability
プロパティは、インストールする依存パッケージの安定性をフィルタリングするためのものです。Symfonyアプリケーションのデフォルトは stable
となっており、この場合、例えば require
プロパティに "some/library": "^2.0"
と書かれていたら、some/library
の 2.0
以上 3.0
未満の stable版 のみが適合します。もし minimum-stability
が dev
なら、some/library
の 2.0
以上 3.0
未満の dev版以上 がすべて適合します。(つまり、dev版やbeta版などがインストールされうる)
prefer-stable
プロパティは、依存パッケージをインストールする際により安定なバージョンが選択されるようにするためのフラグです。例えば、minimum-stability
が dev
で、依存パッケージの 2.0.0
と 2.1.x-dev
の2つのバージョンが適合したとき、"prefer-stable": true
がセットされている場合は(バージョン番号がより低くても)より安定な 2.0.0
がインストールされます。
Symfonyアプリケーションのデフォルトは "prefer-stable": true
ですが、minimum-stability
が stable
である限りは実質的に意味を成しません(そもそもstable版にしか適合しないので、その中で「より安定」を選択する余地がない)。開発の途中で minimum-stability
をstable未満に変更したときに、"prefer-stable": true
がセットされていると「必要に応じて不安定なバージョンを許容しつつ、できる限り安定なバージョンを使う」という挙動になるという効果があります。
require
require-dev
require
プロパティと require-dev
プロパティはさすがに説明不要でしょうか。
require
にはこのパッケージが本番環境の文脈で必要とする依存パッケージを、require-dev
には開発中やテストの実行などにのみ必要な依存パッケージを列挙します。
composer install --no-dev
でインストールすることで require-dev
の依存パッケージを除いて require
の依存パッケージのみをインストールすることができます。本番環境にデプロイする際には --no-dev
オプションを付けるのを忘れないようにしましょう。
config.allow-plugins
config.sort-packages
config.allow-plugins
プロパティは、Composerの実行中にどのComposerプラグインにコードの実行を許可するかを指定するためのものです。セキュリティのためにComposer 2.2.0以降で追加された プロパティです。Symfonyアプリケーションのデフォルトでは
"php-http/discovery": true,
"symfony/flex": true,
"symfony/runtime": true
の3つのComposerプラグインパッケージが「許可」として指定されています。
config.sort-packages
プロパティは、composer require
コマンドで composer.json
に依存パッケージを追記する際に、require
および require-dev
配下のパッケージの並びを名前でソートするためのフラグです。Symfonyアプリケーションのデフォルトは "sort-packages": true
です。
autoload.psr-4
autoload-dev.psr-4
autoload
プロパティと autoload-dev
プロパティは、Composerが提供するPHPオートローダーに対しオートローディングのマッピング情報を指示するためのものです。psr-4
psr-0
classmap
files
プロパティによる指定がサポートされており、Symfonyアプリケーションのデフォルトでは psr-4
が使用されています。
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
この記述により、
-
src/
ディレクトリ配下のクラスファイル群を、App\
名前空間配下にマッピングする - その際、
src/
ディレクトリ配下のクラスファイル群は PSR-4 に準拠して構成されていることを期待する
という指示をしていることになります。これにより、例えば src/Controller/HomeController.php
に App\Controller\HomeController
クラスを定義(PSR-4準拠)しておくと、他のクラスで App\Controller\HomeController
を use
した際に自動で src/Controller/HomeController.php
がロードされてくれる、という(普段意識していなくてもお馴染みになっている)挙動が実現されます。
autoload-dev
は、開発中やテストの実行などにのみ必要なクラスのオートロードルールを、メインのオートロードルールとは分けて定義するためのものです。
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
},
この記述で、tests/
ディレクトリ配下を App\Tests\
名前空間配下にマッピングしています。
replace
replace
プロパティはちょっと難解です(というか僕の理解も正しいか自信薄なので有識者からのご指摘お待ちしてます🙏)。
公式ドキュメントの説明をGoogle翻訳で日本語訳すると以下のような内容になっています。
このパッケージによって置き換えられるパッケージのマップ。これにより、パッケージをフォークし、独自のバージョン番号を持つ別の名前で公開できますが、元のパッケージが置き換えられるため、元のパッケージを必要とするパッケージは引き続きフォークで動作します。
これは、サブパッケージを含むパッケージにも役立ちます。たとえば、メインの symfony/symfony パッケージには、個別のパッケージとしても利用できるすべての Symfony コンポーネントが含まれています。メイン パッケージが必要な場合は、個別のコンポーネントのいずれかの要件が置き換えられるため、その要件が自動的に満たされます。
上記で説明したサブパッケージの目的で replace を使用する場合は注意が必要です。通常は、バージョン制約として
self.version
を使用して置換するだけにして、メイン パッケージがその正確なバージョンのサブパッケージのみを置換し、他のバージョンは置換しないようにする必要があります (これは正しくありません)。
正直これを読んでも「???」って感じですよね。
個人的には以下のページが理解に役立ちました。
- How does the "replace" property work with composer? - Stack Overflow
- How does the “replace” property work in Composer? - Darwin Biler
以下に要約します。
-
replace
プロパティの本来の使い方は以下のようなもの- 例えば
original/library
をフォークして自作したbetter/library
のcomposer.json
に、"replace": {"original/libary": "1.0.2"}
のように書いておくと、「better/library
はoriginal/library:1.0.2
の代替である」ということをComposerに伝えることができる - これにより、あるパッケージXが
better/library
とother/package
に依存していて、other/package
がoriginal/library:1.0.2
に依存している場合 に、パッケージ Xでcomposer install
をした際、original/library:1.0.2
はインストールされず、代わりにbetter/library
が使われる という挙動を実現できる
- 例えば
- 応用的に、多数のコンポーネントから成るフレームワークの
composer.json
には、以下の理由から、「各コンポーネントをフレームワーク自身でreplace
する記述」がある場合がある- フレームワークとコンポーネントの両方に依存しているユーザーに対して、コンポーネントを重複してインストールさせたくない
- なぜなら、フレームワークさえインストールされていればそこにすべてのコンポーネントが含まれるので
- 例えば
symfony/symfony
のcomposer.json
は 確かにこんな感じになっている
これを理解した上で、symfony/skeleton
の composer.json
の replace
プロパティの内容を見てみましょう。
"replace": {
"symfony/polyfill-ctype": "*",
"symfony/polyfill-iconv": "*",
"symfony/polyfill-php72": "*",
"symfony/polyfill-php73": "*",
"symfony/polyfill-php74": "*",
"symfony/polyfill-php80": "*",
"symfony/polyfill-php81": "*",
"symfony/polyfill-php82": "*"
},
「自分自身が、各種polyfill系パッケージの *
(任意のバージョン)の代替である」と宣言している、という意味だと分かります。
自分自身がこれらのパッケージの任意のバージョンの代替である、ということは、結果的に 間接的な依存にこれらのパッケージが含まれていても、それらは一切インストールする必要はない(だって自分自身がそれらの代替なのだから)ということをComposerに伝えていることになります。
ここで改めて require
プロパティの中身を見てみましょう。
"require": {
"php": ">=8.2",
"ext-ctype": "*",
"ext-iconv": "*",
// 略
},
PHPのバージョン8.2以上、ctype拡張、iconv拡張を 物理的に要求しています。
ということは、PHPのバージョン8.2以下の機能やctype拡張やiconv拡張の機能を補完してくれるpolyfillパッケージはインストールする必要がない ですよね(だってそれらはpolyfillしてもらうまでもなく確実にミドルウェアとして存在しているはずだから)。
つまり、この replace
プロパティの意図は、間接的な依存によって無駄なパッケージがインストールされないようにしているだけ ということです(だと思います)。
scripts
scripts
プロパティには任意のスクリプトを定義することができます。Symfonyアプリケーションのデフォルトは以下の内容になっています。
"scripts": {
"auto-scripts": {
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd"
},
"post-install-cmd": [
"@auto-scripts"
],
"post-update-cmd": [
"@auto-scripts"
]
},
post-install-cmd
と post-update-cmd
は特殊なキーで、composer install
および composer update
コマンドが実行された直後に Composerが発行するイベント をフックしてスクリプトが実行されることを意味しています。
上記では、post-install-cmd
と post-update-cmd
の両方のタイミングで @auto-scripts
を実行させています。@auto-scripts
は、「auto-scripts
という名前で定義済みのスクリプトを実行する」という意味 です。
auto-scripts
の定義内容は
{
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd"
}
となっていて、何やら見慣れない形をしています。
実は、auto-scripts
という名前で定義されたスクリプトは、Symfony Flex によって特別に扱われます。Symfony Flexのソースコード を覗いてみると分かりますが、"{bin/consoleのサブコマンド名}": "symfony-cmd"
と書くことで bin/console
のサブコマンドを実行させられるということのようです。
ちなみに、(Symfony Flexのソースコード から分かるとおり、)auto-scripts
に通常のシェルスクリプトを追加したい場合は、以下のように書けばOKです。
{
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd",
"echo hello": "script"
}
conflict
conflict
プロパティは、このパッケージと競合する(ため、同時にインストールすることができない)パッケージを指定するためのものです。
Symfonyアプリケーションのデフォルトでは "symfony/symfony": "*"
が指定されています。つまり、symfony/skeleton
で作成したSymfonyアプリケーションにおいては、symfony/symfony
をインストールすることはできないようになっているということです。symfony/symfony
に直接依存せず、必要なSymfonyコンポーネントを個別にインストールしましょうということでしょう。
extra.symfony.allow-contrib
extra.symfony.require
extra
プロパティは、scripts
プロパティで定義したスクリプト(や、Comoserプラグインによる処理)に使わせるための任意の追加データを記述するためのものです。
Symfonyアプリケーションのデフォルトで定義されている extra.symfony.allow-contrib
と extra.symfony.require
はいずれもSymfony Flexによって使われています。
allow-contrib
は、Symfony Contrib Recipesリポジトリ のレシピの利用を有効化するかどうかのフラグです。デフォルトでは false
ですが、これを true
にすることでSymfony Contrib Recipesリポジトリのレシピが利用可能になります。
require
は、すべての symfony/*
パッケージのバージョンをロックするためのものです。今回はSymfony 7.0をインストールしたため、"require": "7.0.*"
となっています。この指定の効果を理解するために、以下のような状況を想像してみましょう。
- Symfony 7.1がリリースされている
-
require
配下に"symfony/form": "^7.0"
のような指定が追加されている
この状態で composer update
を実行すると、通常なら symfony/form
については7.1系がインストールされるはずです。しかし、extra.symfony.require: "7.0.*"
が指定されているため、実際には7.0系の最新がインストールされます。
extra.symfony.require
に適合しないバージョンはSymfony Flexにより除外されるため、Composerの動作が高速になり、メモリ消費を抑えられるという効果があるそうです。
おわりに
というわけで、symfony/skeleton
パッケージでSymfonyをインストールしたときのデフォルトの composer.json
の内容を、一つひとつ意味を確認しながら読んでみました。
Symfony(とComposer)に対する理解が少し深まりましたね!
Symfony Advent Calendar 2023、明日は空きです🥺どなたかぜひご参加ください!
Discussion