Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions src/Mail/FallbackMailer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<?php

/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/

namespace Nette\Mail;

use Nette;
use Nette\InvalidArgumentException;


class FallbackMailer implements IMailer
{
use Nette\SmartObject;

/** @var callable[] function (FallbackMailer $sender, SendException $e, IMailer $mailer, Message $mail) */
public $onFailure;

/** @var IMailer[] */
private $mailers;

/** @var int */
private $retryCount;

/** @var int in miliseconds */
private $retryWaitTime;


/**
* @param IMailer[]
* @param int
* @param int in miliseconds
*/
public function __construct(array $mailers, $retryCount = 3, $retryWaitTime = 1000)
{
if (!$mailers) {
throw new InvalidArgumentException('At least one mailer must be provided.');
}

$this->mailers = $mailers;
$this->retryCount = $retryCount;
$this->retryWaitTime = $retryWaitTime;
}


/**
* Sends email.
* @return void
* @throws FallbackMailerException
*/
public function send(Message $mail)
{
for ($i = 0; $i < $this->retryCount; $i++) {
Copy link
Member

@milo milo May 21, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about to check, that mailers is not empty?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I'll add the check in constructor.

if ($i > 0) {
usleep($this->retryWaitTime * 1000);
}

foreach ($this->mailers as $mailer) {
try {
$mailer->send($mail);
return;

} catch (SendException $e) {
$failures[] = $e;
$this->onFailure($this, $e, $mailer, $mail);
}
}
}

$e = new FallbackMailerException('All mailers failed to send the message.', 0, $failures[0]);
$e->failures = $failures;
throw $e;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self note: wrap this with another SendException instance

}


/**
* @return IMailer[]
*/
public function getMailers()
{
return $this->mailers;
}


/**
* @param IMailer[]
* @return self
*/
public function addMailer(IMailer $mailer)
{
$this->mailers[] = $mailer;
}


/**
* @return int
*/
public function getRetryCount()
{
return $this->retryCount;
}


/**
* @param int
*/
public function setRetryCount($retryCount)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All setters in nette should return $this. But even better is to omit anything that is not really needed. I think that except addMailer() is not need nothing. And with addMailer() should be if (!$mailers) { throw ... tested later.

{
$this->retryCount = $retryCount;
}


/**
* @return int in miliseconds
*/
public function getRetryWaitTime()
{
return $this->retryWaitTime;
}


/**
* @param int in miliseconds
*/
public function setRetryWaitTime($retryWaitTime)
{
$this->retryWaitTime = $retryWaitTime;
}

}
7 changes: 7 additions & 0 deletions src/Mail/exceptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,10 @@ class SendException extends Nette\InvalidStateException
class SmtpException extends SendException
{
}


class FallbackMailerException extends SendException
{
/** @var SendException[] */
public $failures;
}
88 changes: 88 additions & 0 deletions tests/Mail/FallbackMailer.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

/**
* Test: Nette\Mail\FallbackMailer
*/

use Nette\Mail\FallbackMailer;
use Nette\Mail\FallbackMailerException;
use Nette\Mail\IMailer;
use Nette\Mail\Message;
use Nette\Mail\SendException;
use Tester\Assert;


require __DIR__ . '/../bootstrap.php';

require __DIR__ . '/Mail.php';


class FailingMailer implements IMailer
{
private $failedTimes;


public function __construct($failedTimes)
{
$this->failedTimes = $failedTimes;
}


public function send(Message $mail)
{
static $count = 0;
if ($this->failedTimes--) {
throw new SendException('Failure #' . (++$count));
}
}
}


$send = function () {
$message = new Message();
$this->send($message);
};


test(function () use ($send) {
$subMailerA = new FailingMailer(3);
$subMailerB = new FailingMailer(3);

$mailer = new FallbackMailer([$subMailerA, $subMailerB], 3, 10);
$mailer->onFailure[] = function (FallbackMailer $sender, SendException $e, IMailer $mailer, Message $mail) use (& $onFailureCalls) {
$onFailureCalls[] = $mailer;
};

$e = Assert::exception($send->bindTo($mailer), FallbackMailerException::class, 'All mailers failed to send the message.');
Assert::same([$subMailerA, $subMailerB, $subMailerA, $subMailerB, $subMailerA, $subMailerB], $onFailureCalls);
Assert::count(6, $e->failures);
Assert::same('Failure #1', $e->getPrevious()->getMessage());
});


test(function () use ($send) {
$subMailerA = new FailingMailer(3);
$subMailerB = new FailingMailer(2);

$mailer = new FallbackMailer([$subMailerA, $subMailerB], 3, 10);
$mailer->onFailure[] = function (FallbackMailer $sender, SendException $e, IMailer $mailer, Message $mail) use (& $onFailureCalls) {
$onFailureCalls[] = $mailer;
};

$send->bindTo($mailer)->__invoke();
Assert::same([$subMailerA, $subMailerB, $subMailerA, $subMailerB, $subMailerA], $onFailureCalls);
});


test(function () use ($send) {
$subMailerA = new FailingMailer(0);
$subMailerB = new FailingMailer(2);

$mailer = new FallbackMailer([$subMailerA, $subMailerB], 3, 10);
$mailer->onFailure[] = function (FallbackMailer $sender, SendException $e, IMailer $mailer, Message $mail) use (& $onFailureCalls) {
$onFailureCalls[] = $mailer;
};

$send->bindTo($mailer)->__invoke();
Assert::null($onFailureCalls);
});
6 changes: 6 additions & 0 deletions tests/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,9 @@

Tester\Environment::setup();
date_default_timezone_set('Europe/Prague');


function test(\Closure $function)
{
$function();
}