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

# Attributi contestuali e risoluzione delle dipendenze nei pacchetti

> Analizzare i ContextualAttribute di Laravel 13 e spiegare gli attributi che iniettano la configurazione di un pacchetto, l'ordine di priorità della risoluzione, la differenza tra make e call, i test e la manutenzione dei servizi a lunga durata.

A volte si vuole permettere agli utenti di un pacchetto di iniettare i valori di cui hanno bisogno senza elencare chiavi di configurazione nel service provider. Esponendo un attributo contestuale, è possibile risolvere configurazioni o servizi specifici del pacchetto a partire dai metadati applicati a un parametro.

Questa pagina esamina il sorgente ufficiale di Laravel Framework `v13.39.0` e implementa un attributo personalizzato che legge la configurazione `courier`. Se il solo attributo integrato `#[Config('courier.timeout')]` è sufficiente per lo scopo, non serve aggiungere attributi personalizzati. Valutali quando vuoi offrire, come API del pacchetto, convenzioni proprie sulle chiavi o una politica di risoluzione specifica.

## Il ruolo dell'attributo è decidere quale valore iniettare

Gli attributi che implementano `Illuminate\Contracts\Container\ContextualAttribute` vengono letti dal container di Laravel quando risolve i parametri. Alla classe che li usa non viene passato l'oggetto attributo in sé, ma il valore restituito dal resolver.

| Metodo | Dove si configura | Uso adatto |
| - | - | - |
| `when()->needs()->give()` | Service provider | Cambiare le dipendenze dal lato applicazione senza modificare la classe che le usa |
| Attributi integrati `Config`, `Storage`, `Tag` | Parametri della classe che li usa | Dichiarare un metodo di risoluzione fornito da Laravel |
| Attributo contestuale proprio del pacchetto | Parametri della classe che li usa e classe dell'attributo | Risolvere valori secondo convenzioni specifiche del pacchetto |

Per i binding ordinari e gli attributi integrati consulta [Service container](https://kawax.biz/it/service-container.md). Gli attributi contestuali sono un meccanismo diverso dagli attributi che dichiarano binding di classi o interfacce, come `Bind`, `Singleton` e `Scoped`.

## Creare un attributo che legge la configurazione del pacchetto

### Registrare la configurazione predefinita

Per prima cosa, prepara i valori predefiniti in `config/courier.php` del pacchetto.

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

return [
    'timeout' => 30,
    'retries' => 3,
];
```

Unisci la configurazione nel metodo `register()` di `src/CourierServiceProvider.php`. Per la registrazione automatica del provider consulta [Struttura interna del package auto-discovery](https://kawax.biz/it/advanced/package-discovery.md), per la pubblicazione e la gestione della cache di configurazione consulta [Merge e cache della configurazione dei pacchetti](https://kawax.biz/it/advanced/package-config-merging.md).

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

namespace Acme\Courier;

use Illuminate\Support\ServiceProvider;

class CourierServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->mergeConfigFrom(
            __DIR__.'/../config/courier.php', 'courier'
        );
    }
}
```

### Implementare `ContextualAttribute`

In `src/Attributes/CourierConfig.php` definisci un attributo che antepone `courier.` alla chiave di configurazione.

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

namespace Acme\Courier\Attributes;

use Attribute;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Container\ContextualAttribute;
use ReflectionParameter;

#[Attribute(Attribute::TARGET_PARAMETER)]
final class CourierConfig implements ContextualAttribute
{
    public function __construct(
        public ?string $key = null,
        public mixed $default = null,
    ) {}

    public static function resolve(
        self $attribute,
        Container $container,
        ReflectionParameter $parameter,
    ): mixed {
        $key = $attribute->key ?? $parameter->getName();

        return $container->make('config')->get(
            'courier.'.$key, $attribute->default
        );
    }
}
```

Nell'implementazione esaminata, al resolver vengono passati i seguenti tre argomenti.

1. L'istanza dell'attributo applicato al parametro.
2. Il container che sta risolvendo la dipendenza.
3. Il `ReflectionParameter` che rappresenta il parametro da risolvere.

In questo esempio, se la chiave viene omessa si usa il nome del parametro. `#[CourierConfig(default: 30)] int $timeout` fa riferimento a `courier.timeout`. Se vuoi leggere la stessa configurazione anche quando il nome del parametro cambia lato utente, specifica la chiave in modo esplicito, ad esempio `#[CourierConfig('timeout', default: 30)]`.

Al momento della verifica, `ContextualAttribute` è un'interfaccia marcatore che non dichiara metodi. Per questo, il solo `implements` non garantisce l'esistenza né la firma del resolver. Se fornisci `resolve()` come sopra, non è necessario registrare un normale `bind()` per questo attributo.

> [!WARNING]
>
> Usa il costruttore dell'attributo solo per conservare metadati. Il container chiama `newInstance()` separatamente durante la risoluzione e durante la post-elaborazione dell'attributo, quindi non è il posto per un'"inizializzazione da eseguire una sola volta". Non inserire comunicazioni esterne né stato mutabile.

### Creare la classe utente dal container

Applica l'attributo in `src/CourierOptions.php`.

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

namespace Acme\Courier;

use Acme\Courier\Attributes\CourierConfig;

final class CourierOptions
{
    public function __construct(
        #[CourierConfig('timeout', default: 30)] public int $timeout,
        #[CourierConfig('retries', default: 3)] public int $retries,
    ) {}
}
```

Se l'applicazione utente sovrascrive la configurazione, quel valore viene iniettato alla risoluzione successiva.

```php theme={null}
use Acme\Courier\CourierOptions;

config(['courier.timeout' => 45]);

$options = app()->make(CourierOptions::class);
```

In questo esempio `$options->timeout` vale `45` e `$options->retries` vale `3`. L'attributo si limita a ottenere la configurazione e non esegue validazioni di dominio, come verificare che si tratti di un intero positivo o che non superi un valore massimo consentito. Nemmeno le dichiarazioni di tipo sostituiscono la validazione dei valori di configurazione.

## Leggere l'ordine di risoluzione nel costruttore

`Container::resolveDependencies()` determina il valore di ciascun parametro nel seguente ordine.

1. Usa il valore di override passato tramite `makeWith()` o simili, se corrisponde al nome del parametro.
2. Cerca e risolve un attributo contestuale con `Util::getContextualAttributeFromDependency()`.
3. Se il risultato dell'attributo è `null`, passa alla normale risoluzione di valori primitivi o classi.
4. Dopo la post-elaborazione dell'attributo, passa il valore determinato al costruttore.

Il percorso di override del punto 1 salta la risoluzione dell'attributo e la post-elaborazione per quel parametro.

```mermaid theme={null}
flowchart TD
    A["Parametro del costruttore"] --> B{"Già sovrascritto per nome del parametro?"}
    B -->|Yes| C["Usa il valore di override"]
    B -->|No| D{"C'è un ContextualAttribute?"}
    D -->|Yes| E["Chiama il resolver dell'attributo"]
    E --> F{"Il risultato è null?"}
    F -->|No| G["Usa il valore dell'attributo"]
    F -->|Yes| H["Risoluzione normale di tipo o primitivo"]
    D -->|No| H
    G --> I["Post-elaborazione dell'attributo"]
    H --> I
    C --> J["Passa al costruttore"]
    I --> J
```

Le chiavi di `makeWith()` sono i nomi dei parametri della classe utente, non le chiavi di configurazione.

```php theme={null}
use Acme\Courier\CourierOptions;

$options = app()->makeWith(CourierOptions::class, [
    'timeout' => 5,
]);
```

In questo caso `timeout` vale `5` indipendentemente dalla configurazione. D'altra parte, se l'attributo restituisce un valore diverso da `null`, il suo risultato ha la precedenza sul normale `when()->needs('$timeout')->give(...)`. Verifica con i test anche la possibilità che l'aggiunta di un attributo al pacchetto modifichi i binding contestuali degli utenti esistenti.

`Util` ottiene tramite Reflection gli attributi che implementano `ContextualAttribute` e sceglie il primo per la risoluzione. Anche se si affiancano più attributi contestuali diversi, i resolver non vengono composti in sequenza.

### Gli handler registrati hanno la precedenza su `resolve()`

`Container::resolveFromAttribute()` usa il nome della classe dell'attributo come chiave e cerca prima un handler registrato con `whenHasAttribute()`. Se non ne trova, usa il metodo `resolve()` dell'attributo; se non esiste nessuno dei due, lancia una `BindingResolutionException`.

`whenHasAttribute()` è una configurazione per tipo di attributo, non per classe utente. Registrare di nuovo la stessa classe di attributo sovrascrive l'handler precedente. Se un pacchetto modifica incondizionatamente gli handler di altri pacchetti o degli attributi integrati di Laravel, l'effetto si estende all'intera applicazione utente. Colloca il comportamento standard dell'attributo personalizzato in `resolve()` e tratta le modifiche tramite handler come un punto di estensione esplicito.

## `make()`, `call()` e la chiamata diretta non sono la stessa cosa

Anche `app()->call()` risolve gli attributi, ma il normale percorso di `BoundMethod`, quando trova un attributo, aggiunge il risultato così com'è agli argomenti. A differenza della risoluzione del costruttore, anche se il risultato dell'attributo è `null` non torna alla risoluzione del valore predefinito.

Ecco un esempio che legge la chiave inesistente `courier.missing`, in assenza di altri binding contestuali.

```php theme={null}
use Acme\Courier\Attributes\CourierConfig;

final class NullableTimeoutOptions
{
    public function __construct(
        #[CourierConfig('missing')] public ?int $timeout = 99,
    ) {}
}

$readTimeout = function (
    #[CourierConfig('missing')] ?int $timeout = 99,
): ?int {
    return $timeout;
};
```

| Chiamata | Risultato | Motivo |
| - | - | - |
| `app()->make(NullableTimeoutOptions::class)->timeout` | `99` | L'attributo restituisce `null` e si passa al valore predefinito del primitivo |
| `app()->call($readTimeout)` | `null` | Il risultato dell'attributo viene passato così com'è |
| `$readTimeout()` | `99` | È una normale chiamata PHP e non risolve l'attributo |

Anche `new CourierOptions(timeout: 5, retries: 1)` è una normale creazione in PHP. Il solo fatto di applicare un attributo non aggiunge l'iniezione delle dipendenze a un `new` diretto o alle chiamate di metodo.

> [!NOTE]
>
> Se ti serve lo stesso comportamento predefinito sia nel costruttore sia nelle callable, non affidarti solo al valore predefinito del parametro, ma definisci il valore predefinito lato attributo, ad esempio `#[CourierConfig('timeout', default: 30)]`. Questo `default` viene usato quando la chiave di configurazione non esiste e non sostituisce il valore di una chiave esistente che vale `null`. Nelle API che trattano `null` come un valore significativo, verifica separatamente i due percorsi di risoluzione.

## Distinguere la reiniezione dei valori dalla durata dei servizi

L'attributo legge la configurazione nel momento in cui risolve il parametro. Non è una funzionalità che osserva o aggiorna le proprietà di un `CourierOptions` già creato.

Ad esempio, se registri `CourierOptions` come singleton, i valori iniettati la prima volta restano conservati in quell'istanza. Anche se in seguito modifichi la configurazione e chiami un normale `make()`, i valori della stessa istanza non cambiano.

Anche quando crei un attributo che legge informazioni legate alla singola richiesta, non è l'attributo stesso a determinare una durata singleton o scoped. Scegli la durata del servizio che conserva i valori o gli oggetti iniettati seguendo [Progettare il ciclo di vita dei servizi di un pacchetto](https://kawax.biz/it/advanced/package-service-lifetimes.md). Considera separatamente anche la rigenerazione della cache di configurazione e l'aggiornamento dei processi a lunga durata o dei servizi già creati.

## Testare i percorsi di risoluzione

I test del solo attributo si possono scrivere, come nei test ufficiali, usando un nuovo `Container` e `Illuminate\Config\Repository`. Il seguente esempio PHPUnit presuppone un ambiente di test in cui siano caricati `CourierConfig`, `CourierOptions` e `NullableTimeoutOptions` definiti sopra.

```php theme={null}
use Acme\Courier\Attributes\CourierConfig;
use Acme\Courier\CourierOptions;
use Illuminate\Config\Repository;
use Illuminate\Container\Container;
use Illuminate\Contracts\Container\Container as ContainerContract;
use PHPUnit\Framework\TestCase;

class CourierConfigTest extends TestCase
{
    private function container(array $settings = []): Container
    {
        $container = new Container;
        $container->instance('config', new Repository([
            'courier' => $settings,
        ]));

        return $container;
    }

    public function testConfigDefaultsAndNamedOverrides(): void
    {
        $container = $this->container();
        $this->assertSame(30, $container->make(CourierOptions::class)->timeout);

        $container->make('config')->set('courier.timeout', 45);
        $container->when(CourierOptions::class)->needs('$timeout')->give(77);
        $this->assertSame(45, $container->make(CourierOptions::class)->timeout);

        $options = $container->makeWith(CourierOptions::class, ['timeout' => 5]);
        $this->assertSame(5, $options->timeout);
        $this->assertSame(3, $options->retries);
    }

    public function testParameterNameIsAvailableToTheResolver(): void
    {
        $container = $this->container(['timeout' => 45]);

        $value = $container->call(function (
            #[CourierConfig(default: 30)] int $timeout,
        ): int {
            return $timeout;
        });

        $this->assertSame(45, $value);
    }

    public function testRegisteredHandlerOverridesResolve(): void
    {
        $container = $this->container(['timeout' => 45]);
        $container->whenHasAttribute(
            CourierConfig::class,
            static fn (CourierConfig $attribute, ContainerContract $app) => 7,
        );

        $this->assertSame(7, $container->make(CourierOptions::class)->timeout);
    }

    public function testNullDiffersBetweenMakeAndCall(): void
    {
        $container = $this->container();
        $readTimeout = function (
            #[CourierConfig('missing')] ?int $timeout = 99,
        ): ?int {
            return $timeout;
        };

        $this->assertSame(99, $container->make(NullableTimeoutOptions::class)->timeout);
        $this->assertNull($container->call($readTimeout));
        $this->assertSame(99, $readTimeout());
    }

    public function testSingletonKeepsItsResolvedValues(): void
    {
        $container = $this->container(['timeout' => 45]);
        $container->singleton(CourierOptions::class);
        $options = $container->make(CourierOptions::class);

        $container->make('config')->set('courier.timeout', 60);

        $this->assertSame($options, $container->make(CourierOptions::class));
        $this->assertSame(45, $options->timeout);
    }
}
```

L'esempio in cui l'handler restituisce sempre `7` serve solo a testare l'ordine di priorità. All'interno di questo container influisce su tutte le risoluzioni dello stesso attributo, incluso `retries`.

Questo test unitario da solo non verifica l'integrazione con la registrazione del provider del pacchetto, con `mergeConfigFrom()` o con la cache di configurazione. Verifica anche, nell'ambiente applicativo descritto in [Testare pacchetti Laravel con Orchestra Testbench](https://kawax.biz/it/advanced/package-testing.md), gli stati prima della pubblicazione, dopo la pubblicazione e con la cache di configurazione attiva.

### Controlli prima del rilascio

* Tratta come API pubblica il nome della classe dell'attributo, i nomi dei parametri del costruttore, le chiavi di configurazione e il tipo di ritorno. Fai attenzione anche alle modifiche di `key` e `default`, usati come argomenti con nome.
* Se la specifica prevede l'uso del nome del parametro quando la chiave è omessa, verifica che rinominare un parametro nella classe utente non cambi la configurazione a cui si fa riferimento.
* Testa separatamente i binding esistenti senza attributo, la risoluzione con attributo e l'override tramite nome del parametro.
* Testa `null`, la configurazione mancante e i tipi non validi sia con `make()` sia con `call()`.
* Verifica che il design non faccia conservare valori obsoleti a singleton o worker a lunga durata.
* Quando aggiorni le versioni di Laravel supportate, ricontrolla sul sorgente ufficiale e sui test gli argomenti del resolver, l'ordine di priorità e la post-elaborazione degli attributi.

## Pagine correlate

> [card] [Merge e cache della configurazione dei pacchetti](https://kawax.biz/it/advanced/package-config-merging.md)
>
> Verifica la configurazione predefinita, la configurazione pubblicata e i confini di aggiornamento della cache.

> [card] [Progettare il ciclo di vita dei servizi di un pacchetto](https://kawax.biz/it/advanced/package-service-lifetimes.md)
>
> Scegli la durata dei servizi che conservano i valori iniettati.

> [card] [Tag del container e aggregazione di plugin nei pacchetti](https://kawax.biz/it/advanced/package-container-tags.md)
>
> Verifica l'attributo integrato Tag e l'aggregazione delle implementazioni registrate.

> [card] [PHP Reflection API](https://kawax.biz/it/advanced/php-reflection.md)
>
> Verifica il meccanismo che legge i metadati di attributi e parametri.

## Fonti primarie consultate

* [Documentazione ufficiale di Laravel: Defining custom attributes](https://github.com/laravel/docs/blob/960eedf0911155ad16326ade49c0ea5d18eda0b8/container.md#defining-custom-attributes)
* [Documentazione ufficiale di Laravel: configurazione dei pacchetti](https://github.com/laravel/docs/blob/960eedf0911155ad16326ade49c0ea5d18eda0b8/packages.md#configuration)
* [Container: resolveDependencies(), resolveFromAttribute(), whenHasAttribute() e post-elaborazione degli attributi](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Container/Container.php)
* [BoundMethod: risoluzione degli argomenti delle callable](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Container/BoundMethod.php)
* [Util: rilevamento dei ContextualAttribute tramite Reflection](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Container/Util.php)
* [ContextualAttribute: interfaccia marcatore](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Contracts/Container/ContextualAttribute.php)
* [Config: attributo di configurazione integrato](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Container/Attributes/Config.php)
* [Config Repository: get() e lettura dei valori di configurazione](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Config/Repository.php)
* [Arr: valore predefinito di get() e gestione dei null esistenti](https://github.com/laravel/framework/blob/v13.39.0/src/Illuminate/Collections/Arr.php)
* [Test ufficiali: resolver degli attributi, handler registrati, ReflectionParameter e risoluzione con call](https://github.com/laravel/framework/blob/v13.39.0/tests/Container/ContextualAttributeBindingTest.php)


## Related topics

- [Registrazione dei comandi Artisan di un pacchetto e risoluzione delle dipendenze](https://kawax.biz/it/advanced/package-commands.md)
- [Tag del container e aggregazione di plugin nei pacchetti](https://kawax.biz/it/advanced/package-container-tags.md)
- [Service container](https://kawax.biz/it/service-container.md)
- [Hook di risoluzione dei servizi nei pacchetti](https://kawax.biz/it/advanced/package-service-resolution.md)
- [Registrazione e cache dei listener di eventi di un pacchetto](https://kawax.biz/it/advanced/package-events.md)


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