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

# De interne structuur van package discovery

> We lezen de interne implementatie van de PackageManifest-klasse en leggen uit hoe extra.laravel uit composer.json wordt gecachet en als bootstrap/cache/packages.php wordt ingeladen.

## Over deze pagina

In [De basis van packageontwikkeling](/nl/advanced/package-development) zagen we dat service providers en facades automatisch worden geregistreerd zodra je de sectie `extra.laravel` in `composer.json` opneemt. Deze pagina legt de achterkant daarvan uit: hoe de klasse `Illuminate\Foundation\PackageManifest` werkt, op broncodeniveau.

<Info>
  Deze pagina is een zusterpagina van [De basis van packageontwikkeling](/nl/advanced/package-development). We raden je aan eerst het basisgebruik van automatische detectie te lezen.
</Info>

## Het totaalbeeld van automatische detectie

```mermaid theme={null}
flowchart TD
    A["composer install / update"] --> B["Composer vuurt het<br>post-autoload-dump-event af"]
    B --> C["Illuminate\\Foundation\\ComposerScripts::postAutoloadDump()"]
    C --> D["Cachebestanden zoals<br>bootstrap/cache/packages.php verwijderen"]
    D --> E["Bij de volgende start wordt PackageManifest::build() uitgevoerd"]
    E --> F["vendor/composer/installed.json inlezen"]
    F --> G["extra.laravel van elk package verzamelen"]
    G --> H["Uitsluiten op basis van dont-discover"]
    H --> I["Wegschrijven naar bootstrap/cache/packages.php"]
    I --> J["Application leest providers()/aliases()<br>en registreert de service providers"]
```

## De `PackageManifest`-klasse

De kern van de automatische detectie is `Illuminate\Foundation\PackageManifest`. Hieronder staat de implementatie (samengevat) zoals in framework 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)
            : [];
    }
}
```

Er zijn drie belangrijke punten:

* **Het manifest wordt na één keer inlezen in het geheugen gecachet** (de property `$this->manifest`). Ook als je `providers()` binnen één request meerdere keren aanroept, is er maar één keer bestands-I/O.
* **`build()` wordt alleen uitgevoerd als het manifestbestand niet bestaat**. In normaal gebruik wordt het dus niet elke keer opnieuw opgebouwd.
* Het bestand zelf is een simpel PHP-bestand dat een array `return`t (`bootstrap/cache/packages.php`) — het snelst mogelijke formaat, dat je met alleen een `require` kunt inladen.

## Het buildproces van het manifest

De `build()`-methode is het deel dat daadwerkelijk de informatie uit `composer.json` samenvoegt.

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

Het belangrijkste punt is dat niet rechtstreeks `composer.json` wordt geparset, maar dat **`vendor/composer/installed.json`** wordt gelezen. Dit is het metadatabestand van alle geïnstalleerde packages dat Composer genereert bij `composer install` / `composer update`. Omdat de `extra`-secties uit de `composer.json` van elk package hier zijn samengebracht, leest Laravel alleen informatie die onder beheer van Composer staat.

<Info>
  `installed.json` staat normaal gesproken in `.gitignore`, dus bij de eerste setup (zonder `vendor/`) werkt de automatische detectie niet. Pas na afronding van `composer install` wordt de cache voor het eerst opgebouwd.
</Info>

### De twee manieren om `dont-discover` te schrijven

`dont-discover` kun je zowel in de `composer.json` van het package als in die van de applicatie zetten, maar de betekenis verschilt.

```json title="composer.json aan de packagekant" theme={null}
"extra": {
    "laravel": {
        "providers": ["Acme\\Courier\\CourierServiceProvider"],
        "dont-discover": []
    }
}
```

```json title="composer.json aan de applicatiekant" theme={null}
"extra": {
    "laravel": {
        "dont-discover": [
            "acme/courier"
        ]
    }
}
```

Als je de implementatie van `build()` bekijkt, wordt de `$ignore`-array opgebouwd door de `configuration['dont-discover']` van elk package via `array_merge` te verzamelen. Dat betekent dat een package technisch gezien ook "de automatische detectie van zijn eigen dependencies kan uitschakelen" (bijvoorbeeld als je niet wilt dat de provider van een intern gebruikt subpackage dubbel wordt geregistreerd). In de praktijk gebruik je meestal de uitschakeling aan de applicatiekant.

### `*` opgeven bij `dont-discover`

Als de array die `packagesToIgnore()` teruggeeft `*` bevat, wordt `$ignoreAll = true` en wordt **de automatische detectie van alle packages volledig uitgeschakeld**. Dit gebruik je als je de overhead van automatische detectie wilt vermijden in CI- of testomgevingen, of als je alles volledig handmatig wilt beheren via `bootstrap/providers.php`.

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

## Wat er in het cachebestand staat

`getCachedPackagesPath()` geeft de waarde van de environment variable `APP_PACKAGES_CACHE` terug als die bestaat, en anders `bootstrap/cache/packages.php`.

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

Als je dit bestand direct opent, zie je dat het alleen een eenvoudige associatieve array `return`t.

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

Omdat de packagenaam de sleutel is, kun je in de uitvoer van `php artisan package:discover` controleren "welke packages er zijn gedetecteerd".

## Wanneer de cache opnieuw wordt opgebouwd

`Illuminate\Foundation\ComposerScripts` haakt in op drie Composer-events, die allemaal dezelfde `clearCompiled()` aanroepen.

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

Met andere woorden: bij het uitvoeren van `composer install`, `composer update` of `composer dump-autoload` worden de configcache, servicecache en packagecache allemaal in één keer verwijderd. Bij de eerstvolgende start van Laravel draait `PackageManifest::build()` en wordt alles opnieuw opgebouwd vanuit de actuele staat van `installed.json`.

Dit is het standaardgedrag van een Laravel-project, geregistreerd in de `scripts` van `composer.json`.

```json title="composer.json van een Laravel-app (fragment)" 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"
    ]
}
```

## Handmatig opnieuw opbouwen met `php artisan package:discover`

Als de cache verouderd is, of als je `vendor/` rechtstreeks hebt gewijzigd zonder Composer te gebruiken, kun je met het `package:discover`-commando handmatig opnieuw opbouwen.

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

Dit commando is in feite een heel dunne 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());
    }
}
```

Het roept alleen `$manifest->build()` aan en toont het resultaat; commandospecifieke logica is er nauwelijks. In situaties waarin Composer-events niet worden afgevuurd — bijvoorbeeld als je in een CI-pipeline `composer install --no-scripts` gebruikt — moet je dit commando expliciet aanroepen.

<Warning>
  Als je [Orchestra Testbench](/nl/advanced/package-testing) gebruikt voor packagetests: Testbench biedt een eigen `vendor/bin/testbench package:discover`-commando. Zie [Packages testen met Testbench](/nl/advanced/package-testing) voor de details. Het is iets anders dan het `package:discover` van Artisan en bouwt het manifest voor de Testbench-specifieke skeleton-app.
</Warning>

## Aandachtspunten bij deployen

Bij een productiedeploy voer je meestal `composer install --no-dev --optimize-autoloader` uit, maar als je `--no-scripts` toevoegt, wordt de packagecache niet bijgewerkt. Het is veiliger om een expliciete herbouwstap in je deployscript op te nemen.

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

Ook de volgorde is belangrijk. Voer `package:discover` uit vóór `config:cache`. Als de configcache eerst wordt aangemaakt, kan later toegevoegde packageconfiguratie (zoals configuratie die via `mergeConfigFrom` wordt geregistreerd) niet meer worden meegenomen.

## Samenvatting

* Automatische detectie werkt niet op basis van de statische inhoud van `composer.json`, maar door het door Composer gegenereerde `vendor/composer/installed.json` te lezen.
* Het resultaat wordt als een platte PHP-array gecachet in `bootstrap/cache/packages.php` en supersnel ingeladen met alleen een `require`.
* De cache wordt bij elk van de events `composer install/update/dump-autoload` automatisch verwijderd en bij de volgende start opnieuw opgebouwd.
* In omgevingen waarin je Composer met `--no-scripts` draait, moet je `php artisan package:discover` expliciet aanroepen.
* Met `*` bij `dont-discover` schakel je de volledige automatische detectie uit.

## Gerelateerde pagina's

* [De basis van packageontwikkeling](/nl/advanced/package-development) — het basisgebruik van service providers en `extra.laravel`
* [Deferred service providers](/nl/advanced/deferred-provider) — het laadmoment van gedetecteerde providers optimaliseren
* [Packages testen met Orchestra Testbench](/nl/advanced/package-testing) — het Testbench-specifieke `package:discover`-commando


## Related topics

- [Laravel-packages ontwikkelen](/nl/advanced/package-development.md)
- [Migratiegids van oude naar nieuwe structuur](/nl/advanced/app-structure-migration.md)
- [BlueskyManager en HasShortHand](/nl/packages/laravel-bluesky/bluesky-manager.md)
- [FAQ over de nieuwe appstructuur van Laravel 11+](/nl/advanced/app-structure-faq.md)
- [Een custom authenticatieguard implementeren](/nl/advanced/custom-auth-guard.md)
