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

# Fonctionnement interne de la découverte automatique des packages

> Décryptage de l'implémentation de la classe PackageManifest : comment extra.laravel du composer.json est mis en cache et chargé sous forme de bootstrap/cache/packages.php.

## À propos de cette page

Dans [Développement de packages Laravel](/fr/advanced/package-development), nous avons vu qu'il suffit de renseigner la section `extra.laravel` du `composer.json` pour que les service providers et les façades soient enregistrés automatiquement. Cette page en explique les coulisses, en décryptant au niveau du code source le fonctionnement de la classe `Illuminate\Foundation\PackageManifest`.

<Info>
  Cette page complète [Développement de packages Laravel](/fr/advanced/package-development). Nous vous recommandons de lire d'abord l'utilisation de base de la découverte automatique.
</Info>

## Vue d'ensemble du mécanisme de découverte automatique

```mermaid theme={null}
flowchart TD
    A["composer install / update"] --> B["Composer déclenche l'événement<br>post-autoload-dump"]
    B --> C["Illuminate\\Foundation\\ComposerScripts::postAutoloadDump()"]
    C --> D["Suppression des fichiers de cache<br>comme bootstrap/cache/packages.php"]
    D --> E["Au prochain démarrage, PackageManifest::build() s'exécute"]
    E --> F["Lecture de vendor/composer/installed.json"]
    F --> G["Collecte de extra.laravel de chaque package"]
    G --> H["Exclusions via dont-discover"]
    H --> I["Écriture dans bootstrap/cache/packages.php"]
    I --> J["L'Application lit providers()/aliases()<br>et enregistre les service providers"]
```

## La classe `PackageManifest`

Le cœur de la découverte automatique est `Illuminate\Foundation\PackageManifest`. Voici (en résumé) l'implémentation dans le framework en version 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)
            : [];
    }
}
```

Trois points à retenir :

* **Le manifeste est mis en cache en mémoire dès sa première lecture** (propriété `$this->manifest`). Même si `providers()` est appelée plusieurs fois dans la même requête, l'accès disque n'a lieu qu'une seule fois.
* **`build()` n'est exécuté que si le fichier manifeste n'existe pas.** En fonctionnement normal, il n'est pas reconstruit à chaque requête.
* Le fichier lui-même n'est qu'un simple tableau PHP retourné via `return` (`bootstrap/cache/packages.php`), la forme la plus rapide qui peut être chargée par un simple `require`.

## Processus de construction du manifeste

C'est la méthode `build()` qui agrège les informations issues des `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());
}
```

Le point important est que Laravel ne lit pas directement le `composer.json` : il consulte **`vendor/composer/installed.json`**. Ce fichier est généré par Composer lors de `composer install` / `composer update` et regroupe les métadonnées de tous les packages installés. Les sections `extra` déclarées dans le `composer.json` de chaque package y sont agrégées, si bien que Laravel ne se fie qu'aux informations sous la responsabilité de Composer.

<Info>
  Comme `installed.json` est en général ignoré par Git (`.gitignore`), la découverte automatique ne fonctionne pas lors du tout premier setup (quand `vendor/` n'existe pas). Le cache n'est construit qu'après un premier `composer install`.
</Info>

### Deux manières d'utiliser `dont-discover`

`dont-discover` peut être déclaré dans le `composer.json` du package ou dans celui de l'application, mais avec des significations différentes.

```json title="composer.json côté package" theme={null}
"extra": {
    "laravel": {
        "providers": ["Acme\\Courier\\CourierServiceProvider"],
        "dont-discover": []
    }
}
```

```json title="composer.json côté application" theme={null}
"extra": {
    "laravel": {
        "dont-discover": [
            "acme/courier"
        ]
    }
}
```

En observant l'implémentation de `build()`, on voit que le tableau `$ignore` est alimenté en cumulant, via `array_merge`, la valeur `configuration['dont-discover']` de chaque package. Un package peut donc techniquement « désactiver la découverte automatique de ses propres dépendances » (par exemple pour éviter la double inscription d'un provider de sous-package qu'il utilise en interne). En pratique, la désactivation est cependant surtout utilisée côté application.

### Utiliser `*` dans `dont-discover`

Si le tableau retourné par `packagesToIgnore()` contient `*`, `$ignoreAll` vaut `true` et **la découverte automatique est complètement désactivée pour tous les packages**. C'est utile en CI ou en environnement de test pour éviter le coût de la découverte, ou lorsque vous souhaitez tout gérer manuellement dans `bootstrap/providers.php`.

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

## Le fichier de cache

`getCachedPackagesPath()` renvoie la valeur de la variable d'environnement `APP_PACKAGES_CACHE` si elle est définie, sinon `bootstrap/cache/packages.php`.

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

Si vous ouvrez ce fichier, vous constaterez qu'il se contente de retourner un tableau associatif simple.

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

Comme les clés sont les noms de packages, la sortie de `php artisan package:discover` vous permet de vérifier « quels packages ont été détectés ».

## Quand le cache est-il reconstruit ?

`Illuminate\Foundation\ComposerScripts` s'abonne à trois événements Composer et appelle à chaque fois la même méthode `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);
    }
}
```

Autrement dit, quel que soit l'événement (`composer install` / `composer update` / `composer dump-autoload`), les caches de configuration, de services et de packages sont supprimés d'un seul coup. Au démarrage suivant de Laravel, `PackageManifest::build()` s'exécute et reconstruit tout à partir de la dernière version d'`installed.json`.

Ce comportement standard des projets Laravel est déclaré dans les `scripts` du `composer.json`.

```json title="composer.json d'une application Laravel (extrait)" 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"
    ]
}
```

## Reconstruction manuelle via `php artisan package:discover`

Si le cache est resté obsolète, ou si `vendor/` a été modifié sans passer par Composer, vous pouvez reconstruire manuellement le manifeste via la commande `package:discover`.

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

Cette commande n'est qu'un fin wrapper.

```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());
    }
}
```

Elle se limite à appeler `$manifest->build()` puis à afficher le résultat : il n'y a quasiment aucune logique propre. Dans un pipeline CI qui utilise `composer install --no-scripts` par exemple, les événements Composer ne sont pas déclenchés et il faut donc appeler explicitement cette commande.

<Warning>
  Si vous testez votre package avec [Orchestra Testbench](/fr/advanced/package-testing), sachez que Testbench fournit sa propre commande `vendor/bin/testbench package:discover`. Reportez-vous à [Tester un package avec Testbench](/fr/advanced/package-testing) pour plus de détails. Elle est distincte de `package:discover` d'Artisan et construit le manifeste pour l'application squelette dédiée à Testbench.
</Warning>

## Points d'attention lors du déploiement

En production, on exécute couramment `composer install --no-dev --optimize-autoloader`. Mais avec l'option `--no-scripts`, le cache des packages n'est pas mis à jour. Il est donc plus sûr d'ajouter une étape explicite de reconstruction à votre script de déploiement.

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

L'ordre compte : lancez `package:discover` **avant** `config:cache`. Si le cache de configuration est généré en premier, la configuration ajoutée ultérieurement par les packages (par exemple via `mergeConfigFrom`) risque de ne pas être prise en compte.

## Récapitulatif

* La découverte automatique s'appuie non pas sur le contenu statique du `composer.json`, mais sur `vendor/composer/installed.json` généré par Composer.
* Le résultat est mis en cache dans `bootstrap/cache/packages.php` sous forme de tableau PHP brut, lisible très rapidement via un simple `require`.
* Ce cache est automatiquement supprimé lors des événements `composer install/update/dump-autoload`, puis reconstruit au prochain démarrage.
* Dans les environnements qui appellent Composer avec `--no-scripts`, il faut invoquer `php artisan package:discover` explicitement.
* En plaçant `*` dans `dont-discover`, vous désactivez entièrement la découverte automatique.

## Pages associées

* [Développement de packages Laravel](/fr/advanced/package-development) — utilisation de base des service providers et de `extra.laravel`.
* [Deferred service providers](/fr/advanced/deferred-provider) — optimiser le moment de chargement des providers détectés.
* [Tester un package avec Orchestra Testbench](/fr/advanced/package-testing) — la commande `package:discover` spécifique à Testbench.


## Related topics

- [Développement de packages Laravel](/fr/advanced/package-development.md)
- [Blog](/fr/blog/index.md)
- [Gestion de la compatibilité de versions de package](/fr/advanced/package-versioning.md)
- [Développer un package avec Testbench Workbench](/fr/advanced/package-workbench.md)
- [Laravel Cloud Hibernation (mise en veille automatique)](/fr/blog/laravel-cloud-hibernation.md)
