Composerを深ぼってみた

現在PHPを始めてから約1年半になろうとしている大学生です。

本日はPHPを用いた開発では欠かせないComposerに焦点を当てて記事を書きたいなと思います。

ComposerはPHPやLaravelの使用者だったら誰もが使用しているツールかと思います。
ただ、あまり内部構造やcomposer install, composer requireとかの基本的な使い方以外の用法を使ったことがなかったので、今回は色々深ぼって調べていきたいなと思います。

## 想定読者

- PHP, Laravel等の使用者
- Composerの内部構造を知りたい
- Composerの便利な使い方を知りたい など

基本的な使い方がわかっていて、構造に興味を持っている方に良い情報を届けられたらなと思っています。

## Composerの基本おさらい

ComposerはPHPのパッケージ管理ツールという位置付けで存在しています。

パッケージとは標準で用意されているもの、例えばfor文や組み込み関数のなど、意外で使う便利な道具といったところだと認識していただければと思います。
EX) Guzzle, PHPUnit, Swagger等

composer.jsonというファイルに以下のようにして書き込むことで、インストールしたいパッケージを定義できます。
定義した状態で、composer installを実行すると、composer.lock・vendorフォルダが用意され、実際にパッケージを自分のプロジェクトで使うことができるようになります。

以下はLaravelを使った際のcomposer.jsonの内容です。

**composer.json** (色々省略しています)
```json
{
    "name": "laravel/laravel",
    "type": "project",
    "description": "The Laravel Framework.",
    "keywords": [
        "framework",
        "laravel"
    ],
    "license": "MIT",
    "require": {
        "php": "^8.1",
    },
    "require-dev": {
    	"fakerphp/faker": "^1.9.1",
    },
    "autoload": {
        "psr-4": {
        }
    },
    "autoload-dev": {
        "psr-4": {
        }
    },
    "scripts": {
    },
}
```

composer installを実行するとcomposer.lockが生成されます。

こちらにはより詳細な情報、例えばバージョン情報などが記載されていて、2回目以降のcomposer installの実行ではcomposer.lockをもとに実行されます。

一般的に、composer.lockファイルをチームで共有することで同じ状態をチーム内で作ることができます。

## composer installコマンドの実行時に起きていること

composerコマンドの実行時の挙動を詳しくみていくために、composer install実行時に生成される実ファイルたちが格納されているVendorフォルダ内を見ていきます。

composer.jsonに記載してインストールしたFakerライブラリを今回は追っていきます。

**vendor/fakerphp/faker**
![vendor-faker.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3470041/8981a39b-a96a-69af-f8b9-38fbdd1da28a.png)

fakerのfolderの中にcomposer.jsonを発見しました。
中身はこんな感じです。

**vendor/fakerphp/faker/composer.json (一部省略)**
```json
{
    "name": "fakerphp/faker",
    "type": "library",
    "description": "Faker is a PHP library that generates fake data for you.",
    "keywords": [
        "faker",
        "fixtures",
        "data"
    ],
    "license": "MIT",
    "authors": [
        {
            "name": "François Zaninotto"
        }
    ],
    "require": {
        "php": "^7.4 || ^8.0",
        "psr/container": "^1.0 || ^2.0",
        "symfony/deprecation-contracts": "^2.2 || ^3.0"
    },
    "require-dev": {
        "ext-intl": "*",
        "bamarni/composer-bin-plugin": "^1.4.1",
        "doctrine/persistence": "^1.3 || ^2.0",
        "phpunit/phpunit": "^9.5.26",
        "symfony/phpunit-bridge": "^5.4.16"
    },
}
```

何やらfakerのライブラリでも他のライブラリを使用しているみたいです。

ただ、fakerフォルダの中にはcomposer.lockもありませんし、vendorフォルダもありません。

このcomposer.jsonには依存先が書いてありますが、フォルダ内に実態はないみたいですね。

そこで大元のvendorフォルダを見てみます。すると、、、、、

![psr.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3470041/4038f19a-7161-1c27-6e41-8a157aee619e.png)

vendor/psr/containerというフォルダが存在しました！！！

プロジェクトのcomposer.jsonでは指定していないのに、fakerが使っているため、自動的に依存関係を解決してインストールしているみたいですね。
プロジェクトのcomposer.jsonでは指定していませんでしたが、composer.lockを調べてみると。。

**composer.lock**
```json
"psr/container": "^1.1.1|^2.0.1",
```
しっかり情報が記載されていました！

これまでの挙動をまとめてみると >>>
- composer.jsonにプロジェクトの必要なライブラリを記載する
- 初回composer install時にはcomposer.jsonを参照し、必要パッケージをインストールする
- 上記を実行する際に、インストールしたいパッケージが使用している別のパッケージをcomposer.jsonから識別子、インストールする
- 上の繰り返し
- インストールしたすべての情報はcomposer.lockに格納し、2回目以降の実行ではこちらを参照

コマンド実行時は上記のような流れになっているみたいですね。

これがcomposer.lockには「より詳細な情報が書かれている」と言われている所以だったのだなと理解できるかなとます。


## オートロード

これまでのセクションで、composer installが実行されたタイミングで起こることを紐解いていくことで、パッケージの依存関係解消や管理に関する働きを見ることができました。このセクションではcomposerのもう一つの役割であるautoloadというものについて見ていきます。

### オートロードとは

オートロードとはその名前からもわかるように、ファイルを自動的にロードしてくれる機能のことを言います。

PHPの場合、他のファイルに定義している関数やクラスを使用するには以下のように使用したいファイルの読み込みが必要になります。

最初にオートロードを使用した場合とそうでない場合を見比べてみます。


**case: app/directory1/A.phpでB.phpに定義してあるクラスを使用して、クラスBのechoというfunctionを実行する。**

```tex
composer.json
A.php <= クラスBを呼び出す
src
  /directory
   /B.php
```

- **オートロード使用なしver**

A.php

```php
<?php

require_once "src/directory2/B.php"

$b = new classB();
$b->echo(); // 呼び出し完了
```

B.php

```php
<?php

class B {
	function echo() {
		echo "呼び出し完了";
	}
}
```

- **オートロード使用 ver**

composer.json

```json
{
    "autoload": {
        "psr-4": {
            "app\\": "src/"
        }
    }
}
```

A.php

```php
<?php

require_once "vendor/autoload.php";

$b = new app\directory\B();
$b->hello(); // 呼び出し完了
```

B.php

```php
<?php

namespace app\directory;

class B {
	function echo() {
		echo "呼び出し完了";
	}
}
```

異なる点は3点あります。

1️⃣ composerにautoloadに関する設定をする。
2️⃣ classB にnamespaceをつける。
3️⃣ autoloadを使用する際に、classAでは使用するクラスのファイルではなく、vendor/autoload.phpを読み込む。

### namespace & composer.jsonの設定

autoloadを使用した例ではclassBにnamespaceというものを以下のようにして定義していました。

```php
<?php

namespace app\directory;

class B {
	function echo() {
		echo "呼び出し完了";
	}
}
```

これはPHPの機能で同じ名前の関数やクラス名を区別するために設定されます。

例えばclassAからclassBと/directory/classBというものを呼び出したい場合、namespaceを使わなかったらその二つの区別がつかず、うまく動作しなくなってしまいます。

そこで、namespace(ディレクトリの情報)を含めることにより、同じ名前でも区別をすることが可能になります。

composer.jsonにあった以下の記述について

```json
{
    "autoload": {
        "psr-4": {
            "app\\": "src/"
        }
    }
}
```

これはvendor/autoload.phpを遡るとわかることなのですが、namespaceとファイルパスの紐付けを行うために必要な記述です。

Laravelのファイルを用いて説明します。

composer.json

```json
"autoload": {
    "psr-4": {
        "App\\": "app/",
        "Database\\Factories\\": "database/factories/",
        "Database\\Seeders\\": "database/seeders/"
    }
},
```

autoload内ではnamespaceとファイルパスの紐付けをしています。
この場合、app配下のファイルたちのnamespaceはAppから始まります。
database/factories配下のファイルたちのnamespaceはDatabase\Factoriesから始まります。

**vendor/composer/autoload_classmap.php**
```php
<?php

// autoload_classmap.php @generated by Composer

$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);

return array(
		'App\\Console\\Kernel' => $baseDir . '/app/Console/Kernel.php',
		// 省略
		'Faker\\Calculator\\Ean' => $vendorDir . '/fakerphp/faker/src/Faker/Calculator/Ean.php',
        'Faker\\Calculator\\Iban' => $vendorDir . '/fakerphp/faker/src/Faker/Calculator/Iban.php',
		// 省略
);
```

compsoer.jsonに書かれているautoloadの設定を元に、composer.lock内にあるパッケージのファイルパスをnamespaceにマッピングしていることがわかります。

composer.jsonはそれぞれのパッケージにあるので、それぞれのパッケージの設定によってnamespaceが割り当てられています。

### autoloadで解消できる事

- ファイルを大量に読み込まないでよくなる
    - autoloaderファイルをrequireするだけでOK
    - ⇒ autoloadがなければめっちゃrequire文を書くことになります。(autoload_classmap.phpを見ればわかります。)


## 普段使わない便利なコマンド

これまでは仕組み的なところを見ていきましたが、
私は日頃、composerコマンドに関しては基礎的なものしか使っておりません。
それで困ることもあまりありませんが、今回はcomposerのあまり使わないけど便利なコマンドについて紹介します。

### composer show [パッケージ]

とにかくパッケージの情報をたくさん出してくれます。
![composer_show.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3470041/a24e1145-1da4-7e14-b4eb-304a04356d22.png)

### composer browse -show [パッケージ]

リポジトリのみ知りたい場合はこのコマンドを使います。
![composer_browse.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3470041/ba79ae1d-5cf6-7bb2-5ca2-89f4492d0bc0.png)


### composer suggests [パッケージ]

関連するパッケージ等を表示してくれます。
![composer_suggests.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3470041/f240ba8b-8d33-9b96-50ad-7a8940f34b86.png)


## 終わりに

Composerについてはなんとなく使っていましたが、今回調べてみてだいぶへーって思うことが多かったです。

開発のパフォーマンスが向上するかは定かではないですが、身近な技術を深ぼるのは面白いなと感じました。

PHPカンファレンスの記事を主に参考にしました。
今回はcomposer関連を見ましたが、他も魅力的でした。
行ってみたい。。

## 参考

- https://speakerdeck.com/r_eiyama/composernoliang-itokorowobao-menibao-memakurult
- https://speakerdeck.com/kerokero/phpconjapan2023-watasi-composergaqi-ninarimasu
- https://qiita.com/atwata/items/5ba72d3d881a81227c2a