1<?php
2
3/*
4 * This file is part of SwiftMailer.
5 * (c) 2004-2009 Chris Corbyn
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
10
11//@require 'Swift/DependencyException.php';
12
13/**
14 * Dependency Injection container.
15 * @package Swift
16 * @author Chris Corbyn
17 */
18class Swift_DependencyContainer
19{
20
21  /** Constant for literal value types */
22  const TYPE_VALUE = 0x0001;
23
24  /** Constant for new instance types */
25  const TYPE_INSTANCE = 0x0010;
26
27  /** Constant for shared instance types */
28  const TYPE_SHARED = 0x0100;
29
30  /** Constant for aliases */
31  const TYPE_ALIAS = 0x1000;
32
33  /** Singleton instance */
34  private static $_instance = null;
35
36  /** The data container */
37  private $_store = array();
38
39  /** The current endpoint in the data container */
40  private $_endPoint;
41
42  /**
43   * Constructor should not be used.
44   * Use {@link getInstance()} instead.
45   */
46  public function __construct() { }
47
48  /**
49   * Returns a singleton of the DependencyContainer.
50   * @return Swift_DependencyContainer
51   */
52  public static function getInstance()
53  {
54    if (!isset(self::$_instance))
55    {
56      self::$_instance = new self();
57    }
58    return self::$_instance;
59  }
60
61  /**
62   * List the names of all items stored in the Container.
63   * @return array
64   */
65  public function listItems()
66  {
67    return array_keys($this->_store);
68  }
69
70  /**
71   * Test if an item is registered in this container with the given name.
72   * @param string $itemName
73   * @return boolean
74   * @see register()
75   */
76  public function has($itemName)
77  {
78    return array_key_exists($itemName, $this->_store)
79      && isset($this->_store[$itemName]['lookupType']);
80  }
81
82  /**
83   * Lookup the item with the given $itemName.
84   * @param string $itemName
85   * @return mixed
86   * @throws Swift_DependencyException If the dependency is not found
87   * @see register()
88   */
89  public function lookup($itemName)
90  {
91    if (!$this->has($itemName))
92    {
93      throw new Swift_DependencyException(
94        'Cannot lookup dependency "' . $itemName . '" since it is not registered.'
95        );
96    }
97
98    switch ($this->_store[$itemName]['lookupType'])
99    {
100      case self::TYPE_ALIAS:
101        return $this->_createAlias($itemName);
102      case self::TYPE_VALUE:
103        return $this->_getValue($itemName);
104      case self::TYPE_INSTANCE:
105        return $this->_createNewInstance($itemName);
106      case self::TYPE_SHARED:
107        return $this->_createSharedInstance($itemName);
108    }
109  }
110
111  /**
112   * Create an array of arguments passed to the constructor of $itemName.
113   * @param string $itemName
114   * @return array
115   */
116  public function createDependenciesFor($itemName)
117  {
118    $args = array();
119    if (isset($this->_store[$itemName]['args']))
120    {
121      $args = $this->_resolveArgs($this->_store[$itemName]['args']);
122    }
123    return $args;
124  }
125
126  /**
127   * Register a new dependency with $itemName.
128   * This method returns the current DependencyContainer instance because it
129   * requires the use of the fluid interface to set the specific details for the
130   * dependency.
131   *
132   * @param string $itemName
133   * @return Swift_DependencyContainer
134   * @see asNewInstanceOf(), asSharedInstanceOf(), asValue()
135   */
136  public function register($itemName)
137  {
138    $this->_store[$itemName] = array();
139    $this->_endPoint =& $this->_store[$itemName];
140    return $this;
141  }
142
143  /**
144   * Specify the previously registered item as a literal value.
145   * {@link register()} must be called before this will work.
146   *
147   * @param mixed $value
148   * @return Swift_DependencyContainer
149   */
150  public function asValue($value)
151  {
152    $endPoint =& $this->_getEndPoint();
153    $endPoint['lookupType'] = self::TYPE_VALUE;
154    $endPoint['value'] = $value;
155    return $this;
156  }
157
158  /**
159   * Specify the previously registered item as an alias of another item.
160   * @param string $lookup
161   * @return Swift_DependencyContainer
162   */
163  public function asAliasOf($lookup)
164  {
165    $endPoint =& $this->_getEndPoint();
166    $endPoint['lookupType'] = self::TYPE_ALIAS;
167    $endPoint['ref'] = $lookup;
168    return $this;
169  }
170
171  /**
172   * Specify the previously registered item as a new instance of $className.
173   * {@link register()} must be called before this will work.
174   * Any arguments can be set with {@link withDependencies()},
175   * {@link addConstructorValue()} or {@link addConstructorLookup()}.
176   *
177   * @param string $className
178   * @return Swift_DependencyContainer
179   * @see withDependencies(), addConstructorValue(), addConstructorLookup()
180   */
181  public function asNewInstanceOf($className)
182  {
183    $endPoint =& $this->_getEndPoint();
184    $endPoint['lookupType'] = self::TYPE_INSTANCE;
185    $endPoint['className'] = $className;
186    return $this;
187  }
188
189  /**
190   * Specify the previously registered item as a shared instance of $className.
191   * {@link register()} must be called before this will work.
192   * @param string $className
193   * @return Swift_DependencyContainer
194   */
195  public function asSharedInstanceOf($className)
196  {
197    $endPoint =& $this->_getEndPoint();
198    $endPoint['lookupType'] = self::TYPE_SHARED;
199    $endPoint['className'] = $className;
200    return $this;
201  }
202
203  /**
204   * Specify a list of injected dependencies for the previously registered item.
205   * This method takes an array of lookup names.
206   *
207   * @param array $lookups
208   * @return Swift_DependencyContainer
209   * @see addConstructorValue(), addConstructorLookup()
210   */
211  public function withDependencies(array $lookups)
212  {
213    $endPoint =& $this->_getEndPoint();
214    $endPoint['args'] = array();
215    foreach ($lookups as $lookup)
216    {
217      $this->addConstructorLookup($lookup);
218    }
219    return $this;
220  }
221
222  /**
223   * Specify a literal (non looked up) value for the constructor of the
224   * previously registered item.
225   *
226   * @param mixed $value
227   * @return Swift_DependencyContainer
228   * @see withDependencies(), addConstructorLookup()
229   */
230  public function addConstructorValue($value)
231  {
232    $endPoint =& $this->_getEndPoint();
233    if (!isset($endPoint['args']))
234    {
235      $endPoint['args'] = array();
236    }
237    $endPoint['args'][] = array('type' => 'value', 'item' => $value);
238    return $this;
239  }
240
241  /**
242   * Specify a dependency lookup for the constructor of the previously
243   * registered item.
244   *
245   * @param string $lookup
246   * @return Swift_DependencyContainer
247   * @see withDependencies(), addConstructorValue()
248   */
249  public function addConstructorLookup($lookup)
250  {
251    $endPoint =& $this->_getEndPoint();
252    if (!isset($this->_endPoint['args']))
253    {
254      $endPoint['args'] = array();
255    }
256    $endPoint['args'][] = array('type' => 'lookup', 'item' => $lookup);
257    return $this;
258  }
259
260  // -- Private methods
261
262  /** Get the literal value with $itemName */
263  private function _getValue($itemName)
264  {
265    return $this->_store[$itemName]['value'];
266  }
267
268  /** Resolve an alias to another item */
269  private function _createAlias($itemName)
270  {
271    return $this->lookup($this->_store[$itemName]['ref']);
272  }
273
274  /** Create a fresh instance of $itemName */
275  private function _createNewInstance($itemName)
276  {
277    $reflector = new ReflectionClass($this->_store[$itemName]['className']);
278    if ($reflector->getConstructor())
279    {
280      return $reflector->newInstanceArgs(
281        $this->createDependenciesFor($itemName)
282        );
283    }
284    else
285    {
286      return $reflector->newInstance();
287    }
288  }
289
290  /** Create and register a shared instance of $itemName */
291  private function _createSharedInstance($itemName)
292  {
293    if (!isset($this->_store[$itemName]['instance']))
294    {
295      $this->_store[$itemName]['instance'] = $this->_createNewInstance($itemName);
296    }
297    return $this->_store[$itemName]['instance'];
298  }
299
300  /** Get the current endpoint in the store */
301  private function &_getEndPoint()
302  {
303    if (!isset($this->_endPoint))
304    {
305      throw new BadMethodCallException(
306        'Component must first be registered by calling register()'
307        );
308    }
309    return $this->_endPoint;
310  }
311
312  /** Get an argument list with dependencies resolved */
313  private function _resolveArgs(array $args)
314  {
315    $resolved = array();
316    foreach ($args as $argDefinition)
317    {
318      switch ($argDefinition['type'])
319      {
320        case 'lookup':
321          $resolved[] = $this->_lookupRecursive($argDefinition['item']);
322          break;
323        case 'value':
324          $resolved[] = $argDefinition['item'];
325          break;
326      }
327    }
328    return $resolved;
329  }
330
331  /** Resolve a single dependency with an collections */
332  private function _lookupRecursive($item)
333  {
334    if (is_array($item))
335    {
336      $collection = array();
337      foreach ($item as $k => $v)
338      {
339        $collection[$k] = $this->_lookupRecursive($v);
340      }
341      return $collection;
342    }
343    else
344    {
345      return $this->lookup($item);
346    }
347  }
348
349}
350