Skip to content
This repository has been archived by the owner on Jan 21, 2020. It is now read-only.

Commit

Permalink
Merge pull request #2322 from zazabe/cm-mail-concurrency
Browse files Browse the repository at this point in the history
Send mail via SMTP with concurrency support
  • Loading branch information
zazabe authored Sep 7, 2016
2 parents b015e94 + ae4603b commit 126359c
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 21 deletions.
73 changes: 58 additions & 15 deletions library/CM/Mail/Mailer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,19 @@ class CM_Mail_Mailer extends Swift_Mailer implements CM_Service_ManagerAwareInte

use CM_Service_ManagerAwareTrait;

public function __construct(Swift_Transport $transport) {
/** @var array */
private $_headers;

/**
* @param Swift_Transport $transport
* @param array|null $headers
*/
public function __construct(Swift_Transport $transport, array $headers = null) {
if (null === $headers) {
$headers = [];
}
$this->_headers = $headers;

CM_Mail_Message::register();
parent::__construct($transport);
}
Expand All @@ -15,26 +27,57 @@ public function send(Swift_Mime_Message $message, &$failedRecipients = null) {
if (empty($to)) {
throw new CM_Exception_Invalid('No recipient specified');
}
$numSent = parent::send($message, $failedRecipients);
if (0 === $numSent || 0 !== count($failedRecipients)) {
$context = new CM_Log_Context();
$context->setExtra([
'message' => [
'subject' => $message->getSubject(),
'from' => $message->getSender(),
'to' => $message->getTo(),
'cc' => $message->getCc(),
'bcc' => $message->getBcc(),
],
'failedRecipients' => $failedRecipients,
]);
$this->getServiceManager()->getLogger()->error('Failed to send email to all recipients', $context);

$numSent = 0;
try {
$message = clone $message;
foreach ($this->_getHeaders() as $key => $value) {
$message->getHeaders()->addTextHeader($key, $value);
}
$numSent = parent::send($message, $failedRecipients);
$this->getTransport()->stop();
if (0 === $numSent || 0 !== count($failedRecipients)) {
$this->_logSendError($message, $failedRecipients);
}
} catch (Exception $exception) {
$this->_logSendError($message, $failedRecipients, $exception);
}

return $numSent;
}

public function createMessage($service = null) {
$service = null === $service ? 'cm-message' : $service;
return parent::createMessage($service);
}

/**
* @return array
*/
protected function _getHeaders() {
return $this->_headers;
}

/**
* @param Swift_Mime_Message $message
* @param array|null $failedRecipients
* @param Exception|null $exception
*/
protected function _logSendError(Swift_Mime_Message $message, array $failedRecipients = null, Exception $exception = null) {
$context = new CM_Log_Context();
$context->setExtra([
'message' => [
'subject' => $message->getSubject(),
'from' => $message->getFrom(),
'to' => $message->getTo(),
'cc' => $message->getCc(),
'bcc' => $message->getBcc(),
],
'failedRecipients' => $failedRecipients,
]);
if ($exception) {
$context->setException($exception);
}
$this->getServiceManager()->getLogger()->error('Failed to send email', $context);
}
}
7 changes: 5 additions & 2 deletions library/CM/Mail/MailerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,26 @@ public function createLogMailer($logLevel = null) {
/**
* @param string|null $host
* @param int|null $port
* @param array|null $headers
* @param string|null $username
* @param string|null $password
* @param string|null $security
* @return CM_Mail_Mailer
*/
public function createSmtpMailer($host = null, $port = null, $username = null, $password = null, $security = null) {
public function createSmtpMailer($host = null, $port = null, array $headers = null, $username = null, $password = null, $security = null) {
$host = null !== $host ? (string) $host : 'localhost';
$port = null !== $port ? (int) $port : 25;
$headers = null !== $headers ? (array) $headers : [];
$security = null !== $security ? (string) $security : null;

$transport = new Swift_SmtpTransport($host, $port, $security);
if (null !== $username) {
$transport->setUsername((string) $username);
}
if (null !== $password) {
$transport->setPassword((string) $password);
}
return new CM_Mail_Mailer($transport);
return new CM_Mail_Mailer($transport, $headers);
}

/**
Expand Down
67 changes: 63 additions & 4 deletions tests/library/CM/Mail/MailerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,27 @@ public function testSend() {
$this->assertSame([], $failedRecipients);
}

public function testSendWithHeaders() {
$transport = $this->mockInterface('Swift_Transport')->newInstance();
$transport->mockMethod('isStarted')->set(true);
$message = new CM_Mail_Message('foo', 'content');
$message->setTo('[email protected]');
$message->setCc('[email protected]', 'bar');
$client = new CM_Mail_Mailer($transport, ['X-foo' => 'bar']);

$sendMethod = $transport->mockMethod('send')->set(function (CM_Mail_Message $message) {
$this->assertSame(['X-foo' => ['bar']], $message->getCustomHeaders());
return 2;
});
$failedRecipients = [];
$this->assertSame([], $message->getCustomHeaders());
$numSent = $client->send($message, $failedRecipients);
$this->assertSame([], $message->getCustomHeaders());
$this->assertSame(1, $sendMethod->getCallCount());
$this->assertSame(2, $numSent);
$this->assertSame([], $failedRecipients);
}

public function testSendNoRecipient() {
$transport = $this->mockInterface('Swift_Transport')->newInstance();
$transport->mockMethod('isStarted')->set(true);
Expand All @@ -37,24 +58,63 @@ public function testSendNoRecipient() {
$this->assertSame(0, $sendMethod->getCallCount());
}

public function testSendThrows() {
$serviceManager = new CM_Service_Manager();
$logger = $this->mockObject('CM_Log_Logger');
$serviceManager->registerInstance('logger', $logger);
$transport = $this->mockInterface('Swift_Transport')->newInstance();
$transport->mockMethod('isStarted')->set(true);
$message = new Swift_Message('foo', 'content');
$message->setFrom('[email protected]', 'Foobar');
$message->setTo('[email protected]');
$message->setCc('[email protected]', 'bar');

$client = new CM_Mail_Mailer($transport);
$client->setServiceManager($serviceManager);

$sendMethod = $transport->mockMethod('send')->set(function () {
throw new Exception('Failed');
});
$errorMethod = $logger->mockMethod('error')->set(function ($message, CM_Log_Context $context = null) {
$exception = $context->getException();
$this->assertSame('Failed to send email', $message);
$this->assertSame([
'message' => [
'subject' => 'foo',
'from' => ['[email protected]' => 'Foobar'],
'to' => ['[email protected]' => null],
'cc' => ['[email protected]' => 'bar'],
'bcc' => null,
],
'failedRecipients' => [],
], $context->getExtra());
$this->assertInstanceOf('Exception', $exception);
$this->assertSame('Failed', $exception->getMessage());
});

$client->send($message);
/** @var CM_Exception_Invalid $exception */
$this->assertSame(1, $sendMethod->getCallCount());
$this->assertSame(1, $errorMethod->getCallCount());
}

public function testSendFailed() {
$serviceManager = new CM_Service_Manager();
$logger = $this->mockObject('CM_Log_Logger');
$serviceManager->registerInstance('logger', $logger);
$transport = $this->mockInterface('Swift_Transport')->newInstance();
$transport->mockMethod('isStarted')->set(true);
$message = new Swift_Message('foo', 'content');
$message->setSender('[email protected]', 'Foobar');
$message->setFrom('[email protected]', 'Foobar');
$message->setTo('[email protected]');
$message->setCc('[email protected]', 'bar');

$client = new CM_Mail_Mailer($transport);
$client->setServiceManager($serviceManager);

$sendMethod = $transport->mockMethod('send')->set(0);
$warningMethod = $logger->mockMethod('warning');
$errorMethod = $logger->mockMethod('error')->set(function ($message, CM_Log_Context $context = null) {
$this->assertSame('Failed to send email to all recipients', $message);
$this->assertSame('Failed to send email', $message);
$this->assertSame([
'message' => [
'subject' => 'foo',
Expand All @@ -72,7 +132,6 @@ public function testSendFailed() {

/** @var CM_Exception_Invalid $exception */
$this->assertSame(1, $sendMethod->getCallCount());
$this->assertSame(0, $warningMethod->getCallCount());
$this->assertSame(1, $errorMethod->getCallCount());
}
}

0 comments on commit 126359c

Please sign in to comment.