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

# Struttura interna del package auto-discovery

> Analizziamo l'implementazione interna della classe PackageManifest per capire come extra.laravel di composer.json viene messo in cache e caricato come bootstrap/cache/packages.php.

## A proposito di questa pagina

In [Fondamenti dello sviluppo di pacchetti](/it/advanced/package-development) abbiamo visto che basta scrivere la sezione `extra.laravel` di `composer.json` per registrare automaticamente service provider e facade. In questa pagina spieghiamo cosa succede dietro le quinte, ovvero come funziona la classe `Illuminate\Foundation\PackageManifest` a livello di codice sorgente.

<Info>
  Questa pagina è la pagina gemella di [Fondamenti dello sviluppo di pacchetti](/it/advanced/package-development). Ti consigliamo di leggere prima l'uso di base dell'auto-discovery.
</Info>

## Visione d'insieme del meccanismo di auto-discovery

```mermaid theme={null}
flowchart TD
    A["composer install / update"] --> B["Composer scatena l'evento<br>post-autoload-dump"]
    B --> C["Illuminate\\Foundation\\ComposerScripts::postAutoloadDump()"]
    C --> D["Elimina i file di cache come<br>bootstrap/cache/packages.php"]
    D --> E["Al successivo avvio viene eseguito PackageManifest::build()"]
    E --> F["Legge vendor/composer/installed.json"]
    F --> G["Raccoglie l'extra.laravel di ogni pacchetto"]
    G --> H["Applica l'esclusione tramite dont-discover"]
    H --> I["Scrive in bootstrap/cache/packages.php"]
    I --> J["L'Application legge providers()/aliases()<br>e registra i service provider"]
```

## La classe `PackageManifest`

Il cuore dell'auto-discovery è `Illuminate\Foundation\PackageManifest`. Qui sotto trovi l'implementazione (riassunta) del framework alla versione 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)
            : [];
    }
}
```

I punti chiave sono tre.

* **Il manifest, una volta caricato, viene messo in cache in memoria** (proprietà `$this->manifest`). Anche chiamando più volte `providers()` all'interno di una singola richiesta, l'I/O sul file avviene una sola volta.
* **`build()` viene eseguito solo quando il file manifest non esiste**. In esercizio normale non viene ricostruito ad ogni richiesta.
* L'oggetto reale è un file che si limita a fare `return` di un semplice array PHP (`bootstrap/cache/packages.php`), la forma più veloce da caricare con un semplice `require`.

## Il processo di build del manifest

Il metodo `build()` è la parte che aggrega effettivamente le informazioni dei `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());
}
```

Il punto importante è che, invece di fare direttamente il parsing dei `composer.json`, viene letto **`vendor/composer/installed.json`**. Questo è un file di metadati generato da Composer durante `composer install` / `composer update` che contiene tutti i pacchetti installati. La sezione `extra` scritta nei `composer.json` di ciascun pacchetto è aggregata qui dentro, quindi Laravel legge solo informazioni sotto il controllo di Composer.

<Info>
  `installed.json` è di solito escluso da git tramite `.gitignore`, quindi al primo setup (senza la directory `vendor/`) l'auto-discovery non funziona. La cache viene costruita per la prima volta solo dopo il completamento di `composer install`.
</Info>

### Le due modalità di `dont-discover`

`dont-discover` può essere scritto sia nel `composer.json` del pacchetto sia in quello dell'applicazione, ma il significato cambia.

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

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

Osservando l'implementazione di `build()`, l'array `$ignore` accumula i `configuration['dont-discover']` di ogni pacchetto con `array_merge`. In altre parole, tecnicamente è possibile che un pacchetto stesso "disabiliti l'auto-discovery per i propri pacchetti dipendenti" (esempio: per evitare doppie registrazioni dei provider di sotto-pacchetti usati internamente). Nell'uso pratico, tuttavia, la disabilitazione avviene di solito lato applicazione.

### Specificare `*` in `dont-discover`

Se l'array restituito da `packagesToIgnore()` contiene `*`, `$ignoreAll = true` e **l'auto-discovery viene disabilitato in blocco per tutti i pacchetti**. Utile in CI o in ambienti di test dove si vuole evitare l'overhead dell'auto-discovery, oppure quando si vuole gestire completamente a mano `bootstrap/providers.php`.

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

## Il file di cache

`getCachedPackagesPath()` restituisce il valore della variabile d'ambiente `APP_PACKAGES_CACHE` se presente, altrimenti `bootstrap/cache/packages.php`.

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

Se apri direttamente questo file, vedi che si limita a fare `return` di un semplice array associativo.

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

Poiché la chiave è il nome del pacchetto, dall'output di `php artisan package:discover` puoi verificare "quali pacchetti sono stati scoperti".

## Quando la cache viene ricostruita

`Illuminate\Foundation\ComposerScripts` aggancia tre eventi di Composer, tutti richiamano lo stesso `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);
    }
}
```

Quindi, eseguendo `composer install`, `composer update` o `composer dump-autoload`, la cache di configurazione, servizi e pacchetti viene eliminata in blocco. Al successivo avvio di Laravel viene eseguito `PackageManifest::build()` che ricostruisce tutto dallo stato più recente di `installed.json`.

Questo è il comportamento standard di un progetto Laravel, registrato negli `scripts` del suo `composer.json`.

```json title="composer.json dell'app Laravel (estratto)" 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"
    ]
}
```

## Ricostruzione manuale con `php artisan package:discover`

Se la cache è rimasta obsoleta, oppure se hai modificato direttamente `vendor/` senza passare da Composer, puoi ricostruire manualmente con il comando `package:discover`.

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

L'implementazione di questo comando è un wrapper molto sottile.

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

Si limita a chiamare `$manifest->build()` e a stampare il risultato: non c'è quasi nessuna logica specifica del comando. In situazioni in cui gli eventi di Composer non vengono scatenati — ad esempio quando in una pipeline CI usi `composer install --no-scripts` — devi invocare esplicitamente questo comando.

<Warning>
  Quando testi i pacchetti con [Orchestra Testbench](/it/advanced/package-testing), Testbench fornisce il proprio comando `vendor/bin/testbench package:discover`. Per i dettagli vedi [Test dei pacchetti con Testbench](/it/advanced/package-testing). È diverso dal `package:discover` di Artisan e costruisce il manifest per l'app scheletro dedicata a Testbench.
</Warning>

## Note per il deploy

In produzione è comune eseguire `composer install --no-dev --optimize-autoloader`, ma se aggiungi `--no-scripts` la cache dei pacchetti non viene aggiornata. È più sicuro inserire uno step di ricostruzione esplicito nello script di deploy.

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

Anche l'ordine è importante. Esegui `package:discover` prima di `config:cache`. Se la cache di configurazione viene creata per prima, le configurazioni dei pacchetti aggiunte successivamente (ad esempio quelle registrate tramite `mergeConfigFrom`) potrebbero non essere riflesse.

## Riepilogo

* L'auto-discovery non si basa sul contenuto statico dei `composer.json`, ma legge il `vendor/composer/installed.json` generato da Composer.
* Il risultato viene messo in cache come semplice array PHP in `bootstrap/cache/packages.php` e caricato rapidamente con un semplice `require`.
* La cache viene eliminata automaticamente dagli eventi `composer install/update/dump-autoload` e ricostruita al successivo avvio.
* Negli ambienti in cui Composer viene eseguito con `--no-scripts` devi chiamare esplicitamente `php artisan package:discover`.
* Specificando `*` in `dont-discover` puoi disabilitare l'intero auto-discovery.

## Pagine correlate

* [Fondamenti dello sviluppo di pacchetti](/it/advanced/package-development) — uso di base di service provider e `extra.laravel`
* [Service provider differiti](/it/advanced/deferred-provider) — ottimizzare quando i provider scoperti vengono caricati
* [Test dei pacchetti con Orchestra Testbench](/it/advanced/package-testing) — il comando `package:discover` specifico di Testbench


## Related topics

- [Sviluppo di pacchetti Laravel](/it/advanced/package-development.md)
- [Struttura dell'applicazione da Laravel 11](/it/advanced/app-structure.md)
- [FAQ sulla nuova struttura dell'app da Laravel 11](/it/advanced/app-structure-faq.md)
- [Guida di migrazione dalla vecchia alla nuova struttura](/it/advanced/app-structure-migration.md)
- [Implementare un guard di autenticazione personalizzato](/it/advanced/custom-auth-guard.md)
