Symfony 2 Joboard : Отправка писем через Mailer

Symfony по-умолчанию содержит в себе один из лучших решений в PHP для организации рассылки — Swift Mailer. Конечно же библиотека полностью интегрирована с Symfony и включает в себя некоторые дополнительные функции. Давайте начнем с отправки простого письма с токеном, в котором партнеру сообщается, что его аккаунт был активирован. Но сначала надо настроить несколько параметров в файле parameters.yml:

# ...
   # ...
   mailer_transport:  gmail
   mailer_host:       ~
   mailer_user:       [email protected]
   mailer_password:   your_password
   # ...

Чтобы код работал корректно вам понадобится заменить [email protected] на настоящий email адрес. После этого очистите кеш для dev и prod сред:

php app/console cache:clear --env=dev
php app/console cache:clear --env=prod

Так как в качестве транспорта мы указали gmail, почтовый адрес должен быть от google. Процесс создания письма в Symfony напоминает работу с обычным почтовым клиентом. Вы должны указать тему, тело и одного или нескольких получателей письма.

Вам потребуется выполнить следующие шаги:

  • вызвать метод класс Swift_message newInstance() (В официальной документации Swift Mailer вы найдете подробную информацию об этом методе).
  • Указать адрес отправителя — setFrom().
  • Указать тему письма — setSubject().
  • Указать получателей при помощи одного из следующих методов — setTo(), setCc(), setBcc().
  • Указать тело письма — setBody().

Замените метод activate на следующий код:

<?php
# src/App/JoboardBundle/Controller/AffiliateAdminController.php
// ...
   public function activateAction($id)
    {
        if ($this->admin->isGranted('EDIT') === false) {
            throw new AccessDeniedException();
        }
        $em = $this->getDoctrine()->getManager();
        $affiliate = $em->getRepository('AppJoboardBundle:Affiliate')->findOneById($id);
        try {
            $affiliate->setIsActive(true);
            $em->flush();
            $message = \Swift_Message::newInstance()
                ->setSubject('Токен партнёра')
                ->setFrom('[email protected]')
                ->setTo($affiliate->getEmail())
                ->setBody(
                    $this->renderView(
                        'AppJoboardBundle:Affiliate:email.txt.twig',
                        ['affiliate' => $affiliate->getToken()]
                    ))
            ;
            $this->get('mailer')->send($message);
        } catch(\Exception $e) {
            $this->get('session')->setFlash('sonata_flash_error', $e->getMessage());
        }
        return new RedirectResponse($this->admin->generateUrl('list', $this->admin->getFilterParameters()));
    }
// ...

Отправка письма осуществляется методом send() и передачи ему самого сообщения в качестве параметра.

Для тела сообщения мы создадим новый файл — email.txt.twig, который будет содержать тело письма.

<!-- src/App/JoboardBundle/Resources/views/Affiliate/email.txt.twig -->
Ваш партнёрский аккаунт был успешно активирован.
Ваш секретный ключ {{affiliate}}.
Вы можете просматривать новые вакансии по следующим ссылкам:
http://joboard.local/app_dev.php/api/{{affiliate}}/jobs.xml
или http://joboard.local/app_dev.php/api/{{affiliate}}/jobs.json
или http://joboard.local/app_dev.php/api/{{affiliate}}/jobs.yaml

Теперь добавим новый функционал в метод batchActionActivate, чтобы мы могли одновременно сделать рассылку для нескольких партнеров:

<?php
# src/App/JoboardBundle/Controller/AffiliateAdminController.php
// ... 
   public function batchActionActivate(ProxyQueryInterface $selectedModelQuery)
   {
       // ...
       try {
           foreach($selectedModels as $selectedModel) {
               $selectedModel->activate();
               $modelManager->update($selectedModel);
               $message = \Swift_Message::newInstance()
                    ->setSubject('Токен партнёра')
                    ->setFrom('[email protected]')
                    ->setTo($selectedModel->getEmail())
                    ->setBody(
                        $this->renderView(
                            'AppJoboardBundle:Affiliate:email.txt.twig',
                            ['affiliate' => $selectedModel->getToken()]
                        ))
               ;
               $this->get('mailer')->send($message);
           }
       } catch(\Exception $e) {
           $this->get('session')->setFlash('sonata_flash_error', $e->getMessage());
           return new RedirectResponse($this->admin->generateUrl('list', $this->admin->getFilterParameters()));
       }
       // ...
   }
// ...

Тестирование

После того как мы добавили новый функционал для отправки писем, настало время создать тест для проверки, что все работает как положено.

Для проверки функционала мы должны быть авторизованы, а для этого нам понадобится имя пользователя и пароль. Поэтому создадим новый файл фикстур, где добавим пользователя с правами администратора.

<?php
namespace App\JoboardBundle\DataFixtures\ORM;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use App\JoboardBundle\Entity\User;
class LoadUserData implements FixtureInterface, OrderedFixtureInterface, ContainerAwareInterface
{
    /**
     * @var ContainerInterface
     */
    private $container;
    /**
     * {@inheritDoc}
     */
    public function setContainer(ContainerInterface $container = null)
    {
        $this->container = $container;
    }
    /**
     * @param \Doctrine\Common\Persistence\ObjectManager $em
     */
    public function load(ObjectManager $em)
    {
        $user = new User();
        $user->setUsername('admin');
        $encoder = $this->container
            ->get('security.encoder_factory')
            ->getEncoder($user)
        ;
        $encodedPassword = $encoder->encodePassword('111111', $user->getSalt());
        $user->setPassword($encodedPassword);
        $em->persist($user);
        $em->flush();
    }
    public function getOrder()
    {
        return 4;
    }
}

В этом тесте мы воспользуемся сборщиком swiftmaler, чтобы получить информацию об отправленных письмах в предыдущих запросах. Теперь проверим правильно ли отправляются сами сообщения:

<?php
namespace App\JoboardBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Input\ArrayInput;
use Doctrine\Bundle\DoctrineBundle\Command\DropDatabaseDoctrineCommand;
use Doctrine\Bundle\DoctrineBundle\Command\CreateDatabaseDoctrineCommand;
use Doctrine\Bundle\DoctrineBundle\Command\Proxy\CreateSchemaDoctrineCommand;
class AffiliateAdminControllerTest extends WebTestCase
{
    private $em;
    private $application;
    public function setUp()
    {
        static::$kernel = static::createKernel();
        static::$kernel->boot();
        $this->application = new Application(static::$kernel);
        // удаляем базу
        $command = new DropDatabaseDoctrineCommand();
        $this->application->add($command);
        $input = new ArrayInput(array(
            'command' => 'doctrine:database:drop',
            '--force' => true
        ));
        $command->run($input, new NullOutput());
        // закрываем соединение с базой
        $connection = $this->application->getKernel()->getContainer()->get('doctrine')->getConnection();
        if ($connection->isConnected()) {
            $connection->close();
        }
        // создаём базу
        $command = new CreateDatabaseDoctrineCommand();
        $this->application->add($command);
        $input = new ArrayInput(array(
            'command' => 'doctrine:database:create',
        ));
        $command->run($input, new NullOutput());
        // создаём структуру
        $command = new CreateSchemaDoctrineCommand();
        $this->application->add($command);
        $input = new ArrayInput(array(
            'command' => 'doctrine:schema:create',
        ));
        $command->run($input, new NullOutput());
        // получаем Entity Manager
        $this->em = static::$kernel->getContainer()
            ->get('doctrine')
            ->getManager();
        // загружаем фикстуры
        $client = static::createClient();
        $loader = new \Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader($client->getContainer());
        $loader->loadFromDirectory(static::$kernel->locateResource('@AppJoboardBundle/DataFixtures/ORM'));
        $purger   = new \Doctrine\Common\DataFixtures\Purger\ORMPurger($this->em);
        $executor = new \Doctrine\Common\DataFixtures\Executor\ORMExecutor($this->em, $purger);
        $executor->execute($loader->getFixtures());
    }
    public function testActivate()
    {
        $client = static::createClient();
        $client->enableProfiler();
        $crawler = $client->request('GET', '/login');
        $form = $crawler->selectButton('войти')->form([
            '_username' => 'admin',
            '_password' => '111111'
        ]);
        $client->submit($form);
        $client->followRedirect();
        $this->assertTrue(200 === $client->getResponse()->getStatusCode());
        $crawler = $client->request('GET', '/admin/app/joboard/affiliate/list');
        $link    = $crawler->filter('.btn.edit_link')->link();
        $client->click($link);
        $mailCollector = $client->getProfile()->getCollector('swiftmailer');
        // Check that an e-mail was sent
        $this->assertEquals(1, $mailCollector->getMessageCount());
        $collectedMessages = $mailCollector->getMessages();
        $message = $collectedMessages[0];
        // Asserting e-mail data
        $this->assertInstanceOf('Swift_Message', $message);
        $this->assertEquals('Токен партнёра', $message->getSubject());
        $this->assertRegExp(
            '/Ваш секретный ключ symfony/',
            $message->getBody()
        );
    }
}

Скорей всего при выполнении теста, вы получите ошибку. Чтобы её исправить, проверьте включен ли у вас profiler. для среды test. Если значение enabled выставлено в false, то его надо изменить на true:

# app/config/config_test.yml
# ...
framework:
    test: ~
    session:
        storage_id: session.storage.mock_file
    profiler:
        enabled: true
        collect: true
# ...

Теперь осталось только очистить кеш и выполнить тест.

phpunit -c app src/App/JoboardBundle/Tests/Controller/AffiliateAdminControllerTest