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('Cannot lookup dependency "'.$itemName.'" since it is not registered.'); 105 } 106 107 switch ($this->store[$itemName]['lookupType']) { 108 case self::TYPE_ALIAS: 109 return $this->createAlias($itemName); 110 case self::TYPE_VALUE: 111 return $this->getValue($itemName); 112 case self::TYPE_INSTANCE: 113 return $this->createNewInstance($itemName); 114 case self::TYPE_SHARED: 115 return $this->createSharedInstance($itemName); 116 case self::TYPE_ARRAY: 117 return $this->createDependenciesFor($itemName); 118 } 119 } 120 121 /** 122 * Create an array of arguments passed to the constructor of $itemName. 123 * 124 * @param string $itemName 125 * 126 * @return array 127 */ 128 public function createDependenciesFor($itemName) 129 { 130 $args = []; 131 if (isset($this->store[$itemName]['args'])) { 132 $args = $this->resolveArgs($this->store[$itemName]['args']); 133 } 134 135 return $args; 136 } 137 138 /** 139 * Register a new dependency with $itemName. 140 * 141 * This method returns the current DependencyContainer instance because it 142 * requires the use of the fluid interface to set the specific details for the 143 * dependency. 144 * 145 * @see asNewInstanceOf(), asSharedInstanceOf(), asValue() 146 * 147 * @param string $itemName 148 * 149 * @return $this 150 */ 151 public function register($itemName) 152 { 153 $this->store[$itemName] = []; 154 $this->endPoint = &$this->store[$itemName]; 155 156 return $this; 157 } 158 159 /** 160 * Specify the previously registered item as a literal value. 161 * 162 * {@link register()} must be called before this will work. 163 * 164 * @param mixed $value 165 * 166 * @return $this 167 */ 168 public function asValue($value) 169 { 170 $endPoint = &$this->getEndPoint(); 171 $endPoint['lookupType'] = self::TYPE_VALUE; 172 $endPoint['value'] = $value; 173 174 return $this; 175 } 176 177 /** 178 * Specify the previously registered item as an alias of another item. 179 * 180 * @param string $lookup 181 * 182 * @return $this 183 */ 184 public function asAliasOf($lookup) 185 { 186 $endPoint = &$this->getEndPoint(); 187 $endPoint['lookupType'] = self::TYPE_ALIAS; 188 $endPoint['ref'] = $lookup; 189 190 return $this; 191 } 192 193 /** 194 * Specify the previously registered item as a new instance of $className. 195 * 196 * {@link register()} must be called before this will work. 197 * Any arguments can be set with {@link withDependencies()}, 198 * {@link addConstructorValue()} or {@link addConstructorLookup()}. 199 * 200 * @see withDependencies(), addConstructorValue(), addConstructorLookup() 201 * 202 * @param string $className 203 * 204 * @return $this 205 */ 206 public function asNewInstanceOf($className) 207 { 208 $endPoint = &$this->getEndPoint(); 209 $endPoint['lookupType'] = self::TYPE_INSTANCE; 210 $endPoint['className'] = $className; 211 212 return $this; 213 } 214 215 /** 216 * Specify the previously registered item as a shared instance of $className. 217 * 218 * {@link register()} must be called before this will work. 219 * 220 * @param string $className 221 * 222 * @return $this 223 */ 224 public function asSharedInstanceOf($className) 225 { 226 $endPoint = &$this->getEndPoint(); 227 $endPoint['lookupType'] = self::TYPE_SHARED; 228 $endPoint['className'] = $className; 229 230 return $this; 231 } 232 233 /** 234 * Specify the previously registered item as array of dependencies. 235 * 236 * {@link register()} must be called before this will work. 237 * 238 * @return $this 239 */ 240 public function asArray() 241 { 242 $endPoint = &$this->getEndPoint(); 243 $endPoint['lookupType'] = self::TYPE_ARRAY; 244 245 return $this; 246 } 247 248 /** 249 * Specify a list of injected dependencies for the previously registered item. 250 * 251 * This method takes an array of lookup names. 252 * 253 * @see addConstructorValue(), addConstructorLookup() 254 * 255 * @return $this 256 */ 257 public function withDependencies(array $lookups) 258 { 259 $endPoint = &$this->getEndPoint(); 260 $endPoint['args'] = []; 261 foreach ($lookups as $lookup) { 262 $this->addConstructorLookup($lookup); 263 } 264 265 return $this; 266 } 267 268 /** 269 * Specify a literal (non looked up) value for the constructor of the 270 * previously registered item. 271 * 272 * @see withDependencies(), addConstructorLookup() 273 * 274 * @param mixed $value 275 * 276 * @return $this 277 */ 278 public function addConstructorValue($value) 279 { 280 $endPoint = &$this->getEndPoint(); 281 if (!isset($endPoint['args'])) { 282 $endPoint['args'] = []; 283 } 284 $endPoint['args'][] = ['type' => 'value', 'item' => $value]; 285 286 return $this; 287 } 288 289 /** 290 * Specify a dependency lookup for the constructor of the previously 291 * registered item. 292 * 293 * @see withDependencies(), addConstructorValue() 294 * 295 * @param string $lookup 296 * 297 * @return $this 298 */ 299 public function addConstructorLookup($lookup) 300 { 301 $endPoint = &$this->getEndPoint(); 302 if (!isset($this->endPoint['args'])) { 303 $endPoint['args'] = []; 304 } 305 $endPoint['args'][] = ['type' => 'lookup', 'item' => $lookup]; 306 307 return $this; 308 } 309 310 /** Get the literal value with $itemName */ 311 private function getValue($itemName) 312 { 313 return $this->store[$itemName]['value']; 314 } 315 316 /** Resolve an alias to another item */ 317 private function createAlias($itemName) 318 { 319 return $this->lookup($this->store[$itemName]['ref']); 320 } 321 322 /** Create a fresh instance of $itemName */ 323 private function createNewInstance($itemName) 324 { 325 $reflector = new ReflectionClass($this->store[$itemName]['className']); 326 if ($reflector->getConstructor()) { 327 return $reflector->newInstanceArgs( 328 $this->createDependenciesFor($itemName) 329 ); 330 } 331 332 return $reflector->newInstance(); 333 } 334 335 /** Create and register a shared instance of $itemName */ 336 private function createSharedInstance($itemName) 337 { 338 if (!isset($this->store[$itemName]['instance'])) { 339 $this->store[$itemName]['instance'] = $this->createNewInstance($itemName); 340 } 341 342 return $this->store[$itemName]['instance']; 343 } 344 345 /** Get the current endpoint in the store */ 346 private function &getEndPoint() 347 { 348 if (!isset($this->endPoint)) { 349 throw new BadMethodCallException('Component must first be registered by calling register()'); 350 } 351 352 return $this->endPoint; 353 } 354 355 /** Get an argument list with dependencies resolved */ 356 private function resolveArgs(array $args) 357 { 358 $resolved = []; 359 foreach ($args as $argDefinition) { 360 switch ($argDefinition['type']) { 361 case 'lookup': 362 $resolved[] = $this->lookupRecursive($argDefinition['item']); 363 break; 364 case 'value': 365 $resolved[] = $argDefinition['item']; 366 break; 367 } 368 } 369 370 return $resolved; 371 } 372 373 /** Resolve a single dependency with an collections */ 374 private function lookupRecursive($item) 375 { 376 if (\is_array($item)) { 377 $collection = []; 378 foreach ($item as $k => $v) { 379 $collection[$k] = $this->lookupRecursive($v); 380 } 381 382 return $collection; 383 } 384 385 return $this->lookup($item); 386 } 387} 388