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