Login to bookmark this video
18.

Servicio de fábrica de correos electrónicos

|

Share this awesome video!

|

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:

56 lines | src/Email/BookingEmailFactory.php
// ... 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:

56 lines | src/Email/BookingEmailFactory.php
// ... 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:

56 lines | src/Email/BookingEmailFactory.php
// ... 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:

56 lines | src/Email/BookingEmailFactory.php
// ... 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 $trippor $booking->getTrip(). Por último, ->htmlTemplate('email/booking_confirmation.html.twig'):

56 lines | src/Email/BookingEmailFactory.php
// ... 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:

56 lines | src/Email/BookingEmailFactory.php
// ... 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:

61 lines | src/Controller/TripController.php
// ... 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):

61 lines | src/Controller/TripController.php
// ... 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'):

57 lines | src/Email/BookingEmailFactory.php
// ... 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.