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