16. 16日目: メール送信

  • この記事は Symfony 1.4 向けのオリジナルの Jobeet Tutorial の一部分です。
昨日は、 Jobeet に読み取り専用の Web サービスを追加しました。
現在、アフィリエイトはアカウントの作成はできますが、使用する前に管理者によってアクティブ化される必要があります。
アフィリエイトがトークンを受け取るためには、メール通知を実装する必要があります。今日の行う作業はそれになります。
Symfony フレームワークは最高の PHP のメール送信ソリューションの一つ、 Swift Mailer がバンドルされています。
もちろん、ライブラリは完全に、 Symfony と統合されており、いくつかの素晴らしい機能がデフォルトの機能の上に追加されています。
では、アカウントがアクティブ化された際アフィリエイトにトークンを通知するため、簡単な電子メールを送ることから始めましょう。
しかし、最初に、ご使用の環境を設定する必要があります。

app/config/parameters.yml

# ...
    # ...
    mailer_transport:  gmail
    mailer_host:       ~
    mailer_user:       address@example.com
    mailer_password:   your_password
    # ...

Note

コードが正しく動作するためには、 address@example.com を実際のメールアドレスに変更し、実際のパスワードを設定する必要があります。

app/config/parameters_test.yml ファイルに同じことを行います。
二つのファイルを変更した後、両方のテストおよび開発環境用のキャッシュをクリアします。
$ php app/console cache:clear --env=dev
$ php app/console cache:clear --env=prod
メール転送を gmail に設定したため、 mailer_user のメールアドレスを置き換える際に、 Google のメールアドレスをで置き換えます。
メッセージの作成は、メールクライアントでメール作成ボタンをクリックしたときの実行手順と類似していると考えることができます。
タイトルを設定し、いくつかの受信者を指定し、メッセージを書きます。

メッセージを作成するには、以下となります。

  • Swift_messageのnewInstance() メソッドをコールします。(このオブジェクトの詳細を学ぶためには Swift Mailer の公式ドキュメントを参照してください)
  • setForm() メソッドで送信アドレス( From: )を設定します。
  • setSubject() メソッドでタイトルを設定します。
  • 次のいずれかの方法で、受信者を設定します。setTo(), SetCC(), setBcc()。
  • setBodyで本文を設定します。

次のコードで activateAction を置き換えます。

src/Ibw/JobeetBundle/Controller/AffiliateAdminController.php

// ...

public function activateAction($id)
{
 if($this->admin->isGranted('EDIT') === false) {
      throw new AccessDeniedException();
  }

  $em = $this->getDoctrine()->getManager();
  $affiliate = $em->getRepository('IbwJobeetBundle:Affiliate')->findOneById($id);

  try {
      $affiliate->setIsActive(true);
      $em->flush();

      $message = \Swift_Message::newInstance()
          ->setSubject('Jobeet affiliate token')
          ->setFrom('address@example.com')
          ->setTo($affiliate->getEmail())
          ->setBody(
              $this->renderView('IbwJobeetBundle:Affiliate:email.txt.twig', array('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/Ibw/JobeetBundle/Resources/views/Affiliate/email.txt.twig

Your affiliate account has been activated.
Your secret token is {{affiliate}}.
You can see the jobs list at the following addresses:
http://jobeet.local/app_dev.php/api/{{affiliate}}/jobs.xml
or http://jobeet.local/app_dev.php/api/{{affiliate}}/jobs.json
or http://jobeet.local/app_dev.php/api/{{affiliate}}/jobs.yaml

ここで、複数のアフィリエイトアカウントを選択しアクティブ化した場合でも、アクティベーションメールが届くように、batchActionActivate にメール機能を追加してみましょう。

src/Ibw/JobeetBundle/Controller/AffiliateAdminController.php

// ...

public function batchActionActivate(ProxyQueryInterface $selectedModelQuery)
{
  // ...

  try {
      foreach($selectedModels as $selectedModel) {
          $selectedModel->activate();
          $modelManager->update($selectedModel);

          $message = \Swift_Message::newInstance()
              ->setSubject('Jobeet affiliate token')
              ->setFrom('address@example.com')
              ->setTo($selectedModel->getEmail())
              ->setBody(
                  $this->renderView('IbwJobeetBundle:Affiliate:email.txt.twig', array('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()));
  }

  // ...
}

// ...

16.1. テスト

Symfony のメーラーでメール送信する機能を作成しました。次は、作成したものが正しく動くか機能テストを書いて確認しましょう。
この新しい機能をテストするには、ログインする必要があります。ログインするには、ユーザー名とパスワードが必要になります。
ユーザー admin を追加する新しいフィクスチャファイルを作成することから始めます。

src/Ibw/JobeetBundle/DataFixtures/ORM/LoadUserData.php

<?php
namespace Ibw\JobeetBundle\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 Ibw\JobeetBundle\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('admin', $user->getSalt());
        $user->setPassword($encodedPassword);

        $em->persist($user);
        $em->flush();
    }

    public function getOrder()
    {
        return 4; // the order in which fixtures will be loaded
    }
}
テストにおいて、以前のリクエストで送信したメッセージに関する情報を取得するため、プロファイラ上の SwiftMailer コレクタを使用します。
では、メールが正しく送信された場合の、いくつかのテストを追加してみましょう。

src/Ibw/JobeetBundle/Tests/Controller/AffiliateAdminControllerTest.php

<?php
namespace Ibw\JobeetBundle\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);

        // drop the database
        $command = new DropDatabaseDoctrineCommand();
        $this->application->add($command);
        $input = new ArrayInput(array(
            'command' => 'doctrine:database:drop',
            '--force' => true
        ));
        $command->run($input, new NullOutput());

        // we have to close the connection after dropping the database so we don't get "No database selected" error
        $connection = $this->application->getKernel()->getContainer()->get('doctrine')->getConnection();
        if ($connection->isConnected()) {
            $connection->close();
        }

        // create the database
        $command = new CreateDatabaseDoctrineCommand();
        $this->application->add($command);
        $input = new ArrayInput(array(
            'command' => 'doctrine:database:create',
        ));
        $command->run($input, new NullOutput());

        // create schema
        $command = new CreateSchemaDoctrineCommand();
        $this->application->add($command);
        $input = new ArrayInput(array(
            'command' => 'doctrine:schema:create',
        ));
        $command->run($input, new NullOutput());

        // get the Entity Manager
        $this->em = static::$kernel->getContainer()
            ->get('doctrine')
            ->getManager();

        // load fixtures
        $client = static::createClient();
        $loader = new \Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader($client->getContainer());
        $loader->loadFromDirectory(static::$kernel->locateResource('@IbwJobeetBundle/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();

        // Enable the profiler for the next request (it does nothing if the profiler is not available)
        $client->enableProfiler();
        $crawler = $client->request('GET', '/login');

        $form = $crawler->selectButton('login')->form(array(
            '_username'      => 'admin',
            '_password'      => 'admin'
        ));

        $crawler = $client->submit($form);
        $crawler = $client->followRedirect();

        $this->assertTrue(200 === $client->getResponse()->getStatusCode());

        $crawler = $client->request('GET', '/admin/ibw/jobeet/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('Jobeet affiliate token', $message->getSubject());
        $this->assertRegExp(
            '/Your secret token is symfony/',
            $message->getBody()
        );
    }
}
ここでテストを実行する場合、エラーを取得するでしょう。
問題を防ぐには、 config_test.yml ファイルに移動し、プロファイラがテスト環境で有効になっていることを確認してください。
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/Ibw/JobeetBundle/Tests/Controller/AffiliateAdminControllerTest

See also

Symfony2日本語ドキュメント

豊富な日本語ドキュメントがありますので合わせて読み進めてみましょう。

Note

Creative Commons License

このチュートリアルは、クリエイティブ・コモンズ・ライセンス 表示 - 継承 3.0 非移植 (CC BY-SA 3.0) のもとでライセンスされています。 翻訳の元にしたオリジナルはこちらです。 Symfony2 Jobeet http://www.intelligentbee.com/blog/tag/symfony2-jobeet/.