BatchMailer.php 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. <?php
  2. /**
  3. * Handles batch mailing with Swift Mailer with fail-safe support.
  4. * Restarts the connection if it dies and then continues where it left off.
  5. * Please read the LICENSE file
  6. * @copyright Chris Corbyn <chris@w3style.co.uk>
  7. * @author Chris Corbyn <chris@w3style.co.uk>
  8. * @package Swift
  9. * @license GNU Lesser General Public License
  10. */
  11. class Swift_BatchMailer
  12. {
  13. /**
  14. * The current instance of Swift.
  15. * @var Swift
  16. */
  17. protected $swift;
  18. /**
  19. * The maximum number of times a single recipient can be attempted before giving up.
  20. * @var int
  21. */
  22. protected $maxTries = 2;
  23. /**
  24. * The number of seconds to sleep for if an error occurs.
  25. * @var int
  26. */
  27. protected $sleepTime = 0;
  28. /**
  29. * Failed recipients (undeliverable)
  30. * @var array
  31. */
  32. protected $failed = array();
  33. /**
  34. * The maximum number of successive failures before giving up.
  35. * @var int
  36. */
  37. protected $maxFails = 0;
  38. /**
  39. * A temporary copy of some message headers.
  40. * @var array
  41. */
  42. protected $headers = array();
  43. /**
  44. * Constructor.
  45. * @param Swift The current instance of Swift
  46. */
  47. public function __construct(Swift $swift)
  48. {
  49. $this->setSwift($swift);
  50. }
  51. /**
  52. * Set the current Swift instance.
  53. * @param Swift The instance
  54. */
  55. public function setSwift(Swift $swift)
  56. {
  57. $this->swift = $swift;
  58. }
  59. /**
  60. * Get the Swift instance which is running.
  61. * @return Swift
  62. */
  63. public function getSwift()
  64. {
  65. return $this->swift;
  66. }
  67. /**
  68. * Set the maximum number of times a single address is allowed to be retried.
  69. * @param int The maximum number of tries.
  70. */
  71. public function setMaxTries($max)
  72. {
  73. $this->maxTries = abs($max);
  74. }
  75. /**
  76. * Get the number of times a single address will be attempted in a batch.
  77. * @return int
  78. */
  79. public function getMaxTries()
  80. {
  81. return $this->maxTries;
  82. }
  83. /**
  84. * Set the amount of time to sleep for if an error occurs.
  85. * @param int Number of seconds
  86. */
  87. public function setSleepTime($secs)
  88. {
  89. $this->sleepTime = abs($secs);
  90. }
  91. /**
  92. * Get the amount of time to sleep for on errors.
  93. * @return int
  94. */
  95. public function getSleepTime()
  96. {
  97. return $this->sleepTime;
  98. }
  99. /**
  100. * Log a failed recipient.
  101. * @param string The email address.
  102. */
  103. public function addFailedRecipient($address)
  104. {
  105. $this->failed[] = $address;
  106. $this->failed = array_unique($this->failed);
  107. }
  108. /**
  109. * Get all recipients which failed in this batch.
  110. * @return array
  111. */
  112. public function getFailedRecipients()
  113. {
  114. return $this->failed;
  115. }
  116. /**
  117. * Clear out the list of failed recipients.
  118. */
  119. public function flushFailedRecipients()
  120. {
  121. $this->failed = null;
  122. $this->failed = array();
  123. }
  124. /**
  125. * Set the maximum number of times an error can be thrown in succession and still be hidden.
  126. * @param int
  127. */
  128. public function setMaxSuccessiveFailures($fails)
  129. {
  130. $this->maxFails = abs($fails);
  131. }
  132. /**
  133. * Get the maximum number of times an error can be thrown and still be hidden.
  134. * @return int
  135. */
  136. public function getMaxSuccessiveFailures()
  137. {
  138. return $this->maxFails;
  139. }
  140. /**
  141. * Restarts Swift forcibly.
  142. */
  143. protected function forceRestartSwift()
  144. {
  145. //Pre-empting problems trying to issue "QUIT" to a dead connection
  146. $this->swift->connection->stop();
  147. $this->swift->connection->start();
  148. $this->swift->disconnect();
  149. //Restart swift
  150. $this->swift->connect();
  151. }
  152. /**
  153. * Takes a temporary copy of original message headers in case an error occurs and they need restoring.
  154. * @param Swift_Message The message object
  155. */
  156. protected function copyMessageHeaders(&$message)
  157. {
  158. $this->headers["To"] = $message->headers->has("To") ?
  159. $message->headers->get("To") : null;
  160. $this->headers["Reply-To"] = $message->headers->has("Reply-To") ?
  161. $message->headers->get("Reply-To") : null;
  162. $this->headers["Return-Path"] = $message->headers->has("Return-Path") ?
  163. $message->headers->get("Return-Path") : null;
  164. $this->headers["From"] = $message->headers->has("From") ?
  165. $message->headers->get("From") : null;
  166. }
  167. /**
  168. * Restore message headers to original values in the event of a failure.
  169. * @param Swift_Message The message
  170. */
  171. protected function restoreMessageHeaders(&$message)
  172. {
  173. foreach ($this->headers as $name => $value)
  174. {
  175. $message->headers->set($name, $value);
  176. }
  177. }
  178. /**
  179. * Run a batch send in a fail-safe manner.
  180. * This operates as Swift::batchSend() except it deals with errors itself.
  181. * @param Swift_Message To send
  182. * @param Swift_RecipientList Recipients (To: only)
  183. * @param Swift_Address The sender's address
  184. * @return int The number sent to
  185. */
  186. public function send(Swift_Message $message, Swift_RecipientList $recipients, $sender)
  187. {
  188. $sent = 0;
  189. $successive_fails = 0;
  190. $it = $recipients->getIterator("to");
  191. while ($it->hasNext())
  192. {
  193. $it->next();
  194. $recipient = $it->getValue();
  195. $tried = 0;
  196. $loop = true;
  197. while ($loop && $tried < $this->getMaxTries())
  198. {
  199. try {
  200. $tried++;
  201. $loop = false;
  202. $this->copyMessageHeaders($message);
  203. $sent += ($n = $this->swift->send($message, $recipient, $sender));
  204. if (!$n) $this->addFailedRecipient($recipient->getAddress());
  205. $successive_fails = 0;
  206. } catch (Exception $e) {
  207. $successive_fails++;
  208. $this->restoreMessageHeaders($message);
  209. if (($max = $this->getMaxSuccessiveFailures())
  210. && $successive_fails > $max)
  211. {
  212. throw new Exception(
  213. "Too many successive failures. BatchMailer is configured to allow no more than " . $max .
  214. " successive failures.");
  215. }
  216. //If an exception was thrown, give it one more go
  217. if ($t = $this->getSleepTime()) sleep($t);
  218. $this->forceRestartSwift();
  219. $loop = true;
  220. }
  221. }
  222. }
  223. return $sent;
  224. }
  225. }