1<?php
2
3/**
4 * Swift Mailer Multiple Redundant Cycling Connection component.
5 * Please read the LICENSE file
6 * @author Chris Corbyn <chris@w3style.co.uk>
7 * @package Swift_Connection
8 * @license GNU Lesser General Public License
9 */
10
11require_once dirname(__FILE__) . "/../ClassLoader.php";
12Swift_ClassLoader::load("Swift_ConnectionBase");
13
14/**
15 * Swift Rotator Connection
16 * Switches through each connection in turn after sending each message
17 * @package Swift_Connection
18 * @author Chris Corbyn <chris@w3style.co.uk>
19 */
20class Swift_Connection_Rotator extends Swift_ConnectionBase
21{
22  /**
23   * The list of available connections
24   * @var array
25   */
26  protected $connections = array();
27  /**
28   * The id of the active connection
29   * @var int
30   */
31  protected $active = null;
32  /**
33   * Contains a list of any connections which were tried but found to be dead
34   * @var array
35   */
36  protected $dead = array();
37
38  /**
39   * Constructor
40   */
41  public function __construct($connections=array())
42  {
43    foreach ($connections as $id => $conn)
44    {
45      $this->addConnection($connections[$id], $id);
46    }
47  }
48  /**
49   * Add a connection to the list of options
50   * @param Swift_Connection An instance of the connection
51   */
52  public function addConnection(Swift_Connection $connection)
53  {
54    $log = Swift_LogContainer::getLog();
55    if ($log->hasLevel(Swift_Log::LOG_EVERYTHING))
56    {
57      $log->add("Adding new connection of type '" . get_class($connection) . "' to rotator.");
58    }
59    $this->connections[] = $connection;
60  }
61  /**
62   * Rotate to the next working connection
63   * @throws Swift_ConnectionException If no connections are available
64   */
65  public function nextConnection()
66  {
67    $log = Swift_LogContainer::getLog();
68    if ($log->hasLevel(Swift_Log::LOG_EVERYTHING))
69    {
70      $log->add(" <==> Rotating connection.");
71    }
72
73    $total = count($this->connections);
74    $start = $this->active === null ? 0 : ($this->active + 1);
75    if ($start >= $total) $start = 0;
76
77    $fail_messages = array();
78    for ($id = $start; $id < $total; $id++)
79    {
80      if (in_array($id, $this->dead)) continue; //The connection was previously found to be useless
81      try {
82        if (!$this->connections[$id]->isAlive()) $this->connections[$id]->start();
83        if ($this->connections[$id]->isAlive())
84        {
85          $this->active = $id;
86          return true;
87        }
88        else
89        {
90          $this->dead[] = $id;
91          $this->connections[$id]->stop();
92          throw new Swift_ConnectionException("The connection started but reported that it was not active");
93        }
94      } catch (Swift_ConnectionException $e) {
95        $fail_messages[] = $id . ": " . $e->getMessage();
96      }
97    }
98
99    $failure = implode("<br />", $fail_messages);
100    throw new Swift_ConnectionException("No connections were started.<br />" . $failure);
101  }
102  /**
103   * Read a full response from the buffer
104   * @return string
105   * @throws Swift_ConnectionException Upon failure to read
106   */
107  public function read()
108  {
109    if ($this->active === null)
110    {
111      throw new Swift_ConnectionException("None of the connections set have been started");
112    }
113    return $this->connections[$this->active]->read();
114  }
115  /**
116   * Write a command to the server (leave off trailing CRLF)
117   * @param string The command to send
118   * @throws Swift_ConnectionException Upon failure to write
119   */
120  public function write($command, $end="\r\n")
121  {
122    if ($this->active === null)
123    {
124      throw new Swift_ConnectionException("None of the connections set have been started");
125    }
126    return $this->connections[$this->active]->write($command, $end);
127  }
128  /**
129   * Try to start the connection
130   * @throws Swift_ConnectionException Upon failure to start
131   */
132  public function start()
133  {
134    if ($this->active === null) $this->nextConnection();
135  }
136  /**
137   * Try to close the connection
138   * @throws Swift_ConnectionException Upon failure to close
139   */
140  public function stop()
141  {
142    foreach ($this->connections as $id => $conn)
143    {
144      if ($this->connections[$id]->isAlive()) $this->connections[$id]->stop();
145    }
146    $this->active = null;
147  }
148  /**
149   * Check if the current connection is alive
150   * @return boolean
151   */
152  public function isAlive()
153  {
154    return (($this->active !== null) && $this->connections[$this->active]->isAlive());
155  }
156  /**
157   * Get the ID of the active connection
158   * @return int
159   */
160  public function getActive()
161  {
162    return $this->active;
163  }
164  /**
165   * Call the current connection's postConnect() method
166   */
167  public function postConnect(Swift $instance)
168  {
169    Swift_ClassLoader::load("Swift_Plugin_ConnectionRotator");
170    if (!$instance->getPlugin("_ROTATOR")) $instance->attachPlugin(new Swift_Plugin_ConnectionRotator(), "_ROTATOR");
171    $this->connections[$this->active]->postConnect($instance);
172  }
173  /**
174   * Call the current connection's setExtension() method
175   */
176  public function setExtension($extension, $attributes=array())
177  {
178    $this->connections[$this->active]->setExtension($extension, $attributes);
179  }
180  /**
181   * Call the current connection's hasExtension() method
182   */
183  public function hasExtension($name)
184  {
185    return $this->connections[$this->active]->hasExtension($name);
186  }
187  /**
188   * Call the current connection's getAttributes() method
189   */
190  public function getAttributes($name)
191  {
192    return $this->connections[$this->active]->getAttributes($name);
193  }
194}
195