* @author Chris Corbyn * @package Swift * @license GNU Lesser General Public License */ class Swift_BatchMailer { /** * The current instance of Swift. * @var Swift */ protected $swift; /** * The maximum number of times a single recipient can be attempted before giving up. * @var int */ protected $maxTries = 2; /** * The number of seconds to sleep for if an error occurs. * @var int */ protected $sleepTime = 0; /** * Failed recipients (undeliverable) * @var array */ protected $failed = array(); /** * The maximum number of successive failures before giving up. * @var int */ protected $maxFails = 0; /** * A temporary copy of some message headers. * @var array */ protected $headers = array(); /** * Constructor. * @param Swift The current instance of Swift */ public function __construct(Swift $swift) { $this->setSwift($swift); } /** * Set the current Swift instance. * @param Swift The instance */ public function setSwift(Swift $swift) { $this->swift = $swift; } /** * Get the Swift instance which is running. * @return Swift */ public function getSwift() { return $this->swift; } /** * Set the maximum number of times a single address is allowed to be retried. * @param int The maximum number of tries. */ public function setMaxTries($max) { $this->maxTries = abs($max); } /** * Get the number of times a single address will be attempted in a batch. * @return int */ public function getMaxTries() { return $this->maxTries; } /** * Set the amount of time to sleep for if an error occurs. * @param int Number of seconds */ public function setSleepTime($secs) { $this->sleepTime = abs($secs); } /** * Get the amount of time to sleep for on errors. * @return int */ public function getSleepTime() { return $this->sleepTime; } /** * Log a failed recipient. * @param string The email address. */ public function addFailedRecipient($address) { $this->failed[] = $address; $this->failed = array_unique($this->failed); } /** * Get all recipients which failed in this batch. * @return array */ public function getFailedRecipients() { return $this->failed; } /** * Clear out the list of failed recipients. */ public function flushFailedRecipients() { $this->failed = null; $this->failed = array(); } /** * Set the maximum number of times an error can be thrown in succession and still be hidden. * @param int */ public function setMaxSuccessiveFailures($fails) { $this->maxFails = abs($fails); } /** * Get the maximum number of times an error can be thrown and still be hidden. * @return int */ public function getMaxSuccessiveFailures() { return $this->maxFails; } /** * Restarts Swift forcibly. */ protected function forceRestartSwift() { //Pre-empting problems trying to issue "QUIT" to a dead connection $this->swift->connection->stop(); $this->swift->connection->start(); $this->swift->disconnect(); //Restart swift $this->swift->connect(); } /** * Takes a temporary copy of original message headers in case an error occurs and they need restoring. * @param Swift_Message The message object */ protected function copyMessageHeaders(&$message) { $this->headers["To"] = $message->headers->has("To") ? $message->headers->get("To") : null; $this->headers["Reply-To"] = $message->headers->has("Reply-To") ? $message->headers->get("Reply-To") : null; $this->headers["Return-Path"] = $message->headers->has("Return-Path") ? $message->headers->get("Return-Path") : null; $this->headers["From"] = $message->headers->has("From") ? $message->headers->get("From") : null; } /** * Restore message headers to original values in the event of a failure. * @param Swift_Message The message */ protected function restoreMessageHeaders(&$message) { foreach ($this->headers as $name => $value) { $message->headers->set($name, $value); } } /** * Run a batch send in a fail-safe manner. * This operates as Swift::batchSend() except it deals with errors itself. * @param Swift_Message To send * @param Swift_RecipientList Recipients (To: only) * @param Swift_Address The sender's address * @return int The number sent to */ public function send(Swift_Message $message, Swift_RecipientList $recipients, $sender) { $sent = 0; $successive_fails = 0; $it = $recipients->getIterator("to"); while ($it->hasNext()) { $it->next(); $recipient = $it->getValue(); $tried = 0; $loop = true; while ($loop && $tried < $this->getMaxTries()) { try { $tried++; $loop = false; $this->copyMessageHeaders($message); $sent += ($n = $this->swift->send($message, $recipient, $sender)); if (!$n) $this->addFailedRecipient($recipient->getAddress()); $successive_fails = 0; } catch (Exception $e) { $successive_fails++; $this->restoreMessageHeaders($message); if (($max = $this->getMaxSuccessiveFailures()) && $successive_fails > $max) { throw new Exception( "Too many successive failures. BatchMailer is configured to allow no more than " . $max . " successive failures."); } //If an exception was thrown, give it one more go if ($t = $this->getSleepTime()) sleep($t); $this->forceRestartSwift(); $loop = true; } } } return $sent; } }