> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# パッケージ自動検出の内部構造

> PackageManifestクラスの内部実装を読み解き、composer.jsonのextra.laravelがどのようにキャッシュされ、bootstrap/cache/packages.phpとして読み込まれるのかを解説します。

## このページについて

[パッケージ開発の基礎](/jp/advanced/package-development)では `composer.json` の `extra.laravel` セクションを書けばサービスプロバイダーとファサードが自動登録されることを紹介しました。このページではその裏側、`Illuminate\Foundation\PackageManifest` クラスがどのように動作しているかをソースコードレベルで解説します。

<Info>
  このページは[パッケージ開発の基礎](/jp/advanced/package-development)の姉妹ページです。自動検出の基本的な使い方を先に読むことをおすすめします。
</Info>

## 自動検出の仕組み全体像

```mermaid theme={null}
flowchart TD
    A["composer install / update"] --> B["Composerが post-autoload-dump<br>イベントを発火"]
    B --> C["Illuminate\\Foundation\\ComposerScripts::postAutoloadDump()"]
    C --> D["bootstrap/cache/packages.phpなどの<br>キャッシュファイルを削除"]
    D --> E["次回起動時にPackageManifest::build()が実行される"]
    E --> F["vendor/composer/installed.jsonを読み取り"]
    F --> G["各パッケージのextra.laravelを収集"]
    G --> H["dont-discoverでの除外処理"]
    H --> I["bootstrap/cache/packages.phpに書き込み"]
    I --> J["Applicationがproviders()/aliases()を<br>読み取ってサービスプロバイダー登録"]
```

## `PackageManifest` クラス

自動検出の中核は `Illuminate\Foundation\PackageManifest` です。以下がフレームワーク13.x時点の実装（要約）です。

```php theme={null}
class PackageManifest
{
    public function providers()
    {
        return $this->config('providers');
    }

    public function aliases()
    {
        return $this->config('aliases');
    }

    public function config($key)
    {
        return (new Collection($this->getManifest()))
            ->flatMap(fn ($configuration) => (array) ($configuration[$key] ?? []))
            ->filter()
            ->all();
    }

    protected function getManifest()
    {
        if (! is_null($this->manifest)) {
            return $this->manifest;
        }

        if (! is_file($this->manifestPath)) {
            $this->build();
        }

        return $this->manifest = is_file($this->manifestPath)
            ? $this->files->getRequire($this->manifestPath)
            : [];
    }
}
```

ポイントは次の3つです。

* **マニフェストは一度読み込むとメモリにキャッシュされる**（`$this->manifest` プロパティ）。1リクエスト内で複数回 `providers()` を呼んでもファイルI/Oは1回だけ。
* **マニフェストファイルが存在しない場合のみ `build()` が実行される**。通常運用では毎回ビルドされるわけではない。
* 実体は素朴なPHP配列を `return` するファイル（`bootstrap/cache/packages.php`）で、`require` するだけで読み込める最速の形式。

## マニフェストのビルド処理

`build()` メソッドが実際に `composer.json` の情報を集約する部分です。

```php theme={null}
public function build()
{
    $packages = [];

    if ($this->files->exists($path = $this->vendorPath.'/composer/installed.json')) {
        $installed = json_decode($this->files->get($path), true);
        $packages = $installed['packages'] ?? $installed;
    }

    $ignoreAll = in_array('*', $ignore = $this->packagesToIgnore());

    $this->write((new Collection($packages))->mapWithKeys(function ($package) {
        return [$this->format($package['name']) => $package['extra']['laravel'] ?? []];
    })->each(function ($configuration) use (&$ignore) {
        $ignore = array_merge($ignore, $configuration['dont-discover'] ?? []);
    })->reject(function ($configuration, $package) use ($ignore, $ignoreAll) {
        return $ignoreAll || in_array($package, $ignore);
    })->filter()->all());
}
```

重要な点は、`composer.json` を直接パースするのではなく **`vendor/composer/installed.json`** を読んでいることです。これはComposerが `composer install` / `composer update` 実行時に生成する、インストール済み全パッケージのメタデータファイルです。各パッケージの `composer.json` に書かれた `extra` セクションがここに集約されているため、Laravel側はComposerの管理下にある情報だけを信頼して読み取ります。

<Info>
  `installed.json` は通常 `.gitignore` されているため、初回セットアップ時（`vendor/` がない状態）では自動検出は機能しません。`composer install` の完了後に初めてキャッシュが構築されます。
</Info>

### `dont-discover` の2つの書き方

`dont-discover` はパッケージ側・アプリケーション側どちらの `composer.json` にも書けますが、意味が異なります。

```json title="パッケージ側のcomposer.json" theme={null}
"extra": {
    "laravel": {
        "providers": ["Acme\\Courier\\CourierServiceProvider"],
        "dont-discover": []
    }
}
```

```json title="アプリケーション側のcomposer.json" theme={null}
"extra": {
    "laravel": {
        "dont-discover": [
            "acme/courier"
        ]
    }
}
```

`build()` の実装を見ると、`$ignore` 配列は各パッケージの `configuration['dont-discover']` を `array_merge` して蓄積しています。つまりパッケージ自身が「自分の依存パッケージの自動検出を無効化する」ことも技術的には可能です（例：内部で使っているサブパッケージのプロバイダーを重複登録させたくない場合）。ただし実務でよく使うのはアプリケーション側での無効化です。

### `dont-discover` に `*` を指定する

`packagesToIgnore()` が返す配列に `*` が含まれていると `$ignoreAll = true` となり、**すべてのパッケージの自動検出を丸ごと無効化**します。CIやテスト環境で自動検出のオーバーヘッドを避けたい場合や、`bootstrap/providers.php` で完全に手動管理したい場合に使います。

```json theme={null}
"extra": {
    "laravel": {
        "dont-discover": ["*"]
    }
}
```

## キャッシュファイルの実体

`getCachedPackagesPath()` は環境変数 `APP_PACKAGES_CACHE` があればそれを、なければ `bootstrap/cache/packages.php` を返します。

```php theme={null}
public function getCachedPackagesPath()
{
    return $this->normalizeCachePath('APP_PACKAGES_CACHE', 'cache/packages.php');
}
```

このファイルを直接開くと、単純な連想配列を `return` しているだけだとわかります。

```php theme={null}
<?php return array (
  'acme/courier' => 
  array (
    'providers' => 
    array (
      0 => 'Acme\\Courier\\CourierServiceProvider',
    ),
  ),
);
```

パッケージ名がキーになっているため、`php artisan package:discover` の出力で「どのパッケージが検出されたか」を確認できます。

## キャッシュが再構築されるタイミング

`Illuminate\Foundation\ComposerScripts` は3つのComposerイベントにフックしており、いずれも同じ `clearCompiled()` を呼びます。

```php theme={null}
public static function postInstall(Event $event)
{
    require_once $event->getComposer()->getConfig()->get('vendor-dir').'/autoload.php';
    static::clearCompiled();
}

protected static function clearCompiled()
{
    $laravel = new Application(getcwd());

    if (is_file($configPath = $laravel->getCachedConfigPath())) {
        @unlink($configPath);
    }

    if (is_file($servicesPath = $laravel->getCachedServicesPath())) {
        @unlink($servicesPath);
    }

    if (is_file($packagesPath = $laravel->getCachedPackagesPath())) {
        @unlink($packagesPath);
    }
}
```

つまり `composer install` / `composer update` / `composer dump-autoload` のいずれを実行しても、設定キャッシュ・サービスキャッシュ・パッケージキャッシュがまとめて削除されます。次にLaravelが起動したタイミングで `PackageManifest::build()` が走り、`installed.json` の最新状態から再構築されます。

これは `composer.json` の `scripts` に登録されている、標準的なLaravelプロジェクトの挙動です。

```json title="Laravelアプリのcomposer.json（抜粋）" theme={null}
"scripts": {
    "post-autoload-dump": [
        "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump"
    ],
    "post-update-cmd": [
        "@php artisan vendor:publish --tag=laravel-assets --ansi --force"
    ],
    "post-root-package-install": [
        "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
    ],
    "post-create-project-cmd": [
        "@php artisan key:generate --ansi"
    ]
}
```

## `php artisan package:discover` で手動再構築

キャッシュが古いままになっている、あるいはComposerを経由せずに `vendor/` を直接変更したような場合は、`package:discover` コマンドで手動再構築できます。

```shell theme={null}
php artisan package:discover
```

このコマンドの実体は非常に薄いラッパーです。

```php theme={null}
#[AsCommand(name: 'package:discover')]
class PackageDiscoverCommand extends Command
{
    public function handle(PackageManifest $manifest)
    {
        $this->components->info('Discovering packages');

        $manifest->build();

        (new Collection($manifest->manifest))
            ->keys()
            ->each(fn ($description) => $this->components->task($description))
            ->whenNotEmpty(fn () => $this->newLine());
    }
}
```

`$manifest->build()` を呼んで結果を出力するだけで、コマンド固有のロジックはほぼありません。CIパイプラインで `composer install --no-scripts` を使っている場合など、Composerイベントが発火しない状況ではこのコマンドを明示的に呼ぶ必要があります。

<Warning>
  パッケージのテストで [Orchestra Testbench](/jp/advanced/package-testing) を使う場合、Testbenchは独自の `vendor/bin/testbench package:discover` コマンドを提供しています。詳細は[Testbenchでのパッケージテスト](/jp/advanced/package-testing)を参照してください。Artisanの `package:discover` とは別物で、Testbench専用のスケルトンアプリ向けにマニフェストを構築します。
</Warning>

## デプロイ時の注意点

本番デプロイでは `composer install --no-dev --optimize-autoloader` を実行するのが一般的ですが、`--no-scripts` を付けている場合はパッケージキャッシュが更新されません。デプロイスクリプトに明示的な再構築ステップを入れておくと安全です。

```shell theme={null}
composer install --no-dev --optimize-autoloader --no-scripts
php artisan package:discover --ansi
php artisan config:cache
php artisan route:cache
```

順序も重要です。`package:discover` は `config:cache` より前に実行してください。設定キャッシュが先に作られると、後から追加されたパッケージ設定（`mergeConfigFrom` で登録される設定など）が反映されない場合があります。

## まとめ

* 自動検出は `composer.json` の静的な内容ではなく、Composerが生成する `vendor/composer/installed.json` を読み取って動作する。
* 結果は `bootstrap/cache/packages.php` にプレーンなPHP配列としてキャッシュされ、`require` だけで高速に読み込まれる。
* キャッシュは `composer install/update/dump-autoload` の各イベントで自動的に削除され、次回起動時に再構築される。
* `--no-scripts` でComposerを実行する環境では `php artisan package:discover` を明示的に呼ぶ必要がある。
* `dont-discover` に `*` を指定すると自動検出全体を無効化できる。

## 関連ページ

* [パッケージ開発の基礎](/jp/advanced/package-development) — サービスプロバイダーと `extra.laravel` の基本的な使い方
* [遅延サービスプロバイダー](/jp/advanced/deferred-provider) — 検出されたプロバイダーの読み込みタイミングを最適化する
* [Orchestra Testbenchでのパッケージテスト](/jp/advanced/package-testing) — Testbench独自の `package:discover` コマンド


## Related topics

- [Laravelパッケージ開発](/jp/advanced/package-development.md)
- [Laravel 11以降のアプリケーション構造](/jp/advanced/app-structure.md)
- [TextBuilder](/jp/packages/laravel-bluesky/text-builder.md)
- [Laravel MCP](/jp/mcp.md)
- [Engine Manifest](/jp/packages/laravel-voicevox/engine-api/その他/engine-manifest.md)
