v13.39.0. Per l’invio di base consulta Invio di email, per la preparazione del provider consulta Sviluppo di pacchetti.
Separare il nome del Mailer dal nome del Transport
Mail::mailer('receipts') legge mail.mailers.receipts e invoca la callback di creazione corrispondente a transport. MailManager::build() passa quel Transport al Mailer di Laravel e, nella normale risoluzione per nome, imposta anche il mittente globale e altri valori.
Le callback di creazione personalizzate hanno la precedenza sui Transport integrati. Non sovrascrivere smtp o ses: usa un nome di Transport specifico del pacchetto.
Costruire il percorso di registrazione senza invii esterni
Per prima cosa verifica la configurazione e il percorso di risoluzione, non l’API di destinazione. Insrc/Transport/MemoryTransport.php del pacchetto Acme\Postbox, crea una classe che estende l’ArrayTransport standard.
ArrayTransport::send() salva il SentMessage di Symfony in una Collection interna. Puoi consultarlo con messages() e cancellare i messaggi registrati da quell’istanza con flush(). L’etichetta serve a verificare che la configurazione sia arrivata alla callback di creazione.
Registrare sul Manager caricato in modo differito
La documentazione ufficiale chiamaMail::extend() in boot(). In un pacchetto puoi combinarlo con un hook successivo alla risoluzione del servizio, così da non creare inutilmente il Manager nelle applicazioni che non usano la posta.
Crea src/PostboxServiceProvider.php.
- Il
MailServiceProviderdi Laravel è un provider differito e registramail.managercome singleton. Anche la facadeMailrisolve questa chiave. - Se il Manager non è ancora stato risolto,
callAfterResolving()attende la risoluzione successiva; se è già stato risolto, applica subito l’estensione. Non sostituisce i Mailer già creati. - Evita di rendere semplicemente
DeferrableProvideranche il tuo provider che registra l’estensione. Fai in modo che il Transport personalizzato non venga richiesto prima della registrazione dell’hook. - In questo esempio l’hook viene registrato nel tuo
boot(). Non è garantito un ordine di avvio in cui altri provider usino il Mailer personalizzato prima di quel momento. Progetta l’ordine di registrazione e non inviare email all’interno diregister().
Non confondere gli argomenti di extend con quelli di altri Manager
MailManager::createSymfonyTransport() invoca la callback di creazione personalizzata con un solo array di configurazione. Non sono i due argomenti applicazione e configurazione di Cache::extend(). Il valore restituito è un Transport che implementa TransportInterface, non un Mailer di Laravel né un risultato di invio.
Inoltre MailManager::extend() si limita a conservare la closure e non la ricollega al Manager. Nell’esempio si usano closure statiche e argomenti espliciti per non dipendere dall’interpretazione di $this.
Se serve un client per un’API esterna, applica la configurazione in questa callback di creazione e passalo al Transport. Non effettuare invii al momento della registrazione e progetta il codice in modo che configurazioni e destinazioni non si mescolino quando più Mailer usano lo stesso nome di Transport.
Aggiungere i Mailer nell’applicazione che usa il pacchetto
Aggiungi le due voci seguenti all’arraymailers esistente in config/mail.php. Il pacchetto non deve sovrascrivere il valore predefinito dell’utente né l’intero array mailers.
MAIL_MAILER va indicato il nome del Mailer receipts, non il nome del Transport. Specificandolo esplicitamente puoi usarlo senza modificare il valore predefinito esistente.
raw() è un esempio per verificare il percorso di invio sincrono senza preparare una view. Anche nel normale invio con le Mailable, il Mailer genera il contenuto e poi lo passa allo stesso Transport.
Confini nella creazione di un vero Transport di consegna
Per un’implementazione che consegna a un servizio esterno, seguendo la documentazione ufficiale puoi estendereSymfony\Component\Mailer\Transport\AbstractTransport e implementare doSend(SentMessage $message): void e __toString(): string. Il metodo di registrazione visto sopra resta invariato.
Mailer::sendSymfonyMessage() di Laravel chiama send() del Transport. Se un listener di MessageSending annulla l’invio o il Transport restituisce null, anche send() di Laravel non restituisce un risultato di invio. Non considerare la sola assenza di eccezioni come prova di una consegna riuscita. Anche se viene restituito un SentMessage, non c’è garanzia che il messaggio abbia raggiunto la casella del destinatario.
Distinguere la modifica della configurazione dalla nuova risoluzione del Mailer
MailManager conserva i Mailer e i relativi Transport per nome del Mailer. Modificare la configurazione o registrare di nuovo extend() non aggiorna gli oggetti già creati.
$same è lo stesso Mailer di $before. In $after, ottenuto dopo purge('receipts'), il Transport viene creato dalla nuova configurazione.
L’implementazione di
purge() esaminata si limita a rimuovere il riferimento dall’array interno del Manager. Il vecchio $before e i servizi in cui quel Mailer è già stato iniettato non vengono sostituiti. Non è nemmeno un’operazione che chiude esplicitamente le connessioni esterne.
Anche i Mailer padre come failover vengono conservati separatamente
failover e roundrobin leggono la configurazione dei nomi dei Mailer figli, creano direttamente i Transport e li incorporano nel Transport padre. Non condividono le istanze Mail::mailer() dei figli.
Per questo, anche se modifichi la configurazione di un figlio e chiami purge('receipts'), il Transport interno del Mailer padre già risolto non viene aggiornato. Occorre risolvere di nuovo anche il Mailer padre da aggiornare. Nemmeno forgetMailers() può sostituire i riferimenti al Mailer padre già conservati dal codice chiamante.
In produzione, includi nella normale procedura di deploy l’aggiornamento della cache di configurazione e il riavvio dei worker di lunga durata. Non considerare la nuova risoluzione come un aggiornamento simultaneo di tutti i processi e consulta anche Ciclo di vita dei servizi.
Testare il vero percorso di creazione, non solo Mail::fake
Mail::fake() serve a registrare l’invio e l’accodamento delle Mailable. MailFake::mailer() non risolve il vero Mailer: registra il nome del Mailer e restituisce il fake stesso. Anche raw() non esegue alcun invio. Le asserzioni sull’invio delle Mailable possono quindi passare anche senza che il Transport personalizzato sia registrato.
Nell’ambiente di test del pacchetto, fai passare il codice dal vero Manager e dal vero Transport senza usare il fake, come segue. Il Transport di questo esempio non invia all’esterno, quindi puoi verificare il percorso di registrazione senza rischio di consegne reali. Un provider ausiliario viene caricato per primo, così da verificare anche il caso in cui il Manager sia già stato risolto.
Fonti primarie consultate
- Documentazione ufficiale di Laravel: Transport personalizzati e test delle email (alla data della verifica)
- Documentazione ufficiale di Laravel: Sviluppo di pacchetti
- MailManager: creazione, conservazione dei Mailer, purge e Transport composti
- MailServiceProvider: binding di MailManager
- ServiceProvider: callAfterResolving
- Mailer: invio al Transport ed eventi
- ArrayTransport: registrazione dei messaggi
- Facade Mail e MailFake
- MailFake: selezione del Mailer e registrazione delle Mailable
- Test ufficiali di Laravel: verifica dell’invio con Mailer e ArrayTransport