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

# Etiquetas del contenedor en paquetes y agregación de plugins

> Agrega implementaciones de varios paquetes con las etiquetas del contenedor de Laravel 13 y analiza la resolución diferida de tagged(), su diferencia con giveTagged(), el registro duplicado y el ciclo de vida.

Si quieres que varios paquetes o la aplicación que los usa puedan añadir funcionalidades a un mismo servicio agregador, puedes usar `tag()` y `tagged()` del contenedor. El servicio que agrega no necesita conocer todas las clases de implementación.

Esta página está dirigida a Laravel 13 y se basa en la revisión de la implementación y los tests oficiales de Framework `v13.38.0`. Para los fundamentos de los bindings, consulta el [contenedor de servicios](https://kawax.biz/es/service-container.md), y para la distribución de providers, [Desarrollo de paquetes para Laravel](https://kawax.biz/es/advanced/package-development.md).

## Las etiquetas agrupan claves de registro de servicios

`tag()` añade claves de registro del contenedor, como nombres de clase, a la lista de una etiqueta. Etiquetar un servicio no lo crea.

`tagged()` devuelve un `iterable` para recorrer los servicios registrados con esa etiqueta. No es una función que busque automáticamente las clases que implementan una interfaz concreta, sino un mecanismo en el que cada provider participa de forma explícita. Tampoco tiene relación con las etiquetas de recursos publicables de `vendor:publish --tag`.

```mermaid theme={null}
flowchart TD
    A["register() de cada provider"] --> B["bind() de la implementación<br>y añadirla a la misma etiqueta"]
    B --> C["Resolver el servicio agregador"]
    C --> D["Guardar el iterable de tagged()"]
    D --> E["Recorrer con foreach"]
    E --> F["make() por cada clave de registro"]
    F --> G["Llamar a collect() de la implementación"]
```

## Crear un paquete de informes extensible

### Preparar el contrato que comparten las implementaciones

Como ejemplo, crearemos el paquete `acme/reports`, que reúne varios informes. Se asume una configuración PSR-4 de Composer en la que `Acme\Reports\` corresponde a `src/`.

En `src/Contracts/Report.php`, define el contrato que implementarán los plugins.

```php theme={null}
<?php

namespace Acme\Reports\Contracts;

interface Report
{
    public function collect(): array;
}
```

Como implementaciones mínimas, prepara `src/RuntimeReport.php` y `src/MemoryReport.php`.

```php theme={null}
<?php

namespace Acme\Reports;

use Acme\Reports\Contracts\Report;

class RuntimeReport implements Report
{
    public function collect(): array
    {
        return ['php_version' => PHP_VERSION];
    }
}
```

```php theme={null}
<?php

namespace Acme\Reports;

use Acme\Reports\Contracts\Report;

class MemoryReport implements Report
{
    public function collect(): array
    {
        return ['allocated_bytes' => memory_get_usage(true)];
    }
}
```

### El servicio agregador recibe un iterable

`src/ReportCollector.php` llama al proceso de recopilación al recorrer cada implementación. Si defines como constante el nombre de la etiqueta pública, quienes usen el paquete pueden referirse al mismo nombre.

```php theme={null}
<?php

namespace Acme\Reports;

use Acme\Reports\Contracts\Report;
use UnexpectedValueException;

class ReportCollector
{
    public const TAG = 'acme.reports.collectors';

    /** @param iterable<Report> $reports */
    public function __construct(private iterable $reports)
    {
    }

    public function collect(): array
    {
        $results = [];

        foreach ($this->reports as $report) {
            if (! $report instanceof Report) {
                throw new UnexpectedValueException('Tagged services must implement Report.');
            }

            $results[] = $report->collect();
        }

        return $results;
    }
}
```

El tipo `iterable` de PHP por sí solo no permite validar el tipo de los elementos, y `tag()` tampoco comprueba si se trata de una implementación de `Report`. En este ejemplo, el servicio agregador también verifica el contrato.

### En register() solo se registran bindings y etiquetas

En `src/ReportsServiceProvider.php`, registra las implementaciones estándar y el servicio agregador. La factory del servicio agregador pasa tal cual el `iterable` obtenido.

```php theme={null}
<?php

namespace Acme\Reports;

use Illuminate\Contracts\Container\Container;
use Illuminate\Support\ServiceProvider;

class ReportsServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->bind(RuntimeReport::class);
        $this->app->bind(MemoryReport::class);

        $this->app->tag([
            RuntimeReport::class,
            MemoryReport::class,
        ], ReportCollector::TAG);

        $this->app->bind(ReportCollector::class, function (Container $app): ReportCollector {
            return new ReportCollector($app->tagged(ReportCollector::TAG));
        });
    }
}
```

Otros paquetes o la aplicación que los usa preparan, por ejemplo, un `DailyReport` que implemente `Report` y lo añaden a la misma etiqueta dentro del `register()` de su propio provider.

```php theme={null}
use Acme\Reports\ReportCollector;
use App\Reports\DailyReport;

$this->app->bind(DailyReport::class);
$this->app->tag(DailyReport::class, ReportCollector::TAG);
```

Después de registrar todos los providers, al llamar a `app(ReportCollector::class)->collect()` también se agregan las implementaciones añadidas. Aunque aumente el número de implementaciones participantes, no hace falta modificar la creación de `ReportCollector`.

## tagged() y giveTagged() crean los servicios en momentos distintos

En la implementación revisada, `tagged()` sobre una etiqueta registrada devuelve un `RewindableGenerator`. El `make()` de cada servicio se llama durante el recorrido y, si se detiene a mitad, los servicios restantes no se crean.

Además, `RewindableGenerator` crea un Generator nuevo en cada recorrido. No es una función que almacene en caché los resultados de los servicios: con un `bind()` normal, las instancias se vuelven a crear al recorrer de nuevo. Los servicios `singleton()` o `scoped()` siguen sus propias reglas de compartición.

En cambio, `giveTagged()` del contextual binding convierte el resultado de `tagged()` en un array con `iterator_to_array()` en el momento de la resolución.

| Forma de inyección | Momento en que se resuelve cada servicio | Nuevo recorrido sobre el mismo objeto agregador |
| - | - | - |
| Pasar tal cual el resultado de `tagged()` | Al recorrer cada elemento | Se vuelve a ejecutar `make()` |
| Argumento `iterable` con `#[Tag(...)]` | Al recorrer cada elemento | Se vuelve a ejecutar `make()` |
| Inyectar con `giveTagged()` | Al resolver las dependencias del servicio agregador | Se reutilizan los elementos del array guardado |

Si quieres resolver las dependencias de una sola vez como array, puedes sustituir el `bind()` con factory anterior por el siguiente registro.

```php theme={null}
use Acme\Reports\ReportCollector;

$this->app->bind(ReportCollector::class);

$this->app->when(ReportCollector::class)
    ->needs('$reports')
    ->giveTagged(ReportCollector::TAG);
```

Aunque el tipo de `$reports` sea `iterable`, el valor que se guarda con este método es un array. El tipo del argumento por sí solo no garantiza la resolución diferida. Además, lo que hace `giveTagged()` es resolver las instancias, no llamar al `collect()` de cada implementación.

El `resolve()` del atributo `Tag` devuelve directamente `tagged()`. Si usas el atributo, indícalo en un argumento del constructor como en los [atributos contextuales](https://kawax.biz/es/service-container.md#atributos-contextuales) y deja la construcción de la clase en manos del contenedor. El atributo no se aplica automáticamente a un `new` dentro de una factory propia.

## No romper el contrato de registro de plugins

### Explicitar el registro duplicado y el orden

`tag()` es una operación de añadido a la lista y no elimina duplicados aunque se vuelva a añadir la misma clave de registro. Si la misma implementación se recorre dos veces, `collect()` se llama dos veces aunque un singleton devuelva el mismo objeto.

Concentra el registro de la etiqueta en un único lugar: el provider responsable de cada implementación. `bindIf()` es una API para evitar sobrescribir bindings, no para impedir un `tag()` duplicado posterior.

El orden de recorrido es el orden en que se añadieron los elementos a la lista de la etiqueta. Las etiquetas no ordenan por prioridad, así que no hagas depender el orden de procesamiento entre paquetes distintos solo del orden de carga de los providers. Si el orden forma parte de la especificación pública, incluye una prioridad o un identificador en el contrato y diseña el servicio agregador para que ordene de forma explícita.

### Registrar primero los providers que añaden etiquetas

`tagged()` sobre una etiqueta no registrada devuelve un array vacío en lugar de lanzar una excepción. Los registros de etiqueta posteriores no se reflejan en un array vacío recibido antes ni en un array ya creado con `giveTagged()`.

Tampoco des por hecho que el recorrido diferido de una etiqueta registrada reflejará cambios en la lista durante la ejecución. El `tagged()` revisado pasa el número de elementos en el momento de obtenerlo, pero consulta la lista de la etiqueta al recorrerla. Si diseñas el uso para después de que todos los providers hayan terminado de registrarse, también evitas discrepancias entre el número de elementos y los elementos recorridos.

> [!WARNING]
>
> Recorrer una etiqueta no busca, a partir del nombre de la etiqueta, providers que aún no se hayan cargado. Si el provider que añade la etiqueta es un `DeferrableProvider`, el registro de la etiqueta no se ejecuta hasta que ese servicio se resuelva por otro camino, y puede quedar fuera de la lista. Para registrar en una etiqueta implementaciones que deban participar siempre, usa un provider normal y diséñalo para que no cree las implementaciones durante el registro.

Puedes consultar las condiciones de arranque de los providers diferidos en [Service providers diferidos](https://kawax.biz/es/advanced/deferred-provider.md), y la carga durante la instalación en la [detección automática de paquetes](https://kawax.biz/es/advanced/package-discovery.md).

### Separar las etiquetas del ciclo de vida de los servicios

Etiquetar un servicio no lo convierte automáticamente en singleton ni en scoped. Este ejemplo usa un `bind()` normal y no guarda los resultados de la recopilación en el objeto agregador.

En particular, si guardas en un servicio agregador singleton un array resuelto con `giveTagged()`, los servicios scoped del array también pueden quedar retenidos. Restablecer el scope no sustituye las referencias ya guardadas en otros objetos. Para diseños que se usen con Octane o workers de colas, consulta también el [Diseño del ciclo de vida de los servicios de paquetes](https://kawax.biz/es/advanced/package-service-lifetimes.md).

## Probar la resolución diferida y la conversión a array

En un entorno con el autoload de Composer y PHPUnit del paquete, puedes añadir el siguiente test en `tests/ReportCollectorTest.php`. Llama al registro del provider solo con el contenedor y verifica el número de creaciones. Los tests que incluyan la integración con la aplicación y la detección automática se complementan con [Orchestra Testbench](https://kawax.biz/es/advanced/package-testing.md).

```php theme={null}
<?php

namespace Acme\Reports\Tests;

use Acme\Reports\ReportCollector;
use Acme\Reports\ReportsServiceProvider;
use Acme\Reports\RuntimeReport;
use Illuminate\Container\Container;
use PHPUnit\Framework\TestCase;

class ReportCollectorTest extends TestCase
{
    public function test_tagged_reports_are_lazy_and_can_be_resolved_again(): void
    {
        $created = 0;
        $app = $this->makeContainer($created);
        $collector = $app->make(ReportCollector::class);

        $this->assertSame(0, $created);
        $this->assertCount(2, $collector->collect());
        $this->assertSame(1, $created);
        $this->assertCount(2, $collector->collect());
        $this->assertSame(2, $created);
    }

    public function test_give_tagged_resolves_an_array_once(): void
    {
        $created = 0;
        $app = $this->makeContainer($created);
        $app->bind(ReportCollector::class);
        $app->when(ReportCollector::class)
            ->needs('$reports')
            ->giveTagged(ReportCollector::TAG);

        $collector = $app->make(ReportCollector::class);

        $this->assertSame(1, $created);
        $this->assertCount(2, $collector->collect());
        $this->assertCount(2, $collector->collect());
        $this->assertSame(1, $created);
    }

    private function makeContainer(int &$created): Container
    {
        $app = new Container;
        (new ReportsServiceProvider($app))->register();
        $app->bind(RuntimeReport::class, function () use (&$created): RuntimeReport {
            $created++;

            return new RuntimeReport;
        });

        return $app;
    }
}
```

Además, al actualizar el paquete, comprueba las siguientes combinaciones.

| Caso | Qué comprobar |
| - | - |
| Registrar una implementación adicional en la misma etiqueta | Se incluye en la agregación y pasa la comprobación de tipo del contrato |
| Registrar la misma implementación por duplicado | Se puede detectar que el proceso de recopilación se duplica |
| Etiqueta no registrada o nombre de etiqueta incorrecto | Está claro si se admite una lista vacía o si el paquete lanza un error |
| Reutilizar en otra petición u otro job | No se arrastran servicios scoped ni resultados de recopilación |
| Cambiar el nombre de la etiqueta o el contrato | Se evalúa la compatibilidad como API pública a la que hacen referencia los providers de quienes usan el paquete |

## Fuentes primarias consultadas

* [Documentación oficial de Laravel: Tagging](https://github.com/laravel/docs/blob/13.x/container.md#tagging)
* [Documentación oficial de Laravel: Binding primitives y Variadic tag dependencies](https://github.com/laravel/docs/blob/13.x/container.md#binding-primitives)
* [Documentación oficial de Laravel: Service providers](https://github.com/laravel/docs/blob/13.x/providers.md)
* [Documentación oficial de Laravel: Desarrollo de paquetes](https://github.com/laravel/docs/blob/13.x/packages.md)
* [Container: tag() y tagged()](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Container/Container.php)
* [ContextualBindingBuilder: giveTagged()](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Container/ContextualBindingBuilder.php)
* [RewindableGenerator: nuevo recorrido y número de elementos](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Container/RewindableGenerator.php)
* [Atributo Tag: resolve()](https://github.com/laravel/framework/blob/v13.38.0/src/Illuminate/Container/Attributes/Tag.php)
* [Test oficial: ContainerTaggingTest](https://github.com/laravel/framework/blob/v13.38.0/tests/Container/ContainerTaggingTest.php)
* [Test oficial: ContextualBindingTest](https://github.com/laravel/framework/blob/v13.38.0/tests/Container/ContextualBindingTest.php)


## Related topics

- [Service container](https://kawax.biz/es/service-container.md)
- [Temas avanzados](https://kawax.biz/es/advanced/index.md)
- [Assets públicos de paquetes y su actualización](https://kawax.biz/es/advanced/package-assets.md)
- [Diseño del ciclo de vida de los servicios de paquetes](https://kawax.biz/es/advanced/package-service-lifetimes.md)
- [Publicación y actualización de migraciones de paquetes](https://kawax.biz/es/advanced/package-migrations.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.