Como enviar um email

Visão Geral

Enviar emails através de uma aplicação web é uma tarefa muito comum. O symfony usa sua arquitetura para automatizá-la de uma maneira familiar (separação MVC) e provê uma classe específica para lidar com as particularidades dos emails (múltiplos tipos mime, mídias embutidas, anexos).

Introdução

O symfony oferece dois caminhos para se enviar emails através da sua aplicação web:

  • Via a classe sfMail, que é uma classe proxy que provê uma interface com o PHPMailer. Esta solução é simples e rápida, mas não provê separação MVC e praticamente não é compatível com i18n. Emails complexos também são mais difíceis de se compor usando apenas a classe sfMail.

  • Via um action e um template específico. Esta solução é muito versátil e lida com os emails das mesma forma que páginas comuns, com a adição das especificidades dessa mídia. É um pouco mais longa de se colocar em prática, porém muito mais poderosa do que o primeiro método.

A implementação de ambas as soluções irão ser ilustradas através do mesmo exemplo: o envio de uma senha esquecida solicitada pelo usuário.

Uso direto do sfMail

A classe sfMail irá parecer familiar aos que já conhecem a classe PHPMailer. Ela é simplesmente uma classe proxy para o PHPMailer, com a vantagem de usar a sintaxe do symfony. A classe PHPMailer está inclusa no pacote do symfony, logo instalações adicionais não são necessárias.

Para enviar um email contendo a senha para um cliente, uma action tem que fazer o seguinte:

[php]
public function executePasswordRequest()
{
  // determinar o cliente a partir do parâmetro 'id' de uma requisição
  $customer = CustomerPeer::retrieveByPk($this->getRequestParameter('id'));

  // inicialização da classe
  $mail = new sfMail();
  $mail->initialize();
  $mail->setMailer('sendmail');
  $mail->setCharset('utf-8');

  // definição dos parâmetros requeridos
  $mail->setSender('webmaster@minha-empresa.com', 'Webmaster da Minha Empresa');
  $mail->setFrom('webmaster@minha-empresa.com', 'Webmaster da Minha Empresa');
  $mail->addReplyTo('webmaster_copia@minha-empresa.com');


$mail->addAddress($customer->getEmail());
$mail->setSubject('Sua solicitação de senha'); $mail->setBody(' Caro cliente,

  Você está muito distraído. Da próxima vez, tente lembrar sua senha:
  '.$customer->getPassword().'

  Atenciosamente,
  Webmaster da Minha Empresa');

  // send the email
  $mail->send();
}


Uso de uma action alternativa

Em muitos casos, como o processo de envio do email é apenas um desvio na lógica de uma action que faz algo mais, ela é geralmente delegada a uma outra action. Como segue abaixo:

[php]
public function executePasswordRequest()
{
  // envia o email
  $raw_email = $this->sendEmail('mail', 'sendPassword');  

  // registra o email no log
  $this->logMessage($raw_email, 'debug');
}

O envio do email é delegado a uma action sendPassword de um módulo mail. O método sendEmail() da classe sfAction é um tipo especial de forward() que executa outra action e retorna ao ponto de partida (ela não para a execução da action atual). Além disso, ela retorna um email que pode ser gravado no arquivo de log (você encontrará mais informações sobre a forma de registrar informações no log no Capítulo 16).

O mail/sendPassword lida com o objeto sfMail, mas não precisa definir o programa de envio (informação obtida do arquivo de configuração), nem inicializá-lo:

[php]
public function executeSendPassword()
{
  // obtém o cliente a partir do parametro 'id' da requisição
  $customer = CustomerPeer::retrieveByPk($this->getRequestParameter('id'));

  // inicialização da classe
  $mail = new sfMail();
  $mail->setCharset('utf-8');      

  // definição dos parâmetros requeridos
  $mail->setSender('webmaster@minha-empresa.com', 'Webmaster da Minha Empresa');
  $mail->setFrom('webmaster@minha-empresa.com', 'Webmaster da Minha Empresa');
  $mail->addReplyTo('webmaster_copia@minha-empresa.com');


$mail->addAddress($customer->getEmail());
$mail->setSubject('Sua solicitação de senha');

  $this->password = $customer->getPassword();
  $this->mail = $mail;    
}

Observe que a action não precisa chamar o método send() do objeto sfMail; sendo chamada por um sendEmail(), que sabe que necessita finalizar enviando $this->mail ao seu objeto sfMail. E aonde está o email? você irá perguntar. Esta é a beleza da separação MVC: a mensagem do email deve estar escrita no template sendPasswordSuccess.php:

[php]
Caro cliente,

Você está muito distraído. Da próxima vez, tente lembrar sua senha:
<?php echo $password ?>

Atenciosamente,
Webmaster da Minha Empresa

Nota: Se a action sendPassword determinar que o email não deve que ser enviado, ela ainda pode abortar o processo de enviar o email retornando sfView::NONE, da mesma forma que é feita em uma action normal.

Configuração do Envio de Email

Se você usa um método action alternativo, você pode (não tem que) definir o programa de envio de email e ativá-lo ambiente por ambiente através de um arquivo de configuração.

Crie o arquivo de configuração mailer.yml no diretório modules/mail/config/ com o seguinte conteúdo:

dev:
  deliver:    off

all:
  mailer:     sendmail

Isso estipula que programa deve ser usado para o envio de emails, e desativa o envio de emails no ambiente de desenvolvimento.

Enviar email em HTML

Na maioria das vezes, os emails são enviados no formato HTML, ou mesmo no formato multipart (encapsulando os formatos HTML e texto). Para fazer isso diretamente com o objeto sfMail, use o método setContentType() e especifique um alternate body:

[php]
$mail->setContentType('text/html');
$mail->setAltBody('
<p>Caro cliente</p>,
<p>
  Você está muito <i>distraído</i>. Da próxima vez, tente lembrar sua senha:<br>
  <b>'.$customer->getPassword().'</b>
</p>
<p>
  Atenciosamente,<br>
  Webmaster da Minha Empresa
</p>');    
$mail->setAltBody('
Caro cliente,

Você está muito distraído. Da próxima vez, tente lembrar sua senha:
'.$customer->getPassword().'

Atenciosamente,
Webmaster da Minha Empresa');

Caso você use uma action alternativa, você precisará apenas de um template adicional terminado com .altbody.php. O symfony irá automaticamente adicioná-lo como um alternate body:

[php]
// no sendPasswordSuccess.php
<p>Caro cliente</p>,
<p>
  Você é muito <i>distraído</i>. Da próxima vez, tente lembrar sua senha:<br>
  <b><?php echo $password ?></b>
</p>
<p>
  Atenciosamente,<br>
  Webmaster da Minha Empresa
</p>

// no sendPasswordSuccess.altbody.php
Caro cliente,

Você é muito distraído. Da próxima vez, tente lembrar sua senha:
<?php echo $password ?>

Atenciosamente,
Webmaster da Minha Empresa

Nota: Se você usar apenas uma versão HTML sem o template altbody, você precisará definir o tipo de conteúdo para text/html na action sendPassword.

Embutir imagens

Emails HTML podem conter imagens diretamente embutidas no corpo da mensagem. Para adicionar uma imagem embutida, use o método addEmbeddedImage() do objeto sfMail:

[php]
$mail->addEmbeddedImage(sfConfig::get('sf_web_dir').'/images/my_company_logo.gif', 'CID1', 'My Company Logo', 'base64', 'image/gif');

O primeiro argumento é o caminho para a imagem, o segundo é uma referência da imagem para que você possa embutí-la no template:

[php]
// in sendPasswordSuccess.php
<p>Caro cliente</p>,
<p>
  Você está muito <i>distraído</i>. Da próxima vez, tente lembrar sua senha:<br>
  <b><?php echo $password ?></b>
</p>
<p>
  Atenciosamente,<br>
  Webmaster da Minha Empresa
  <img src="cid:CID1" />
</p>

Dica: Você também pode embutir imagens diretamente no template, sem nenhum código na action. Veja a descrição do helper embedded_image() na seção de trechos de códigos.

Anexos

Anexar um documento a um email é tão simples quanto você espera que seja:

[php]
// anexa um documento
$mail->addAttachment(sfConfig::get('sf_data_dir').'/MyDocument.doc');
// anexa um texto ou dados binários (não sendo do sistema de arquivos)
$mail->addStringAttachment('this is some cool text to embed', 'file.txt');

Sintaxe avançada de endereços de email

Além dos destinatários, emails geralmente precisam ser enviados como cópia ('cc:') ou cópia oculta ('bcc:'). O código abaixo mostra como fazer isso com o sfMail:

[php]
$mail->addAddress($customer->getEmail());
$mail->addCc('carbon_copy@minha-empresa.com ');    
$mail->addBcc('blind_carbon_copy@minha-empresa.com');

Os métodos do sfMail usados para definir emails (setSender(), setFrom(), addReplyTo(), addAddress(), addCc(), addBcc()) podem usar duas sintaxes:

[php]
$mail->setFrom('me@symfony-project.com', 'Symfony');
// equivalente à
$mail->setFrom('me@symfony-project.com <Symfony>');

Em adição, para minimizar o código em caso de múltiplos destinatários, o sfMail tem um método addAddresses():

[php]
$mail->addAddress('cliente1@cliente.com <Jules>');
$mail->addAddress('cliente2@cliente.com <Jim>');
// equivalente à
$mail->addAddresses(array('cliente1@client.com <Jules>', 'cliente2@client.com <Jim>'));

Métodos dosfMail

Uma vez que você tenha criado seu objeto sfMail, você pode querer checar o conteúdo dele. Felizmente, todos os métodos set descritos acima possuem um método get equivalente, e você poderá limpar as propriedades definidas anteriormente:

[php]
setCharset($charset)
getCharset()
setContentType($content_type)
getContentType()
setPriority($priority)
getPriority()
setEncoding($encoding)
getEncoding()
setSubject($subject)
getSubject()
setBody($body)
getBody()
setAltBody($text)
getAltBody()
setMailer($type = 'mail')
getMailer()
setSender($address, $name = null)
getSender()
setFrom($address, $name = null)
getFrom()
addAddresses($addresses)
addAddress($address, $name = null)
addCc($address, $name = null)
addBcc($address, $name = null)
addReplyTo($address, $name = null)
clearAddresses()
clearCcs()
clearBccs()
clearReplyTos()
clearAllRecipients()
addAttachment($path, $name = '', $encoding = 'base64', $type = 'application/octet-stream')
addStringAttachment($string, $filename, $encoding = 'base64', $type = 'application/octet-stream')
addEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = 'application/octet-stream')
setAttachments($attachments)
clearAttachments()
addCustomHeader($name, $value)
clearCustomHeaders()
setWordWrap($wordWrap)
getWordWrap()

Enviando emails de fora de uma action

Se você quiser enviar emails de fora de uma action (ex.: num processo batch), você não possui nenhum objeto action para chamar o método sendEmail(). Felizmente, este método também está disponível para o objeto sfController. O código abaixo mostra como chamar a action de envio de email em um script batch:

[php]
<?php

define('SF_ROOT_DIR',    realpath(dirname(__FILE__).'/..'));
define('SF_APP',         'myapp');
define('SF_ENVIRONMENT', 'test');
define('SF_DEBUG',       false);

require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php');

$raw_email = sfContext::getInstance()->getController()->sendEmail('mail', 'sendPassword');