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: /**
12: * Dependency Injection container.
13: * @package Swift
14: * @author Chris Corbyn
15: */
16: class Swift_DependencyContainer
17: {
18: /** Constant for literal value types */
19: const TYPE_VALUE = 0x0001;
20:
21: /** Constant for new instance types */
22: const TYPE_INSTANCE = 0x0010;
23:
24: /** Constant for shared instance types */
25: const TYPE_SHARED = 0x0100;
26:
27: /** Constant for aliases */
28: const TYPE_ALIAS = 0x1000;
29:
30: /** Singleton instance */
31: private static $_instance = null;
32:
33: /** The data container */
34: private $_store = array();
35:
36: /** The current endpoint in the data container */
37: private $_endPoint;
38:
39: /**
40: * Constructor should not be used.
41: * Use {@link getInstance()} instead.
42: */
43: public function __construct() { }
44:
45: /**
46: * Returns a singleton of the DependencyContainer.
47: * @return Swift_DependencyContainer
48: */
49: public static function getInstance()
50: {
51: if (!isset(self::$_instance)) {
52: self::$_instance = new self();
53: }
54:
55: return self::$_instance;
56: }
57:
58: /**
59: * List the names of all items stored in the Container.
60: * @return array
61: */
62: public function listItems()
63: {
64: return array_keys($this->_store);
65: }
66:
67: /**
68: * Test if an item is registered in this container with the given name.
69: * @param string $itemName
70: * @return boolean
71: * @see register()
72: */
73: public function has($itemName)
74: {
75: return array_key_exists($itemName, $this->_store)
76: && isset($this->_store[$itemName]['lookupType']);
77: }
78:
79: /**
80: * Lookup the item with the given $itemName.
81: * @param string $itemName
82: * @return mixed
83: * @throws Swift_DependencyException If the dependency is not found
84: * @see register()
85: */
86: public function lookup($itemName)
87: {
88: if (!$this->has($itemName)) {
89: throw new Swift_DependencyException(
90: 'Cannot lookup dependency "' . $itemName . '" since it is not registered.'
91: );
92: }
93:
94: switch ($this->_store[$itemName]['lookupType']) {
95: case self::TYPE_ALIAS:
96: return $this->_createAlias($itemName);
97: case self::TYPE_VALUE:
98: return $this->_getValue($itemName);
99: case self::TYPE_INSTANCE:
100: return $this->_createNewInstance($itemName);
101: case self::TYPE_SHARED:
102: return $this->_createSharedInstance($itemName);
103: }
104: }
105:
106: /**
107: * Create an array of arguments passed to the constructor of $itemName.
108: * @param string $itemName
109: * @return array
110: */
111: public function createDependenciesFor($itemName)
112: {
113: $args = array();
114: if (isset($this->_store[$itemName]['args'])) {
115: $args = $this->_resolveArgs($this->_store[$itemName]['args']);
116: }
117:
118: return $args;
119: }
120:
121: /**
122: * Register a new dependency with $itemName.
123: * This method returns the current DependencyContainer instance because it
124: * requires the use of the fluid interface to set the specific details for the
125: * dependency.
126: *
127: * @param string $itemName
128: * @return Swift_DependencyContainer
129: * @see asNewInstanceOf(), asSharedInstanceOf(), asValue()
130: */
131: public function register($itemName)
132: {
133: $this->_store[$itemName] = array();
134: $this->_endPoint =& $this->_store[$itemName];
135:
136: return $this;
137: }
138:
139: /**
140: * Specify the previously registered item as a literal value.
141: * {@link register()} must be called before this will work.
142: *
143: * @param mixed $value
144: * @return Swift_DependencyContainer
145: */
146: public function asValue($value)
147: {
148: $endPoint =& $this->_getEndPoint();
149: $endPoint['lookupType'] = self::TYPE_VALUE;
150: $endPoint['value'] = $value;
151:
152: return $this;
153: }
154:
155: /**
156: * Specify the previously registered item as an alias of another item.
157: * @param string $lookup
158: * @return Swift_DependencyContainer
159: */
160: public function asAliasOf($lookup)
161: {
162: $endPoint =& $this->_getEndPoint();
163: $endPoint['lookupType'] = self::TYPE_ALIAS;
164: $endPoint['ref'] = $lookup;
165:
166: return $this;
167: }
168:
169: /**
170: * Specify the previously registered item as a new instance of $className.
171: * {@link register()} must be called before this will work.
172: * Any arguments can be set with {@link withDependencies()},
173: * {@link addConstructorValue()} or {@link addConstructorLookup()}.
174: *
175: * @param string $className
176: * @return Swift_DependencyContainer
177: * @see withDependencies(), addConstructorValue(), addConstructorLookup()
178: */
179: public function asNewInstanceOf($className)
180: {
181: $endPoint =& $this->_getEndPoint();
182: $endPoint['lookupType'] = self::TYPE_INSTANCE;
183: $endPoint['className'] = $className;
184:
185: return $this;
186: }
187:
188: /**
189: * Specify the previously registered item as a shared instance of $className.
190: * {@link register()} must be called before this will work.
191: * @param string $className
192: * @return Swift_DependencyContainer
193: */
194: public function asSharedInstanceOf($className)
195: {
196: $endPoint =& $this->_getEndPoint();
197: $endPoint['lookupType'] = self::TYPE_SHARED;
198: $endPoint['className'] = $className;
199:
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: $this->addConstructorLookup($lookup);
217: }
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: $endPoint['args'] = array();
235: }
236: $endPoint['args'][] = array('type' => 'value', 'item' => $value);
237:
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: $endPoint['args'] = array();
254: }
255: $endPoint['args'][] = array('type' => 'lookup', 'item' => $lookup);
256:
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: return $reflector->newInstanceArgs(
280: $this->createDependenciesFor($itemName)
281: );
282: } else {
283: return $reflector->newInstance();
284: }
285: }
286:
287: /** Create and register a shared instance of $itemName */
288: private function _createSharedInstance($itemName)
289: {
290: if (!isset($this->_store[$itemName]['instance'])) {
291: $this->_store[$itemName]['instance'] = $this->_createNewInstance($itemName);
292: }
293:
294: return $this->_store[$itemName]['instance'];
295: }
296:
297: /** Get the current endpoint in the store */
298: private function &_getEndPoint()
299: {
300: if (!isset($this->_endPoint)) {
301: throw new BadMethodCallException(
302: 'Component must first be registered by calling register()'
303: );
304: }
305:
306: return $this->_endPoint;
307: }
308:
309: /** Get an argument list with dependencies resolved */
310: private function _resolveArgs(array $args)
311: {
312: $resolved = array();
313: foreach ($args as $argDefinition) {
314: switch ($argDefinition['type']) {
315: case 'lookup':
316: $resolved[] = $this->_lookupRecursive($argDefinition['item']);
317: break;
318: case 'value':
319: $resolved[] = $argDefinition['item'];
320: break;
321: }
322: }
323:
324: return $resolved;
325: }
326:
327: /** Resolve a single dependency with an collections */
328: private function _lookupRecursive($item)
329: {
330: if (is_array($item)) {
331: $collection = array();
332: foreach ($item as $k => $v) {
333: $collection[$k] = $this->_lookupRecursive($v);
334: }
335:
336: return $collection;
337: } else {
338: return $this->lookup($item);
339: }
340: }
341: }
342: