Vai al contenuto principale
A volte un pacchetto deve aggiungere un comportamento mantenendo l’implementazione del servizio scelta dall’applicazione che lo utilizza. Con extend() del container puoi avvolgere il servizio risolto in un decorator senza ridefinire la logica di creazione originale. Questa pagina fa riferimento al branch predefinito 13.x della documentazione ufficiale di Laravel e all’implementazione e ai test ufficiali dell’ultima release del Framework, v13.40.0, verificati il 9 novembre 2026. Oltre alla differenza rispetto agli hook di risoluzione dei servizi, spiega per quanto tempo viene mantenuta una decorazione, che cambia a seconda che un’istanza sia già salvata al momento della registrazione.

Separare l’aggiunta di configurazione dalla sostituzione dell’oggetto

La callback di extend() riceve il servizio di destinazione e il container. Puoi configurare lo stesso oggetto e restituirlo, oppure restituire un nuovo decorator. Se dimentichi il return, alla parte successiva della risoluzione viene passato null.
Qui si tratta di Illuminate\Container\Container::extend(). È un’API diversa dall’extend() dei manager come Cache o Mail, che registra il nome di un driver e la sua logica di creazione.

Creare un decorator che implementa lo stesso contratto

Avvolgere l’implementazione originale senza sostituirla

A scopo illustrativo, inserisci il seguente contratto per formattare i messaggi in src/MessageFormatter.php. Anche le classi seguenti si trovano ciascuna in un file PHP con lo stesso nome in src/.
L’implementazione non decorata restituisce l’input invariato.
Il decorator implementa la stessa interfaccia e delega al servizio ricevuto. In questo esempio, aggiunge un prefisso specifico del pacchetto al valore restituito dal delegato.
Se chi usa il pacchetto dichiara come tipo la classe concreta PlainMessageFormatter, un decorator che implementa solo la stessa interfaccia non può sostituirla. Esponi MessageFormatter come dipendenza e mantieni il contratto su argomenti, valori di ritorno, eccezioni e così via. Il solo inoltro dinamico dei metodi non sostituisce l’implementazione di un’interfaccia PHP.

Registrare la decorazione nel provider

src/PrefixServiceProvider.php, che fornisce la decorazione, non sovrascrive il modo in cui il servizio viene creato. Registra soltanto un’estensione nel container. Per il caricamento del provider consulta Struttura interna del package auto-discovery.
La callback riceve anche il container come secondo argomento, ma questo esempio non ne ha bisogno e quindi non lo dichiara. Usa l’argomento $formatter come servizio di destinazione. Chiamare di nuovo make() sulla stessa chiave all’interno della callback può causare una risoluzione circolare. Esegui il binding dell’implementazione originale nell’applicazione che usa il pacchetto, ad esempio in AppServiceProvider::register().
Puoi anche registrare extend() prima del bind() della destinazione. Se la destinazione non è stata risolta e non c’è un’istanza salvata, la sola registrazione non crea il servizio originale. Anche i test ufficiali verificano la creazione differita e la registrazione prima del binding. Successivamente, app(MessageFormatter::class)->format('Spedito.') restituisce [Acme] Spedito.. Anche se l’implementazione originale viene sostituita con un’altra implementazione di MessageFormatter, il decorator avvolge quella implementazione.
Come regola, registra prima della prima risoluzione. Un altro provider potrebbe usare il servizio per primo, quindi spostare semplicemente la registrazione in boot() non impedisce la risoluzione precedente. Inoltre, se rendi differito con leggerezza un provider che registra solo estensioni, la registrazione stessa potrebbe perdere la condizione che la avvia.

Capire dove si applica la decorazione e in quale ordine

In una risoluzione normale, il container crea il servizio originale, lo sostituisce con il valore restituito da ciascun extender nell’ordine di registrazione, lo salva come istanza condivisa e poi chiama gli hook successivi alla risoluzione. Se registri A e poi B, il risultato è B(A(servizio originale)). Le chiamate ai metodi degli utilizzatori partono dalla B esterna, quindi non confondere l’ordine di registrazione con l’ordine di avvio dell’elaborazione. Se dipendi dall’ordine di decorazione di più pacchetti, rendi esplicito l’ordine nell’applicazione e testa la combinazione. Gli extender si applicano alla chiave di risoluzione dopo che il container ha normalizzato gli alias. Una registrazione su MessageFormatter::class non viene applicata quando PlainMessageFormatter::class viene risolto direttamente solo perché i tipi coincidono. Usa sempre l’interfaccia.

La decorazione di un’istanza salvata viene mantenuta diversamente

Container::extend() in v13.40.0 si comporta diversamente a seconda che un’istanza della destinazione sia salvata nel container. L’ultima riga è importante. Se decori un singleton già risolto e poi chiami forgetInstance() o rifai il binding della stessa chiave, una decorazione applicata solo sul momento non viene riapplicata alla creazione successiva. È diverso dalla registrazione nell’array degli extender prima della prima risoluzione. Anche scoped() usa istanze salvate, quindi non dare per scontato che una decorazione registrata in ritardo a metà di uno scope resti registrata anche per lo scope successivo. Verificalo insieme a Progettare il ciclo di vita dei servizi di un pacchetto. Inoltre, se in seguito salvi direttamente un oggetto con instance(), questo non passa per il ciclo che applica gli extender nelle creazioni normali. Anche per le sostituzioni nei test, non è sempre vero che “essendo registrato, l’oggetto che passo viene decorato automaticamente”.

I riferimenti già distribuiti non vengono sostituiti automaticamente

Quando restituisci un nuovo wrapper, come in questo esempio, anche se il valore salvato nel container cambia, l’oggetto originale già salvato in una proprietà di un altro servizio non cambia. La notifica di rebinding() passa il nuovo servizio alle callback registrate. Non cerca né riscrive tutte le dipendenze. Anche la registrazione di una callback di notifica con rebinding() non è sempre differita. Se la destinazione ha già un binding, viene restituito il risultato di make() anche al momento della registrazione. Nel percorso in cui applichi extend() a un binding normale già risolto, make() può essere chiamato per la notifica se esiste una callback rebound. Non presumere in modo generale che inizializzazione ed effetti collaterali avvengano “al prossimo utilizzo”.

Evitare la doppia decorazione e lo stato trascinato

extend() non elimina automaticamente le registrazioni duplicate della stessa closure o dello stesso decorator. Se il register() di questo provider viene applicato due volte allo stesso container, anche il prefisso può essere aggiunto due volte.
  • Concentra la registrazione delle decorazioni nel percorso di avvio fisso del provider e non ripeterla per ogni richiesta o job.
  • Non prevenire i duplicati con un unico flag statico condiviso da tutti i container. Test e nuove applicazioni perderebbero registrazioni necessarie.
  • La decorazione di un singleton viene mantenuta per lo stesso periodo condiviso. Non fissare nel wrapper l’utente corrente, la richiesta o stati simili.
  • forgetExtenders() cancella solo l’array delle registrazioni. Non riporta allo stato originale i wrapper già creati.
Questo esempio modifica la stringa di output. Anche il prefisso, il numero di chiamate delegate e la gestione delle eccezioni diventano comportamento pubblico del pacchetto, quindi non trattarli come un semplice “cambiamento interno dell’implementazione” e verifica la compatibilità.

Testare i tempi di registrazione con lo stesso container

Il seguente esempio PHPUnit presuppone che le classi sopra possano essere caricate tramite l’autoload del pacchetto. Applica direttamente il register() del provider a un Container e verifica per quanto tempo viene mantenuta la decorazione, senza HTTP né servizi esterni. Verifica separatamente l’auto-discovery e l’avvio dell’intera applicazione con Testare pacchetti Laravel con Orchestra Testbench.
L’ultimo test non raccomanda la registrazione tardiva, ma rende esplicito il limite nella versione verificata. Non progettare forgetInstance() da solo come API per rimuovere o registrare di nuovo le decorazioni. Prima di una release, verifica anche l’ordine di più extender, le registrazioni duplicate, la sostituzione con instance() e i riferimenti già detenuti dagli utilizzatori. Quando aggiungi versioni di Laravel supportate, rileggi i rami di extend() e i test ufficiali, oltre alle chiavi di risoluzione e ai contratti dei tipi.

Pagine correlate

Hook di risoluzione dei servizi nei pacchetti

Illustra callAfterResolving(), che configura gli oggetti senza sostituirli, e le precauzioni sul numero di applicazioni.

Progettare il ciclo di vita dei servizi di un pacchetto

Illustra bind, singleton e scoped e le dipendenze mantenute dai servizi già creati.

Fonti primarie consultate

Ultima modifica il 9 novembre 2026