Alura > Cursos de Programação > Cursos de > Conteúdos de > Primeiras aulas do curso Symfony Framework: e-mail, processamento assíncrono, uploads e testes

Symfony Framework: e-mail, processamento assíncrono, uploads e testes

Envio de e-mail - Apresentação

Boas-vindas à Alura! O instrutor Vinicius Dias vai nos guiar nesse treinamento de Symfony Framework, onde vamos aprender diversos conceitos interessantes e evoluir ainda mais a nossa aplicação.

Vinicius Dias é uma pessoa de pele clara e olhos escuros, com cabelos pretos e curtos. Usa bigode e cavanhaque e veste uma camiseta azul escura. Está sentado em uma cadeira preta. Ao fundo, uma parede lisa com iluminação azul clara.

O que vamos aprender?

Vamos começar com o envio de um e-mail ao cadastrar uma nova série na nossa aplicação de controle de séries. Em outras palavras, vamos aprender a como enviar e-mails usando HTML e fazer testes de envio. Para isso, utilizaremos o Mailtrap e conhecemos também o MailCatcher.

Conversaremos sobre o impacto na performance de envio de e-mails. Por isso, vamos passar a enviar mensagens no plano de fundo, ou seja, utilizando o componente de Symfony chamado Messenger.

Com a utilização do Symfony Messenger, vamos enviar mensagens que vão permitir processamento em plano de fundo.

Esse processamento pode ser o envio de e-mails ou até a realização de LOGS, o qual também vamos aprender.

Além disso, poderemos ter imagens de capa para as séries cadastradas em nossa aplicação. Assim, vamos lidar com o upload de arquivos, aprendendo como trabalhar e mover arquivos.

Com isso, também devemos apagar esse arquivo ao excluir uma série. Para isso, vamos utilizar uma fila de processamento assíncrono mais uma vez.

Também vamos aprender a lidar com testes, criando testes de unidade, integração, aplicação para executá-los ao final do treinamento. Vamos entender o que cada conceito significa e escrever alguns testes na prática para a aplicação.

Pré-requisitos

Esperamos que você aproveite esse treinamento. Vamos relembrar alguns pré-requisitos?

Esse treinamento é a continuação de treinamentos anteriores de Symfony. Por isso, supomos que você já saiba sobre entidades, Doctrine, Forms, Encore.

Quer revisitar um desses conceitos? Confira a formação Symfony e Doctrine da Alura.

Também esperamos que você tenha uma boa noção de testes automatizados. Pois, vamos focar especificamente em Symfony no capítulo sobre testes. Não vamos cobrir o que é genérico sobre testes, uma vez que existem outros treinamentos na plataforma sobre o assunto.

Te esperamos no próximo vídeo para começar a melhorar a nossa aplicação!

Envio de e-mail - Código para enviar e-mail

Nesse treinamento, vamos adicionar funcionalidades ao sistema e conhecer mais sobre o framework Symfony.

Avisos iniciais

Como já foi falado, é importante que você tenha feito os treinamentos anteriores, pois continuaremos o projeto que começamos neles.

Mas, caso você esteja chegando nesse projeto agora e acredita que já domina o que foi ensinado nos treinamentos anteriores, o que você pode fazer é:

Assim você terá o banco e o sistema com as dependências. Podemos começar!

Objetivo

Começaremos subindo um servidor. No terminal, rodaremos php -s no localhost, na porta 8123:

php -s 0.0.0.0:8123 -t public/

Com isso, temos o servidor rodando e podemos acessar o nosso sistema no navegador com http://localhost:8123/series. Lembrando que com /series podemos visualizar a listagem das nossas séries, mas não podemos realizar nenhuma ação, pois ainda não logamos.

Então, clicamos no botão "Entrar" no canto superior esquerdo da página, ou acessamos http://localhost:8123/login. Preenchemos o formulário com o e-mail e senha que temos cadastrados e clicamos no botão "Login" no canto inferior esquerdo do formulário.

Já na tela de listagem de séries novamente, visualizamos os botões de ações. O que queremos realizar agora é:

Receber uma notificação ao adicionar uma nova série.

Ou seja, se alguém cadastrou uma série, queremos receber um e-mail informando que essa série foi cadastrada. Para isso, aprenderemos a enviar e-mails utilizando o Symfony.

Como enviar e-mails?

Vamos começar pesquisando na documentação do Symfony, neste link. No menu lateral esquerdo, rolamos a lista até a seção "Advanced Topics", onde temos o tópico "Mailer / Emails". Clicamos nele.

Essa página disponibiliza uma série de informações. A primeira é como instalar o Mailer, o que só é necessário se tivermos a versão "mínima" do Symfony. Esse não é o nosso caso, pois já temos tudo instalado. Além disso, temos definições de transports e outros assuntos mais básicos também.

Queremos ir diretamente para a parte de envio de e-mails, para entender como podemos fazer isso.

Nessa seção, "Creating & Sending Messages", observamos que precisaremos de um Mailer. Então, através de injeção de dependência, vamos receber o MailerInterface:

// código omitido

public function sendEmail(MailerInterface $mailer): Response

// código omitido

E vamos enviar os e-mails criando um objeto do tipo $email:

// código omitido

        $email = (new Email())
            ->from('hello@example.com')
            ->to('you@example.com')
            //->cc('cc@example.com')
            //->bcc('bcc@example.com')
            //->replyTo('fabien@example.com')
            //->priority(Email::PRIORITY_HIGH)
            ->subject('Time for Symfony Mailer!')
            ->text('Sending emails is fun again!')
            ->html('<p>See Twig integration for better HTML integration!</p>');

        $mailer->send($email);

Enviando e-mails

Então, copiamos esse objeto e o implementamos no nosso código.

Vamos fechar o terminal e abrir o nosso Controller, clicando em "src > Controller > SeriesController.php" no menu lateral esquerdo do Symfony.

Lembrete: esse código já conta com as modificações do desafio de bulk insert do SQL, do treinamento anterior.

Queremos realizar essa ação após a inserção da série, portanto após a linha $series = $this->seriesRepository->add($input). Por enquanto, criaremos tudo no Controller mesmo.

Então, faremos as alterações necessárias no objeto $email.

Primeiro, precisaremos de um MailerInterface, do Symfony\Component\Mailer - vamos recebê-lo no construtor, adicionando uma dependência.

A partir de MailerInterface, teremos o objeto $mailer:

class SeriesController extends AbstractController
{
    public function __construct(
        private SeriesRepository $seriesRepository,
        private EntityManagerInterface $entityManager,
        private MailerInterface $mailer,

// código omitido

Uma curiosidade é que: estamos realizando essa injeção de dependência por meio do construtor, mas, como vimos, poderíamos recebê-la diretamente na public function de inserção de séries, como public function addSeries(Request $request, MailerInterface $mailer): Response.

Isso funcionaria perfeitamente, mas não é considerada uma boa prática. O ideal é que nossas dependências fiquem no construtor para separarmos isso melhor em classes.

Repare que começamos a ter várias dependências no construtor. Então, seria interessante separar esses controllers para eles ficarem menores. Isso fica de desafio para você! Por enquanto, manteremos tudo aqui.

Nosso e-mail virá do namespace Symfony\Component\Mime\Email, conforme a informação da documentação.

Em seguida, informaremos que esse e-mail virá do endereço `sistema@example.com` para o usuário que estiver logado no nosso sistema.

Para isso, podemos pegar o $user, utilizando o this->getUser() (que conferimos no treinamento sobre autenticação), e enviar para o objeto de e-mail. Então, passamos $user->getUserIdentifier() como parâmetro de ->to, sabendo que UserIdentifier é o e-mail do nosso usuário.

Em seguida, poderíamos adicionar cópia (->cc()), cópia oculta (->bcc()), para quem responderemos (->replyTo()) e a prioridade desse e-mail (->priority()). Nesse momento, não definiremos nada disso.

Definiremos o assunto (->subject()) como 'Nova série criada'.

Podemos definir um texto (->text()) alternativo para quando o cliente de e-mail não aceitar HTML. Então vamos adicionar um texto simples: "Série {$series->getName()} foi criada".

Em seguida, definiremos o HTML (->html()) com um título "h1" e um pequena frase: '<h1>Série criada</h1><p>Série {$series->getName()} foi criada</p>'. Poderíamos ter um template mais interessante e informativo, mas só esse HTML simples é o suficiente.

Por fim, realizamos o envio desse e-mail com $this->mailer->send($email).

Teremos o seguinte código ao final:

// código omitido

public function addSeries(Request $request): Response
{
    $input = new SeriesCreateFromInput():
    $seriesForm = $this->createForm(SeriesType::class, $input)
            ->handleRequest($request);

    if (!$seriesForm->isValid()) {
            return $this->renderForm('series/form', compact('seriesForm'));
    }
    $user = $this->getUser();

    $series = $this->seriesRepository->add($input);
    $email = (new Email())
            ->from('sistema@example.com')
            ->to($user->getUserIdentifier())
            ->subject('Nova série criada')
            ->text("Série {$series->getName()} foi criada")
            ->html('<h1>Série criada</h1><p>Série {$series->getName()} foi criada</p>');

    $this->$mailer->send($email);

// código omitido

Mas, como esse envio de e-mail será feito?

O envio de e-mail utilizando PHP pode ser feito de várias formas:

Mas, qual forma de envio o mailer utiliza? Nós ainda não configuramos nenhuma.

Para saber disso, vamos ver o que vai acontecer quando adicionarmos uma nova série na aplicação - como um erro ou algo do tipo.

Vamos preencher o formulário de inserção de série com um exemplo qualquer. Ao clicar em "Adicionar", recebemos a seguinte mensagem:

The controller for URI "/series/create" is not callable: Environment variable not found: "MAILER_DSN".

Ou seja, o nosso controller não é um callable porque não encontrou a variável de ambiente "MAILER_DSN".

A mensagem não está muito clara. O que ela diz, basicamente, é que não foi possível executar o código de inserção de série e envio de e-mail com sucesso porque uma das nossas dependências (o MailerInterface) precisa da variável de ambiente MAILER_DSN para ser construída.

Ou seja, ela precisa de uma configuração relacionada a esse componente de enviar e-mails - mas não fizemos nenhuma configuração ainda!

Então, vamos conhecer melhor as configurações do componente de e-mail na próxima aula.

Envio de e-mail - Entendendo as configurações

No vídeo anterior, nos deparamos com um problema onde a variável de ambiente chamada "MAILER_DSN" não foi encontrada:

EnvNotFoundException > InvalidArgumentException

The controller for URI "/series/create" is not callable: Environment variable not found: "MAILER_DSN".

Já estudamos sobre variáveis de ambiente em treinamentos anteriores. Vamos relembrar?

Abrimos o projeto e na raiz, temos um arquivo .env que vai ter algumas opções padrão de variáveis de ambiente. Também temos um arquivo .env.local que vai ter as opções do nosso ambiente local de desenvolvimento.

Quando analisamos o .env, no final do arquivo, temos um exemplo em forma de comentário sobre um MAILER_DSN que manda para null.

.env:

# …

###> symfony/mailer ###
# MAILER_DSN=null://null
###< symfony/mailer ###

Isso significa enviar os e-mails para lugar nenhum e serão ignorados.

Logo, vamos copiar a linha sobre MAILER_DSN e colá-la sem # no .env.local. Afinal, as variáveis de ambiente que só devem existir no ambiente local devem adicionar no arquivo .env.local.

.env.local:

DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
MAILER_DSN=null://null

Após definir o MAILER_DSN como valor nulo, vamos acessar novamente a página do formulário de criação de séries no navegador.

http://localhost:8123/series/create

Novamente vamos tentar criar uma nova série, adicionando as seguintes informações e clicando no botão "Adicionar" ao final da página.

A nossa série foi criada e cadastrada no banco de dados.

Série "Nova Série" adicionada com sucesso.

Com isso, o código $this->mailer->send($email) em "src > Controller > SeriesController.php" foi chamado.

Nosso mailer envia e-mails utilizando algum transportador de e-mails. Assim, ao receber essa mensagem, a envia de alguma forma.

Porém, em .env.local, dizemos para o mailer se conectar ao null://null, ou seja, não vai enviar o e-mail a lugar nenhum.

Ignorar os e-mails pode ser útil em um ambiente de testes onde não queremos mandar e-mails. Mas, no nosso cenário, queremos visualizá-los.

Transportadores

Para visualizar e-mail, vamos ler a documentação do Symfony sobre e-mails com mailer.

No nosso caso, a instalação do mailer não é necessária, pois estamos utilizando a versão fullstack (completa) do Symfony.

Por isso, vamos direto para o tópico de "transport setup", onde informa que os e-mails são enviados através de transports (transportadores).

Um dos transportadores é o SMTP, um protocolo de envio de e-mails. Com SMTP, conseguimos nos conectar a basicamente qualquer servidor.

Outras opções que já vêm instaladas por padrão são:

Caso queiramos um transporte mais rebuscado, podemos instalar transporte de terceiros, como Amazon SES, transporte do Gmail, entre outras opções.

Vamos utilizar o transportador SMTP já que não necessita instalação. Mas, para onde vamos mandar esse e-mail?

Poderíamos abrir uma conta no Gmail e pegar os detalhes do servidor. Porém, vamos utilizar um serviço ideal para o ambiente de desenvolvimento, o Mailtrap.

Mailtrap

No navegador, vamos acessar a nossa conta no site do Mailtrap.

O Mailtrap é um serviço para testar e-mails.

Vamos configurar o servidor do Mailtrap como nosso servidor SMTP, ou seja, estaríamos enviando e-mails através do serviço do Mailtrap.

Porém, o Mailtrap mantem o e-mail para no inbox (caixa de entrada) para que consigamos visualizá-lo, independentemente do destinatário (para quem enviamos o e-mail).

Desse modo, podemos visualizar o layout do e-mail, verificar os textos alternativos e averiguar problemas.

Esse serviço tem algumas limitações: podemos ter no máximo 50 e-mails na caixa de entrada e enviar no máximo 10 e-mails dentro de um certo intervalo.

MailCatcher

Para o ambiente local, poderíamos utilizar outro serviço chamado MailCatcher.

O serviço MailCatcher é instalado na máquina. Assim, todos e-mails enviados não saem da máquina, ou seja, são visualizados na nossa própria máquina.

Porém, esse serviço normalmente utiliza docker. Com isso, temos um contêiner do PHP e outro contêiner com o MailCatcher.

Para não adicionar o conhecimento sobre docker como pré-requisito desse curso, preferimos utilizar a versão online com o Mailtrap.

Porém, se tivéssemos respondido "Sim" quando o Symfony perguntou se queríamos adicionar configurações de docker no primeiro curso sobre o framework, o MailCatcher já viria configurado - o que ajudaria muito.

Utilizando Mailtrap e SMTP

Lembre-se de acessar o Mailtrap e criar uma conta.

Em seguida, acesse sua caixa de entrada chamada "My Inbox" na parte de inboxes. Por padrão, você já terá uma inbox, mas pode criar outras também.

Na página de inboxes de uma conta pessoal da Mailtrap, podemos acessar outras partes do site pela lateral esquerda e visualizamos os projetos de inbox na parte direita.

Em "Inboxes > My Inbox", temos as informações de como conectar ao servidor de SMTP na aba "SMTP Settings".

Dentro de "SMTP Settings", em "Integrations", podemos escolher fazer a conexão através de cURL ou várias outras integrações. Vamos escolher "Symfony 5+".

Agora, temos o código com o MAILER_DSN necessário para configurar nosso mailer.

MAILER_DSN=smtp://a61b0f382eef3a:5d7013d8c3e134@smtp.mailtrap.io:2525?encryption=tls&auth_mode=login

Vamos copiar o trecho acima e colá-lo em .env.local, substituindo o MAILER_DSN igual a nulo.

.env.local:

DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
MAILER_DSN=smtp://a61b0f382eef3a:5d7013d8c3e134@smtp.mailtrap.io:2525?encryption=tls&auth_mode=login

Para entender melhor o que esse código significa, vamos voltar para a documentação do Symfony.

Quando utilizamos o SMTP, precisamos configurar o MAILER_DSN no formato exemplificado na documentação sobre e-mails no tópico "Using Built-in Transports":

Exemplo:

smtp://user:pass@smtp.example.com:25

Nos devemos informar o smtp que é o transport que estamos utilizando, seguido de dois-pontos, barra, barra. Depois, o user que é o usuário e dois-pontos novamente. Em seguida, colocamos no lugar de pass a senha do nosso servidor SMTP, arroba e o endereço do servidor SMTP substituindo smtp.example.com. Por último, colocamos dois-pontos e a porta para onde queremos enviar essas requisições ou mensagens.

Repare que o código copiado do Mailtrap tem algumas opções a mais.O Mailtrap muda a porta, adiciona detalhes de criptografia e como é a autenticação. Tudo vem configurado.

Teoricamente, isso já seria o suficiente para enviar nosso e-mail. Vamos verificar.

Verificação

No navegador, vamos abrir a listagem de séries e apagar a "Nova série", clicando no botão "X" do lado direito de seu nome.

Vamos recriá-la, clicando no botão "Adicionar" ainda na mesma página. Preenchemos o formulário de adição de nova série:

Após apertar no botão "Adicionar", rapidamente recebemos a mensagem de confirmação de envio.

Série "Nova série" adicionada com sucesso.

Note que a mensagem aparece muito rápido. Geralmente, envio de e-mail é mais demorado.

Quando acessamos o Mailtrap e atualizamos sua página no navegador, não temos nenhuma mensagem no nosso inbox. O que será que aconteceu?

Podemos utilizar a debug bar (barra de ferramentas para debug web) para entender o que houve. Em "app series", vamos acessar a requisição anterior a requisição POST. Para isso, clique no link que está entre parênteses, após POST.

Com isso, é aberto o Symfony Profiler. No menu vertical na lateral esquerda, vamos selecionar a opção de "e-mails".

Na metade direita da tela em "Emails", verificamos que existe um e-mail enfileirado (queued). Isto é, esse e-mail foi configurado para ser enviado depois através de um processamento assíncrono de dados.

Vamos aprender sobre processamento assíncrono nesse treinamento, mas ainda não é o momento.

Por isso, vamos interromper o servidor web pelo terminal com "Ctrl + C".

Depois, vamos ao projeto no PHP Storm e abrir "config > packages > messenger.yaml".

Nesse arquivo, vamos remover o detalhe da classe SendEmailMessage: async que está em routing.

messenger.yaml:

# …

routing:
       #apague Symfony\Component\Mailer\Messager\SendEmailMessage: async
       Symfony\Component\Notifier\Message\ChatMessage: async
       Symfony\Component\Notifier\Message\SmsMessage: async

Por enquanto, não precisa se preocupar com o que a linha significa.

Agora, vamos subir o servidor novamente pelo terminal para ler todas as configurações.

php -s 0.0.0.0:8123 -t public/

Vamos tentar mais uma vez criar uma série na aplicação. Para isso, vamos voltar ao navegador para fazer login no sistema novamente já que reiniciamos o servidor.

Na página inicial de listagem de séries, vamos apagar "Nova série". Para depois, adicionar a série na página de formulário de adição.

Repare que quando apertamos o botão "Adicionar", demora um pouca mais para aparecer a mensagem de sucesso.

Série "Nova série" adicionada com sucesso.

Quando acessamos o inbox do Mailtrap no navegador, temos um e-mail intitulado "Nova série criada" enviado a poucos segundos atrás.

Com isso, conseguimos enviar o e-mail na hora em que executamos a linha de comando $this->mailer->send($email) em SeriesController.php.

Agora, o send efetivamente envia o e-mail na hora em que é executado.

Visualizando o e-mail

Vamos agora visualizar o e-mail na inbox do Mailtrap.

No navegador, clicamos no e-mail "Nova série criada" e conseguimos visualizá-lo em uma caixa à direita que contém, de cima para baixo, título, remetente, destinatário, abas, dispositivo de visualização e o corpo do e-mail.

Na aba "HTML", na visualização para computador, vemos o corpo do e-mail com o título <h1> e parágrafo <p>.

Início do e-mail

Série criada

Série {$series->getName()} foi criada criada

Fim do e-mail

Note que existe um problema, pois o nome da série veio como {$series->getName()}. Provavelmente um erro de aspas simples, depois verificaremos.

Se seu cliente não suporta HMTL, também podemos ver a versão alternativa ao clicar na aba "Text":

Série Nova série foi criada criada

Também podemos ver o corpo do e-mail no formato "cru", ou seja, sem ser parseado, com a aba "Raw". Além de contar com análise de spam na aba "Spam Analysis".

Também podemos mudar o dispositivo de visualização do corpo do e-mail. Por exemplo, como a mensagem apareceria em um celular, tablet ou computador.

O Mailtrap é interessante por causa dessas informações que o serviço nos traz.

Enfim, podemos apagar o e-mail ao clicar no ícone de lixeira no canto superior direito.

Agora, vamos corrigir o nosso envio de e-mail.

Corrigindo a mensagem do email

Em "src > Controller > SeriesController.php", em $email, temos a mensagem em text e html.

A mensagem em text está com aspas duplas, porém, em html usamos aspas simples. Por isso, {$series->getName()} foi lido de forma errada no Mailtrap.

No PHP Storm, podemos apertar "Alt + Enter" para mostrar context actions (que ajudam a resolver erros e avisos destacados). Dentre as opções que aparecem, vamos escolher "Replace quotes" (substituir aspas).

Após a alteração da mensagem do html entre aspas duplas, vamos remover o contrabarra (\) de {\$series->getName()} para que entenda que desejamos a leitura da variável.

Também vamos retirar uma das aparições da palavra "criada" que ficou repetida.

Além disso, vamos acrescentar aspas duplas às chaves de {$series->getName()} para ficar mais fácil diferenciar o nome da série do resto do texto.

Contudo, nosso código do nome da série está dentro uma string com aspas duplas. Por isso, precisamos escapar as aspas, colocando um contrabarra antes do abre-aspas e antes do fecha-aspas.

#[Route('/series/create', name: 'app_add_series', methods: ['POST'])]
public function addSeries(Request $request): Response
{

// código omitido

    $series = $this->seriesRepository->add($input);
    $email = (new Email())
        ->from('sistema@example.com')
        ->to($user->getUserIdentifier())
        ->subject('Nova série criada')
        ->text("Série {$series->getName()} foi criada")
        ->html("<h1>Série criada</h1><p>Série \"{$series->getName()}\" foi criada</p>");

    $this->mailer->send($email);
}

Após corrigir esse problema de HTML, vamos voltar para o navegador e apagar novamente a "Nova série" na página de listagem de séries do aplicativo.

Vamos adicionar uma série com as seguintes características:

Após clicar no botão "Adicionar" ao fim da página, esperamos um tempo até que surja a mensagem de sucesso.

Série "Nova série" adicionada com sucesso

Essa demora até o e-mail ser enviado é o motivo pelo qual a configuração sobre envio assíncrono no messenger.yaml vem habilitada por padrão. Vamos falar mais sobre essa configuração em outro vídeo.

Ainda no navegador, vamos atualizar nosso inbox do Mailtrap e visualizar o novo e-mail intitulado "Nova série criada".

Com a aba "HTML" selecionada, podemos ver nossa mensagem com o nome da série entre aspas duplas.

Início do e-mail

Série criada

Série "Nova série" foi criada

Fim do e-mail

Agora, nosso e-mail foi enviado com sucesso e de forma síncrona, ou seja, é enviado na hora em que executamos a linha de código de envio.

O Symfony mailer traz outras opções para criação de e-mails. No próximo vídeo, vamos ler sobre algumas opções interessantes na documentação que podem nos ajudar a criar e-mails.

Sobre o curso Symfony Framework: e-mail, processamento assíncrono, uploads e testes

O curso Symfony Framework: e-mail, processamento assíncrono, uploads e testes possui 157 minutos de vídeos, em um total de 46 atividades. Gostou? Conheça nossos outros cursos de em Programação, ou leia nossos artigos de Programação.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas