Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Integración mejorada de Docker y correos de prueba

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $10.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Symfony ha tenido soporte para Docker durante un tiempo, en particular, para ayudar al desarrollo web local. Por ejemplo, tengo PHP instalado localmente. Así que no estoy usando Docker para obtener el propio PHP. Pero mi proyecto tiene un archivo docker-compose.yml que define un servicio de base de datos. Recuerda que el servidor web local que estamos utilizando proviene del binario Symfony... y es inteligente. Detecta automáticamente que tengodocker-compose ejecutándose con un servicio database... y así lee los parámetros de conexión de este contenedor y los expone como una variable de entorno DATABASE_URL.

¡Comprueba esto! En cualquier página, haz clic en la barra de herramientas de depuración web. Asegúrate de que estás en "Petición/Respuesta", y luego ve a "Parámetros del servidor". Desplázate hacia abajo para encontrarDATABASE_URL configurado como (en mi caso) 127.0.0.1 en el puerto 56239. Tal y como está configurado midocker-compose.yml, creará un nuevo puerto aleatorio cada vez que se inicie.

... line 1
services:
database:
image: 'mysql:8.0'
environment:
MYSQL_ROOT_PASSWORD: password
ports:
# To allow the host machine to access the ports below, modify the lines below.
# For example, to allow the host to connect to port 3306 on the container, you would change
# "3306" to "3306:3306". Where the first port is exposed to the host and the second is the container port.
# See https://docs.docker.com/compose/compose-file/#ports for more information.
- '3306'

El binario de Symfony averiguará entonces de qué puerto aleatorio se trata y creará la variable de entorno en consecuencia. Finalmente, como es normal, gracias a nuestra configuración deconfig/packages/doctrine.yaml, la variable de entorno DATABASE_URL se utiliza para hablar con la base de datos. Así que el binario de Symfony más Docker es una buena manera de arrancar rápida y fácilmente servicios externos como una base de datos, una búsqueda elástica, o más.

Nueva integración de Docker con las recetas de Flex

Recientemente, Symfony ha llevado esto al siguiente nivel. En Symfony.com, encontrarás una entrada de blog llamada Introducing Docker support. La idea es bastante sencilla. Cuando instalas un nuevo paquete -Doctrine, por ejemplo- la receta de ese paquete puede venir con alguna configuración de Docker. Y así, con sólo instalar el paquete, obtienes la configuración de Docker automáticamente.

¡Veamos esto en acción! Puesto que ya tenemos Doctrine instalado, instalemos Mailer, que vendrá con docker-compose config para un servicio llamado MailCatcher. En tu terminal, ejecuta:

composer require mailer

¡Impresionante! Nos detiene y nos pregunta:

La receta de este paquete contiene alguna configuración de Docker. ¿Quieres incluir la configuración Docker de las recetas?

Voy a decir p por "Sí permanentemente". Si no quieres las cosas de Docker, ¡no te preocupes! Contesta no o "No permanentemente" y no te lo volverá a preguntar.

Y... ¡listo! Ahora podemos ejecutar

git status

para ver que ha actualizado las cosas normales, pero también nos ha dado un nuevodocker-compose.override.yml. Si no estás familiarizado, Docker leerá primerodocker-compose.yml y luego leerá docker-compose.override.yml. El propósito del archivo de anulación es cambiar la configuración que es específica de tu máquina, en este caso, nuestra máquina local.

... lines 1 - 2
services:
###> symfony/mailer ###
mailer:
image: schickling/mailcatcher
ports: [1025, 1080]
###< symfony/mailer ###

El nuevo archivo añade un servicio llamado mailer... que arranca algo llamado MailCatcher. MailCatcher es una herramienta de depuración local que inicia un servidor SMTP al que puedes enviar correos electrónicos. Y luego te ofrece una interfaz gráfica de usuario web en la que puedes revisar esos correos electrónicos... dentro de una bandeja de entrada ficticia.

Este servicio vive dentro de docker-compose.override.yml porque sólo queremos que este servicio se ejecute localmente cuando estemos haciendo desarrollo local. Si utilizas Docker para desplegar tu sitio, tendrás una configuración local diferente para producción. Si no estás desplegando con Docker, toda esta configuración puede vivir en tu archivo principal docker-compose.yml si lo deseas.

Probar MailCatcher

De todos modos, antes de empezar a utilizar este servicio, vamos a configurarlo para enviar un correo electrónico. Abre src/Controller/RegistrationController.php. Ya estamos utilizandosymfonycasts/verify-email-bundle... pero en lugar de enviar realmente el correo electrónico de verificación, sólo vamos a poner la URL de verificación directamente en un mensaje flash. Fue un atajo que hice durante el tutorial de Seguridad.

... lines 1 - 16
class RegistrationController extends AbstractController
{
... line 19
public function register(Request $request, UserPasswordHasherInterface $userPasswordHasher, VerifyEmailHelperInterface $verifyEmailHelper, EntityManagerInterface $entityManager): Response
{
... lines 22 - 24
if ($form->isSubmitted() && $form->isValid()) {
... lines 26 - 43
// TODO: in a real app, send this as an email!
$signedUrl = $signatureComponents->getSignedUrl();
$this->addFlash('success', sprintf(
'Confirm your email at: %s',
$signedUrl
));
... lines 50 - 51
}
... lines 53 - 56
}
... lines 58 - 88
}

Pero ahora, vamos a enviar un correo electrónico real. Iré al final de la clase y pegaré una nueva función privada, que puedes obtener de los bloques de código de esta página. Vuelve a escribir la "e" en MailerInterface y pulsa "tab" para añadir esa declaración de use... y haz lo mismo con la "l" en Email. Selecciona la de Symfony\Component\Mime.

... lines 1 - 8
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
... lines 11 - 19
class RegistrationController extends AbstractController
{
... lines 22 - 92
private function sendVerificationEmail(MailerInterface $mailer, User $user, string $signedUrl)
{
$email = (new Email())
->from('hello@example.com')
->to($user->getEmail())
//->cc('cc@example.com')
//->bcc('bcc@example.com')
//->replyTo('fabien@example.com')
//->priority(Email::PRIORITY_HIGH)
->subject('Verify your email on Cauldron Overflow!')
->text('Please, follow the link to verify your email!')
->html(sprintf('<a href="%s">%s</a>', $signedUrl, $signedUrl));
$mailer->send($email);
}
}

¡Perfecto! Esto enviará un correo electrónico de verificación muy sencillo que sólo contiene el enlace de verificación.

Ahora, en el método register(), añade un nuevo argumento al final:MailerInterface $mailer. Luego, aquí abajo, elimina el TODO... y sustitúyelo por $this->sendVerificationEmail() pasando por $mailer, $user, y $signedUrl. Finalmente, en el flash success, cambia el mensaje para decirle al usuario que debe comprobar su correo electrónico.

... lines 1 - 22
public function register(Request $request, UserPasswordHasherInterface $userPasswordHasher, VerifyEmailHelperInterface $verifyEmailHelper, EntityManagerInterface $entityManager, MailerInterface $mailer): Response
{
... lines 25 - 27
if ($form->isSubmitted() && $form->isValid()) {
... lines 29 - 47
$this->sendVerificationEmail($mailer, $user, $signedUrl);
$this->addFlash('success', sprintf(
'Confirm your email - the verify link was sent to %s',
$user->getEmail()
));
... lines 53 - 54
}
... lines 56 - 59
}
... lines 61 - 109

Bien, ya tenemos este nuevo archivo docker-compose.override.yml con MailCatcher. Sin embargo, ese contenedor aún no se está ejecutando. Pero, ignora eso por un momento... y veamos si podemos hacer funcionar el correo electrónico.

Vuelve a la página de Registro... ¡Ups! Obtenemos un error:

Variable de entorno no encontrada: "MAILER_DSN".

¡Por supuesto! El servicio mailer necesita esta variable de entorno para indicarle dónde debe enviar los correos electrónicos. Puedes encontrarla dentro de .env: la receta del mailer nos dio la var. de entorno MAILER_DSN, pero está comentada. Descomenta eso.

34 lines .env
... lines 1 - 30
###> symfony/mailer ###
MAILER_DSN=null://null
###

Por defecto, envía los correos a lo que se llama "transporte nulo"... lo que significa que cuando enviamos correos... no van a ninguna parte. No se entregan realmente... lo que es una buena configuración para el desarrollo.

Refresca, añade una dirección de correo electrónico falsa, regístrate y... ¡funciona! Por supuesto, no envió el correo electrónico a ninguna parte... pero aún podemos ver, más o menos, cómo sería el correo electrónico.

¿Cómo? Haz clic en cualquier enlace para entrar en el Perfilador, haz clic en "Últimos 10", busca la petición POST de /register y haz clic en ella. Aquí abajo, ve a la sección "Correos electrónicos" y... ¡voilà! Muestra nuestro correo electrónico con una vista previa en HTML. Y vaya que es feo... pero eso es culpa mía. Por cierto, la vista previa HTML es una nueva característica de Symfony 5.4.

Iniciando el servicio MailCatcher

Bien, esto es genial. Pero veamos cómo MailCatcher también puede ayudarnos a depurar los correos electrónicos. En primer lugar, si aún no tienes un archivo docker-compose.yml, crea uno. Todo lo que necesitas es la línea version en la parte superior. Así tendremos un archivo docker-compose.yml y un archivodocker-compose.override.yml.

Ahora, busca tu terminal y ejecuta:

docker-compose up -d

Ya tengo docker-compose ejecutándose para mi contenedor de base de datos, pero esto iniciará ahora el contenedor mailer, que inicializará un nuevo servidor SMTP de captación de correo.

Vale... entonces, ¿cómo configuramos mailer para que entregue a este servidor SMTP desde MailCatcher? De todas formas, ¿en qué puerto está funcionando ese servidor SMTP? La respuesta es... ¡no lo sabemos! Y no nos importa.

Observa esto. Vuelve a cualquier página, actualiza... y luego haz clic en el Perfilador. Una vez más, asegúrate de que estás en la sección "Petición/Respuesta" y luego ve a "Parámetros del servidor". Desplázate hasta MAILER_URL.

¡Woh! MAILER_URL se ha convertido de repente en smtp://127.0.0.1:65320!

Esto es lo que ha ocurrido. Cuando iniciamos el servicio mailer, Docker expuso el puerto1025 de ese contenedor -que es el servidor SMTP- a un puerto aleatorio de mi máquina anfitriona. El binario de Symfony vio eso, leyó el puerto aleatorio y luego, al igual que con la base de datos, expuso una variable de entorno MAILER_URL que apunta a él. En otras palabras, ¡nuestros correos electrónicos ya se enviarán a MailCatcher!

¡Vamos a probarlo! Me registraré de nuevo con otra dirección de correo electrónico, aceptaré las condiciones y... ¡genial! ¡No hay error! Para ver el correo electrónico, podríamos volver a entrar en el Perfilador como hemos hecho hace un momento. Pero en teoría, si eso se envió a MailCatcher, deberíamos poder ir a la interfaz de usuario de MailCatcher y revisar el mensaje allí. La pregunta es: ¿dónde está la UI de MailCatcher? ¿En qué puerto se está ejecutando? Porque también se está ejecutando en un puerto aleatorio.

Para ayudarte con esto, pasa el ratón por encima de la sección "Servidor" de la barra de herramientas de depuración web. Puedes ver que detecta que docker-compose se está ejecutando, está exponiendo algunas variables de entorno de Docker, ¡e incluso ha detectado Webmail! Haz clic en "Abrir" para entrar en MailCatcher... ¡y ahí está nuestro correo electrónico!

Si envías más correos, aparecerán aquí como una pequeña bandeja de entrada.

Y... ¡eso es todo! ¡Felicidades! ¡Acabas de actualizar tu aplicación a Symfony 6! ¡Y a PHP 8! ¡Y a los atributos de PHP! ¡Qué cosas más chulas!

Si tienes alguna pregunta o te encuentras con algún problema durante la actualización del que no hayamos hablado, estamos aquí para ti en los comentarios. Muy bien, amigos, ¡hasta la próxima!

Leave a comment!

8
Login or Register to join the conversation
Fabrice Avatar

Hey, you talked about ElasticSearch in this video, why not make a course for ElasticSearch ? it could be awesome!

1 Reply

Hey Fabrice

Thanks for telling us what you'd like to learn next. I can't guarantee anything but we take into account all the requests

Cheers!

1 Reply
SaronGrave Avatar
SaronGrave Avatar SaronGrave | posted hace 4 meses

Hi there! Great video. But I am having a small problem with Chapter 19 ... I'm using the latest full symfony-docker from https://github.com/dunglas/symfony-docker. I have successfully added symfony/mailer package and can see the mailcatcher docker running. But the MAILER_* server variables are just not there... Is this simply not fit for the symfony-docker? or did symfony 6.1 break/change something? already tried clearing caches and rebuilding everything...

Reply

Hey SaronGrave,

Did you update your docker-compose.yaml config file after installing Mailer?
Oh, and how are you running your web server? Remember it has to be executed through the Symfony CLI so it can inject the env vars symfony server:run

Cheers!

Reply
Steve-D Avatar

I've completed the course and the final challenge however the course shows as 92% complete. Any idea what I've missed?

Cheers

Reply

Hey Steve,

Please, take a look at the table of content on this page: https://symfonycasts.com/sc... - please make sure you have a "check" icon for every chapter in that list. If anything is missing - you would need to watch that video till the end to get a credit for it and mark it as viewed with the "check" icon. Also, make sure that all challenges in that list are green, if you missed any or have some red there - try to complete them again.

If you still have this problem and don't know - please, contact us via our contact form: https://symfonycasts.com/co... - so we know your email and could take a look at your account :)

Cheers!

Reply
Steve-D Avatar

HI Victor

I actual had the first video unticked... however I'm still not 100%. The only thing I can see now is the final challenge is not green despite having done it correctly several times. I'll drop a message in via the contact form. Thank you

Reply

Hey Steve,

No problem, I noticed your email and already replied to you :) Now you have that cert ;)

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^8.0.2",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "babdev/pagerfanta-bundle": "^3.6", // v3.6.1
        "composer/package-versions-deprecated": "^1.11", // 1.11.99.5
        "doctrine/annotations": "^1.13", // 1.13.2
        "doctrine/dbal": "^3.3", // 3.3.5
        "doctrine/doctrine-bundle": "^2.0", // 2.6.2
        "doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.2
        "doctrine/orm": "^2.0", // 2.11.2
        "knplabs/knp-markdown-bundle": "^1.8", // 1.10.0
        "knplabs/knp-time-bundle": "^1.18", // v1.18.0
        "pagerfanta/doctrine-orm-adapter": "^3.6", // v3.6.1
        "pagerfanta/twig": "^3.6", // v3.6.1
        "sensio/framework-extra-bundle": "^6.0", // v6.2.6
        "sentry/sentry-symfony": "^4.0", // 4.2.8
        "stof/doctrine-extensions-bundle": "^1.5", // v1.7.0
        "symfony/asset": "6.0.*", // v6.0.7
        "symfony/console": "6.0.*", // v6.0.7
        "symfony/dotenv": "6.0.*", // v6.0.5
        "symfony/flex": "^2.1", // v2.1.7
        "symfony/form": "6.0.*", // v6.0.7
        "symfony/framework-bundle": "6.0.*", // v6.0.7
        "symfony/mailer": "6.0.*", // v6.0.5
        "symfony/monolog-bundle": "^3.0", // v3.7.1
        "symfony/property-access": "6.0.*", // v6.0.7
        "symfony/property-info": "6.0.*", // v6.0.7
        "symfony/proxy-manager-bridge": "6.0.*", // v6.0.6
        "symfony/routing": "6.0.*", // v6.0.5
        "symfony/runtime": "6.0.*", // v6.0.7
        "symfony/security-bundle": "6.0.*", // v6.0.5
        "symfony/serializer": "6.0.*", // v6.0.7
        "symfony/stopwatch": "6.0.*", // v6.0.5
        "symfony/twig-bundle": "6.0.*", // v6.0.3
        "symfony/ux-chartjs": "^2.0", // v2.1.0
        "symfony/validator": "6.0.*", // v6.0.7
        "symfony/webpack-encore-bundle": "^1.7", // v1.14.0
        "symfony/yaml": "6.0.*", // v6.0.3
        "symfonycasts/verify-email-bundle": "^1.7", // v1.10.0
        "twig/extra-bundle": "^2.12|^3.0", // v3.3.8
        "twig/string-extra": "^3.3", // v3.3.5
        "twig/twig": "^2.12|^3.0" // v3.3.10
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.1
        "phpunit/phpunit": "^9.5", // 9.5.20
        "rector/rector": "^0.12.17", // 0.12.20
        "symfony/debug-bundle": "6.0.*", // v6.0.3
        "symfony/maker-bundle": "^1.15", // v1.38.0
        "symfony/var-dumper": "6.0.*", // v6.0.6
        "symfony/web-profiler-bundle": "6.0.*", // v6.0.6
        "zenstruck/foundry": "^1.16" // v1.18.0
    }
}