Servicio de fábrica de correos electrónicos
Nuestra aplicación envía dos correos electrónicos: en SendBookingRemindersCommand
, y enTripController::show()
. Aquí hay... mucha duplicación. ¡Me duele la vista! ¡Pero no te preocupes! Podemos reorganizar esto en un servicio de fábrica de correos electrónicos. Y como tenemos pruebas que cubren ambos correos, podemos refactorizar y estar seguros de que no hemos roto nada. No me canso de decirlo: ¡me encantan las pruebas!
BookingEmailFactory
Empieza creando una nueva clase: BookingEmailFactory
en el espacio de nombres App\Email
. Añade un constructor, copia el argumento $termsPath
de TripController::show()
, pégalo aquí y conviértelo en una propiedad privada:
// ... lines 1 - 11 | |
class BookingEmailFactory | |
{ | |
public function __construct( | |
#[Autowire('%kernel.project_dir%/assets/terms-of-service.pdf')] | |
private string $termsPath, | |
) { | |
} | |
// ... lines 19 - 54 | |
} |
Ahora, crea dos métodos de fábrica: public function createBookingConfirmation()
, que aceptarán Booking $booking
, y devolverán TemplatedEmail
. Luego,public function createBookingReminder(Booking $booking)
también devolverá un TemplatedEmail
:
// ... lines 1 - 11 | |
class BookingEmailFactory | |
{ | |
// ... lines 14 - 19 | |
public function createBookingConfirmation(Booking $booking): TemplatedEmail | |
{ | |
// ... lines 22 - 25 | |
} | |
// ... line 27 | |
public function createBookingReminder(Booking $booking): TemplatedEmail | |
{ | |
// ... lines 30 - 33 | |
} | |
// ... lines 35 - 54 | |
} |
Crea un método para albergar esa maldita duplicación: private function createEmail()
, con argumentos Booking $booking
y string $tag
que devuelve un TemplatedEmail
:
// ... lines 1 - 11 | |
class BookingEmailFactory | |
{ | |
// ... lines 14 - 35 | |
private function createEmail(Booking $booking, string $tag): TemplatedEmail | |
{ | |
// ... lines 38 - 53 | |
} | |
} |
Salta a TripController::show()
, copia todo el código de creación del correo electrónico y pégalo aquí. Arriba, necesitamos dos variables: $customer = $booking->getCustomer()
y$trip = $booking->getTrip()
. Elimina attachFromPath()
, subject()
, yhtmlTemplate()
. En este TagHeader
, utiliza la variable $tag
pasada. Podemos dejar los metadatos igual. Por último, devuelve el $email
:
// ... lines 1 - 11 | |
class BookingEmailFactory | |
{ | |
// ... lines 14 - 35 | |
private function createEmail(Booking $booking, string $tag): TemplatedEmail | |
{ | |
$customer = $booking->getCustomer(); | |
$trip = $booking->getTrip(); | |
$email = (new TemplatedEmail()) | |
->to(new Address($customer->getEmail())) | |
->context([ | |
'customer' => $customer, | |
'trip' => $trip, | |
'booking' => $booking, | |
]) | |
; | |
$email->getHeaders()->add(new TagHeader($tag)); | |
$email->getHeaders()->add(new MetadataHeader('booking_uid', $booking->getUid())); | |
$email->getHeaders()->add(new MetadataHeader('customer_uid', $customer->getUid())); | |
return $email; | |
} | |
} |
Con nuestra lógica compartida en su sitio, úsala en createBookingConfirmation()
. Escribereturn $this->createEmail()
, pasando la variable $booking
y booking
para la etiqueta. Ahora, ->subject()
, copia esto de TripController::show()
, cambiando la variable $trip
por $booking->getTrip()
. Por último, ->htmlTemplate('email/booking_confirmation.html.twig')
:
// ... lines 1 - 11 | |
class BookingEmailFactory | |
{ | |
// ... lines 14 - 19 | |
public function createBookingConfirmation(Booking $booking): TemplatedEmail | |
{ | |
return $this->createEmail($booking, 'booking') | |
->subject('Booking Confirmation for '.$booking->getTrip()->getName()) | |
->htmlTemplate('email/booking_confirmation.html.twig') | |
; | |
} | |
// ... lines 27 - 54 | |
} |
Para createBookingReminder()
, copia el interior de createBookingConfirmation()
y pégalo aquí. Cambia la etiqueta a booking_reminder
, el asunto a Booking Reminder
, y la plantilla a email/booking_reminder.html.twig
:
// ... lines 1 - 11 | |
class BookingEmailFactory | |
{ | |
// ... lines 14 - 19 | |
public function createBookingConfirmation(Booking $booking): TemplatedEmail | |
{ | |
return $this->createEmail($booking, 'booking') | |
->subject('Booking Confirmation for '.$booking->getTrip()->getName()) | |
->htmlTemplate('email/booking_confirmation.html.twig') | |
; | |
} | |
// ... lines 27 - 54 | |
} |
El refactorizador
¡Ahora viene lo divertido! ¡Usar nuestra fábrica y eliminar un montón de código!
En TripController::show()
, en lugar de inyectar $termsPath
, inyectaBookingEmailFactory $emailFactory
:
// ... lines 1 - 18 | |
final class TripController extends AbstractController | |
{ | |
// ... lines 21 - 29 | |
public function show( | |
// ... lines 31 - 35 | |
BookingEmailFactory $emailFactory, | |
): Response { | |
// ... lines 38 - 58 | |
} | |
} |
Elimina todo el código de creación de correo electrónico y dentro de $mailer->send()
, escribe $emailFactory->createBookingConfirmation($booking)
:
// ... lines 1 - 18 | |
final class TripController extends AbstractController | |
{ | |
// ... lines 21 - 29 | |
public function show( | |
// ... lines 31 - 36 | |
): Response { | |
// ... lines 38 - 39 | |
if ($form->isSubmitted() && $form->isValid()) { | |
// ... lines 41 - 49 | |
$mailer->send($emailFactory->createBookingConfirmation($booking)); | |
// ... lines 51 - 52 | |
} | |
// ... lines 54 - 58 | |
} | |
} |
En SendBookingRemindersCommand
, de nuevo, elimina todo el código de creación de correo electrónico. Arriba en el constructor, autoconecta private BookingEmailFactory $emailFactory
:
// ... lines 1 - 18 | |
class SendBookingRemindersCommand extends Command | |
{ | |
public function __construct( | |
// ... lines 22 - 24 | |
private BookingEmailFactory $emailFactory, | |
) { | |
// ... line 27 | |
} | |
// ... lines 29 - 48 | |
} |
Aquí abajo, dentro de $this->mailer->send()
, escribe $this->emailFactory->createBookingReminder($booking)
:
// ... lines 1 - 18 | |
class SendBookingRemindersCommand extends Command | |
{ | |
// ... lines 21 - 29 | |
protected function execute(InputInterface $input, OutputInterface $output): int | |
{ | |
// ... lines 32 - 37 | |
foreach ($io->progressIterate($bookings) as $booking) { | |
$this->mailer->send($this->emailFactory->createBookingReminder($booking)); | |
// ... line 40 | |
} | |
// ... lines 42 - 47 | |
} | |
} |
Pruébalo
Oh, sí, ¡qué bien me ha sentado! ¿Pero hemos roto algo? Los canadienses tenemos fama de ser un poco salvajes. Compruébalo ejecutando las pruebas:
bin/phpunit
¡Uh oh, un fallo! Menos mal que tenemos estas pruebas, ¿eh?
El fallo viene de BookingTest
:
El mensaje no incluye un archivo con nombre de archivo [Condiciones del servicio.pdf].
Arréglalo
¡Fácil de arreglar! Durante nuestra refactorización, olvidé adjuntar el emocionante PDF de las condiciones del servicio al correo electrónico de confirmación de la reserva. Y nuestros clientes dependen de ello. BuscaBookingEmailFactory::createBookingConfirmation()
, y añade->attachFromPath($this->termsPath, 'Terms
of Service.pdf'):
// ... lines 1 - 11 | |
class BookingEmailFactory | |
{ | |
// ... lines 14 - 19 | |
public function createBookingConfirmation(Booking $booking): TemplatedEmail | |
{ | |
return $this->createEmail($booking, 'booking') | |
// ... lines 23 - 24 | |
->attachFromPath($this->termsPath, 'Terms of Service.pdf') | |
; | |
} | |
// ... lines 28 - 55 | |
} |
Vuelve a ejecutar las pruebas:
bin/phpunit
¡Pasadas! ¿Reforzamiento satisfactorio? ¡Comprobado!
A continuación, vamos a cambiar un poco de marcha y sumergirnos en dos nuevos componentes Symfony para consumir los eventos webhook de correo electrónico de Mailtrap.