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

# Estructura interna de la detección automática de paquetes

> Analiza la implementación interna de la clase PackageManifest y explica cómo se guarda en caché extra.laravel de composer.json y cómo se carga como bootstrap/cache/packages.php.

## Sobre esta página

En [Fundamentos del desarrollo de paquetes](/es/advanced/package-development) presentamos que basta con escribir la sección `extra.laravel` en `composer.json` para que los service providers y las fachadas se registren automáticamente. En esta página se explica qué ocurre por debajo: cómo funciona la clase `Illuminate\Foundation\PackageManifest` a nivel de código fuente.

<Info>
  Esta página es la hermana de [Fundamentos del desarrollo de paquetes](/es/advanced/package-development). Se recomienda leer primero el uso básico de la detección automática.
</Info>

## Visión general del mecanismo de detección automática

```mermaid theme={null}
flowchart TD
    A["composer install / update"] --> B["Composer dispara el evento<br>post-autoload-dump"]
    B --> C["Illuminate\\Foundation\\ComposerScripts::postAutoloadDump()"]
    C --> D["Se eliminan archivos de caché<br>como bootstrap/cache/packages.php"]
    D --> E["En el siguiente arranque se ejecuta PackageManifest::build()"]
    E --> F["Se lee vendor/composer/installed.json"]
    F --> G["Se recopilan los extra.laravel de cada paquete"]
    G --> H["Procesamiento de exclusiones con dont-discover"]
    H --> I["Se escribe bootstrap/cache/packages.php"]
    I --> J["La Application lee providers()/aliases()<br>y registra los service providers"]
```

## La clase `PackageManifest`

El núcleo de la detección automática es `Illuminate\Foundation\PackageManifest`. A continuación se muestra un resumen de la implementación en la versión 13.x del framework.

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

Los puntos clave son tres:

* **El manifiesto se guarda en memoria una vez cargado** (propiedad `$this->manifest`). Aunque llames varias veces a `providers()` en una misma petición, la E/S de archivo se produce una sola vez.
* **`build()` solo se ejecuta si el archivo de manifiesto no existe.** En operación normal no se reconstruye en cada arranque.
* El archivo real es un archivo PHP que hace `return` de un array plano (`bootstrap/cache/packages.php`), el formato más rápido de cargar simplemente con `require`.

## Proceso de construcción del manifiesto

El método `build()` es la parte que realmente agrega la información de los `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());
}
```

Un aspecto importante es que en lugar de parsear directamente los `composer.json`, se lee **`vendor/composer/installed.json`**. Es un archivo de metadatos de todos los paquetes instalados que Composer genera al ejecutar `composer install` / `composer update`. Como la sección `extra` del `composer.json` de cada paquete queda concentrada aquí, Laravel confía y lee únicamente la información gestionada por Composer.

<Info>
  `installed.json` suele estar en `.gitignore`, por lo que en la configuración inicial (cuando no existe `vendor/`) la detección automática no funciona. La caché se construye por primera vez tras finalizar `composer install`.
</Info>

### Dos formas de escribir `dont-discover`

`dont-discover` puede declararse tanto en el `composer.json` del paquete como en el de la aplicación, pero su significado es distinto.

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

```json title="composer.json de la aplicación" theme={null}
"extra": {
    "laravel": {
        "dont-discover": [
            "acme/courier"
        ]
    }
}
```

Al observar la implementación de `build()`, el array `$ignore` se acumula mediante `array_merge` con el `configuration['dont-discover']` de cada paquete. Es decir, técnicamente un paquete puede «desactivar la detección automática de sus propios paquetes dependientes» (por ejemplo, cuando no quieres registrar por duplicado los providers de un subpaquete que utilizas internamente). No obstante, en la práctica lo habitual es desactivarlo desde la aplicación.

### Especificar `*` en `dont-discover`

Si el array que devuelve `packagesToIgnore()` contiene `*`, entonces `$ignoreAll = true` y **se desactiva por completo la detección automática de todos los paquetes**. Se utiliza cuando quieres evitar la sobrecarga de la detección automática en CI o entornos de test, o cuando quieres gestionar todo manualmente en `bootstrap/providers.php`.

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

## El archivo de caché en sí

`getCachedPackagesPath()` devuelve la variable de entorno `APP_PACKAGES_CACHE` si está definida y, en caso contrario, `bootstrap/cache/packages.php`.

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

Si abres este archivo directamente, verás que simplemente hace `return` de un array asociativo sencillo.

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

Como la clave es el nombre del paquete, puedes comprobar en la salida de `php artisan package:discover` qué paquetes se han detectado.

## Cuándo se reconstruye la caché

`Illuminate\Foundation\ComposerScripts` engancha tres eventos de Composer y todos llaman al mismo `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);
    }
}
```

Es decir, tanto si ejecutas `composer install`, `composer update` o `composer dump-autoload`, la caché de configuración, la de servicios y la de paquetes se eliminan de golpe. La próxima vez que arranque Laravel se ejecutará `PackageManifest::build()` y se reconstruirá a partir del estado más reciente de `installed.json`.

Este es el comportamiento estándar de un proyecto Laravel, registrado en la sección `scripts` de `composer.json`.

```json title="composer.json de una app Laravel (extracto)" 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"
    ]
}
```

## Reconstrucción manual con `php artisan package:discover`

Si la caché sigue desactualizada o si modificas `vendor/` directamente sin pasar por Composer, puedes reconstruirla manualmente con el comando `package:discover`.

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

El comando es en realidad un envoltorio muy ligero.

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

Solo llama a `$manifest->build()` e imprime el resultado; apenas tiene lógica específica de comando. En situaciones donde no se disparan los eventos de Composer, como cuando usas `composer install --no-scripts` en un pipeline de CI, necesitas invocar este comando explícitamente.

<Warning>
  Si utilizas [Orchestra Testbench](/es/advanced/package-testing) para las pruebas del paquete, Testbench ofrece su propio comando `vendor/bin/testbench package:discover`. Consulta [Pruebas de paquetes con Testbench](/es/advanced/package-testing) para más detalles. Es distinto del `package:discover` de Artisan y construye el manifiesto para la aplicación esqueleto exclusiva de Testbench.
</Warning>

## Precauciones al desplegar

En producción es habitual ejecutar `composer install --no-dev --optimize-autoloader`, pero si añades `--no-scripts` la caché de paquetes no se actualiza. Es más seguro incluir un paso explícito de reconstrucción en el script de despliegue.

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

El orden también importa. Ejecuta `package:discover` antes que `config:cache`. Si la caché de configuración se genera primero, es posible que la configuración de paquetes añadida posteriormente (por ejemplo, la registrada con `mergeConfigFrom`) no se refleje.

## Resumen

* La detección automática no funciona sobre el contenido estático de `composer.json`, sino leyendo el `vendor/composer/installed.json` que genera Composer.
* El resultado se guarda en `bootstrap/cache/packages.php` como un array PHP plano y se carga de forma muy rápida simplemente con `require`.
* La caché se elimina automáticamente en los eventos de `composer install/update/dump-autoload` y se reconstruye en el siguiente arranque.
* En entornos donde ejecutas Composer con `--no-scripts`, es necesario invocar explícitamente `php artisan package:discover`.
* Si añades `*` a `dont-discover`, puedes desactivar por completo la detección automática.

## Páginas relacionadas

* [Fundamentos del desarrollo de paquetes](/es/advanced/package-development) — uso básico de los service providers y `extra.laravel`.
* [Service providers diferidos](/es/advanced/deferred-provider) — optimizar el momento en que se cargan los providers detectados.
* [Pruebas de paquetes con Orchestra Testbench](/es/advanced/package-testing) — el comando `package:discover` exclusivo de Testbench.


## Related topics

- [Desarrollo de paquetes para Laravel](/es/advanced/package-development.md)
- [Laravel Agent Detector — Paquete de detección de agentes de IA](/es/blog/agent-detector-introduction.md)
- [FAQ de la nueva estructura de aplicación de Laravel 11 y posteriores](/es/advanced/app-structure-faq.md)
- [Empieza a testear Laravel con Pest PHP](/es/blog/pest-introduction.md)
- [Estructura de aplicación en Laravel 11 y posteriores](/es/advanced/app-structure.md)
