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

# Interne Struktur der automatischen Paket-Erkennung

> Erläutert die interne Implementierung der PackageManifest-Klasse: wie extra.laravel aus der composer.json zwischengespeichert wird und wie die Datei bootstrap/cache/packages.php geladen wird.

## Über diese Seite

Unter [Grundlagen der Paketentwicklung](/de/advanced/package-development) haben wir vorgestellt, dass Service Provider und Facades automatisch registriert werden, sobald Sie den Abschnitt `extra.laravel` in der `composer.json` angeben. Auf dieser Seite erläutern wir die Interna dahinter, konkret die Funktionsweise der Klasse `Illuminate\Foundation\PackageManifest` auf Quellcode-Ebene.

<Info>
  Diese Seite ist die Ergänzung zu [Grundlagen der Paketentwicklung](/de/advanced/package-development). Wir empfehlen, die Grundlagen der automatischen Erkennung zuerst zu lesen.
</Info>

## Gesamtüberblick zur automatischen Erkennung

```mermaid theme={null}
flowchart TD
    A["composer install / update"] --> B["Composer löst das Event<br>post-autoload-dump aus"]
    B --> C["Illuminate\\Foundation\\ComposerScripts::postAutoloadDump()"]
    C --> D["Löscht Cache-Dateien wie<br>bootstrap/cache/packages.php"]
    D --> E["Beim nächsten Start wird PackageManifest::build() ausgeführt"]
    E --> F["Liest vendor/composer/installed.json"]
    F --> G["Sammelt extra.laravel aus jedem Paket"]
    G --> H["Ausschluss über dont-discover"]
    H --> I["Schreibt nach bootstrap/cache/packages.php"]
    I --> J["Application liest providers()/aliases() ein<br>und registriert die Service Provider"]
```

## Die Klasse `PackageManifest`

Der Kern der automatischen Erkennung ist `Illuminate\Foundation\PackageManifest`. Nachfolgend eine Zusammenfassung der Implementierung zum Zeitpunkt von 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)
            : [];
    }
}
```

Wichtig sind die folgenden drei Punkte:

* **Das Manifest wird nach dem ersten Laden im Speicher zwischengespeichert** (Property `$this->manifest`). Wenn `providers()` innerhalb eines Requests mehrfach aufgerufen wird, entsteht nur ein einziger Datei-I/O.
* **`build()` wird ausschließlich dann ausgeführt, wenn die Manifest-Datei nicht existiert.** Im normalen Betrieb wird das Manifest also nicht bei jedem Aufruf neu gebaut.
* Die tatsächliche Datei ist eine schlichte PHP-Datei, die ein Array `return`t (`bootstrap/cache/packages.php`) — das schnellstmögliche Format, da `require` genügt.

## Bau des Manifests

Die Methode `build()` ist der Teil, der die Informationen aus der `composer.json` tatsächlich zusammenträgt.

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

Wichtig ist, dass nicht die `composer.json` direkt geparst wird, sondern **`vendor/composer/installed.json`** gelesen wird. Diese Datei wird von Composer bei `composer install` bzw. `composer update` erzeugt und enthält die Metadaten aller installierten Pakete. Da die Abschnitte `extra` aus den `composer.json`-Dateien der einzelnen Pakete hier gebündelt sind, greift Laravel ausschließlich auf die von Composer verwalteten Informationen zurück.

<Info>
  `installed.json` ist üblicherweise in `.gitignore` enthalten, daher funktioniert die automatische Erkennung beim initialen Setup (wenn noch kein `vendor/` existiert) nicht. Der Cache wird erst nach Abschluss von `composer install` aufgebaut.
</Info>

### Zwei Schreibweisen für `dont-discover`

`dont-discover` kann sowohl in der `composer.json` eines Pakets als auch der Anwendung stehen, hat aber unterschiedliche Bedeutungen.

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

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

Wie die Implementierung von `build()` zeigt, wird das Array `$ignore` per `array_merge` mit den `configuration['dont-discover']`-Einträgen jedes Pakets gefüllt. Technisch ist es somit auch möglich, dass ein Paket selbst „die automatische Erkennung seiner eigenen Abhängigkeiten deaktiviert" — etwa, wenn ein intern verwendetes Sub-Paket sonst doppelt registriert würde. In der Praxis wird jedoch meist auf Anwendungsseite deaktiviert.

### `*` in `dont-discover` angeben

Enthält das von `packagesToIgnore()` zurückgegebene Array den Wert `*`, wird `$ignoreAll = true` — und damit **die automatische Erkennung für sämtliche Pakete deaktiviert**. Nützlich in CI- oder Testumgebungen, um den Overhead der automatischen Erkennung zu vermeiden, oder wenn Sie in `bootstrap/providers.php` alles vollständig manuell verwalten möchten.

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

## Die Cache-Datei

`getCachedPackagesPath()` gibt die Umgebungsvariable `APP_PACKAGES_CACHE` zurück, falls gesetzt, andernfalls `bootstrap/cache/packages.php`.

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

Wenn Sie diese Datei direkt öffnen, sehen Sie, dass sie lediglich ein einfaches assoziatives Array `return`t.

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

Da der Paketname als Schlüssel dient, können Sie an der Ausgabe von `php artisan package:discover` ablesen, welche Pakete erkannt wurden.

## Wann der Cache neu aufgebaut wird

`Illuminate\Foundation\ComposerScripts` hängt sich in drei Composer-Events ein, die alle dieselbe Methode `clearCompiled()` aufrufen.

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

Egal, ob Sie also `composer install`, `composer update` oder `composer dump-autoload` ausführen — der Konfigurations-, Services- und Paket-Cache werden alle zusammen gelöscht. Beim nächsten Start von Laravel wird `PackageManifest::build()` ausgeführt und der Cache aus dem aktuellen Stand der `installed.json` neu erstellt.

Dies ist das Standardverhalten eines Laravel-Projekts, das über den Abschnitt `scripts` in der `composer.json` konfiguriert ist.

```json title="composer.json einer Laravel-App (Auszug)" 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"
    ]
}
```

## Manueller Neuaufbau mit `php artisan package:discover`

Wenn der Cache veraltet ist oder Sie `vendor/` verändert haben, ohne Composer zu benutzen, können Sie den Cache mit `package:discover` manuell neu aufbauen.

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

Die tatsächliche Implementierung dieses Kommandos ist ein sehr dünner 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());
    }
}
```

Der Befehl ruft lediglich `$manifest->build()` auf und gibt das Ergebnis aus — eine spezifische Kommandologik gibt es kaum. In Situationen, in denen Composer-Events nicht ausgelöst werden (z. B. wenn Sie in der CI-Pipeline `composer install --no-scripts` verwenden), müssen Sie diesen Befehl explizit aufrufen.

<Warning>
  Wenn Sie [Orchestra Testbench](/de/advanced/package-testing) zum Testen Ihres Pakets verwenden, stellt Testbench einen eigenen Befehl `vendor/bin/testbench package:discover` bereit. Details finden Sie unter [Paket-Tests mit Testbench](/de/advanced/package-testing). Er unterscheidet sich vom Artisan-Befehl `package:discover` und baut das Manifest speziell für die Skelett-Anwendung von Testbench auf.
</Warning>

## Hinweise für das Deployment

Im Produktions-Deployment ist es üblich, `composer install --no-dev --optimize-autoloader` auszuführen. Fügen Sie jedoch `--no-scripts` hinzu, wird der Paket-Cache nicht aktualisiert. Es ist daher sicherer, Ihrem Deployment-Skript einen expliziten Neuaufbau hinzuzufügen.

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

Die Reihenfolge ist wichtig: Führen Sie `package:discover` vor `config:cache` aus. Wird der Konfigurations-Cache zuerst erstellt, werden nachträglich hinzugefügte Paketkonfigurationen (etwa die per `mergeConfigFrom` registrierten) möglicherweise nicht berücksichtigt.

## Zusammenfassung

* Die automatische Erkennung basiert nicht auf dem statischen Inhalt der `composer.json`, sondern auf `vendor/composer/installed.json`, das Composer erzeugt.
* Das Ergebnis wird in `bootstrap/cache/packages.php` als reines PHP-Array zwischengespeichert und lässt sich mit `require` allein schnell laden.
* Der Cache wird bei den Events `composer install/update/dump-autoload` automatisch gelöscht und beim nächsten Start neu aufgebaut.
* In Umgebungen, in denen Composer mit `--no-scripts` läuft, müssen Sie `php artisan package:discover` explizit aufrufen.
* Mit `*` in `dont-discover` lässt sich die automatische Erkennung insgesamt deaktivieren.

## Verwandte Seiten

* [Grundlagen der Paketentwicklung](/de/advanced/package-development) — Service Provider und die grundlegende Verwendung von `extra.laravel`
* [Deferred Service Provider](/de/advanced/deferred-provider) — Optimierung des Ladezeitpunkts erkannter Provider
* [Paket-Tests mit Orchestra Testbench](/de/advanced/package-testing) — Der eigene `package:discover`-Befehl von Testbench


## Related topics

- [Laravel-Paketentwicklung](/de/advanced/package-development.md)
- [Laravel Agent Detector – Paket zur Erkennung von KI-Agenten](/de/blog/agent-detector-introduction.md)
- [Eigene Auth-Guards implementieren](/de/advanced/custom-auth-guard.md)
- [⚡ Einführung in Livewire 4 – Reaktive UIs ohne JavaScript](/de/blog/livewire-introduction.md)
- [Migrationsleitfaden von der alten zur neuen Struktur](/de/advanced/app-structure-migration.md)
