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