* This article is part of the original Jobeet Tutorial, created by Fabien Potencier, for Symfony 1.4.
Mailer in jobeet
Yesterday, we added a read-only web service to Jobeet. Affiliates can now create an account but it needs to be activated by the administrator before it can be used. In order for the affiliate to get its token, we still need to implement the email notification. That’s what we will start doing in the coming lines.
The symfony framework comes bundled with one of the best PHP emailing solution: Swift Mailer. Of course, the library is fully integrated with symfony, with some cool features added on top of its default features. Let’s start by sending a simple email to notify the affiliate when his account has been activated and to give him the affiliate token. But first, you need to configure your environment:
# ... # ... mailer_transport: gmail mailer_host: ~ mailer_user: address@example.com mailer_password: your_password # ...
For the code to work properly, you should change the
address@example.com
email address to a real one, along with your real password.
Do the same thing in your app/config/parameters_test.yml
file.
After modifying the two files, clear the cache for both test and development environment:
php app/console cache:clear --env=dev php app/console cache:clear --env=prod
Because we set the mailer transport to gmail, when you will replace the email address from “mailer_user”, you will put a google email address.
You can think of creating a Message as being similar to the steps you perform when you click the compose button in your mail client. You give it a subject, specify some recipients and write your message.
To create the message, you will:
- call the
newInstance()
methond of Swift_message (refer to the Swift Mailer official documentation to learn more about this object). - set your sender address (From:) with
setFrom()
method. - set a subject line with
setSubject()
method. - set recipients with one of these methods:
setTo()
,setCc()
orsetBcc()
. - set a body with
setBody()
.
Replace the activate
action with the following code:
// ... 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())); } // ...
Sending the message is then as simple as calling the send()
method on the mailer instance and passing the message as an argument.
For the message body, we created a new file, called email.txt.twig
, that contains exactly what we want to inform the affiliate about.
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
Mailer in jobeet
Now, let’s add the mailing functionality to the batchActionActivate
too, so that even if we select multiple affiliate accounts to activate, they will receive their account activation email :
// ... 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())); } // ... } // ...
The Tests
Now that we have seen how to send an email with the symfony mailer, let’s write some functional tests to ensure we did the right thing.
To test this new functionality, we need to be logged in. To log in, we will need an username and a password. That’s why we will start by creating a new fixture
file, where we add the user admin
:
namespace IbwJobeetBundleDataFixturesORM; use DoctrineCommonPersistenceObjectManager; use DoctrineCommonDataFixturesAbstractFixture; use DoctrineCommonDataFixturesFixtureInterface; use DoctrineCommonDataFixturesOrderedFixtureInterface; use SymfonyComponentDependencyInjectionContainerAwareInterface; use SymfonyComponentDependencyInjectionContainerInterface; use IbwJobeetBundleEntityUser; class LoadUserData implements FixtureInterface, OrderedFixtureInterface, ContainerAwareInterface { /** * @var ContainerInterface */ private $container; /** * {@inheritDoc} */ public function setContainer(ContainerInterface $container = null) { $this->container = $container; } /** * @param DoctrineCommonPersistenceObjectManager $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 } }
In the tests, we will use the swiftmailer
collector on the profiler to get information about the messages send on the previous requests. Now, let’s add some tests to check if the email is sent properly:
namespace IbwJobeetBundleTestsController; use SymfonyBundleFrameworkBundleTestWebTestCase; use SymfonyBundleFrameworkBundleConsoleApplication; use SymfonyComponentConsoleOutputNullOutput; use SymfonyComponentConsoleInputArrayInput; use DoctrineBundleDoctrineBundleCommandDropDatabaseDoctrineCommand; use DoctrineBundleDoctrineBundleCommandCreateDatabaseDoctrineCommand; use DoctrineBundleDoctrineBundleCommandProxyCreateSchemaDoctrineCommand; 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 SymfonyBridgeDoctrineDataFixturesContainerAwareLoader($client->getContainer()); $loader->loadFromDirectory(static::$kernel->locateResource('@IbwJobeetBundle/DataFixtures/ORM')); $purger = new DoctrineCommonDataFixturesPurgerORMPurger($this->em); $executor = new DoctrineCommonDataFixturesExecutorORMExecutor($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() ); } }
If you run the test now, you’ll get and error. To prevent this for happening, go to your config_test.yml
file and make sure that the profiler is enabled in the test environment. If it’s set to false
, change it to true
:
# ... framework: test: ~ session: storage_id: session.storage.mock_file profiler: enabled: true # ...
Now, clear the cache, run the test command in your console and enjoy the green bar :
phpunit -c app src/Ibw/JobeetBundle/Tests/Controller/AffiliateAdminControllerTest
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
1 Comment
Comments are closed.
Cool piece! Thanks for sharing.
Maybe, you’ll be interested to check how our engineers tested Symfony Swift Mailer with external SMTP servers.
You may read this short tutorial https://blog.mailtrap.io/swiftmailer-sendmail
and share your thoughts.
Cheers!