1<?php 2 3/* 4 * This file is part of the symfony package. 5 * (c) Fabien Potencier <fabien.potencier@symfony-project.com> 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 * sfServiceContainerBuilder is a DI container that provides an interface to build the services. 13 * 14 * @package symfony 15 * @subpackage service 16 * @author Fabien Potencier <fabien.potencier@symfony-project.com> 17 * @version SVN: $Id: sfServiceContainerBuilder.php 269 2009-03-26 20:39:16Z fabien $ 18 */ 19class sfServiceContainerBuilder extends sfServiceContainer 20{ 21 protected 22 $definitions = array(), 23 $aliases = array(), 24 $loading = array(); 25 26 /** 27 * Sets a service. 28 * 29 * @param string $id The service identifier 30 * @param object $service The service instance 31 */ 32 public function setService($id, $service) 33 { 34 unset($this->aliases[$id]); 35 36 parent::setService($id, $service); 37 } 38 39 /** 40 * Returns true if the given service is defined. 41 * 42 * @param string $id The service identifier 43 * 44 * @return Boolean true if the service is defined, false otherwise 45 */ 46 public function hasService($id) 47 { 48 return isset($this->definitions[$id]) || isset($this->aliases[$id]) || parent::hasService($id); 49 } 50 51 /** 52 * Gets a service. 53 * 54 * @param string $id The service identifier 55 * 56 * @return object The associated service 57 * 58 * @throw InvalidArgumentException if the service is not defined 59 * @throw LogicException if the service has a circular reference to itself 60 */ 61 public function getService($id) 62 { 63 try 64 { 65 return parent::getService($id); 66 } 67 catch (InvalidArgumentException $e) 68 { 69 if (isset($this->loading[$id])) 70 { 71 throw new LogicException(sprintf('The service "%s" has a circular reference to itself.', $id)); 72 } 73 74 if (!$this->hasServiceDefinition($id) && isset($this->aliases[$id])) 75 { 76 return $this->getService($this->aliases[$id]); 77 } 78 79 $definition = $this->getServiceDefinition($id); 80 81 $this->loading[$id] = true; 82 83 if ($definition->isShared()) 84 { 85 $service = $this->services[$id] = $this->createService($definition); 86 } 87 else 88 { 89 $service = $this->createService($definition); 90 } 91 92 unset($this->loading[$id]); 93 94 return $service; 95 } 96 } 97 98 /** 99 * Gets all service ids. 100 * 101 * @return array An array of all defined service ids 102 */ 103 public function getServiceIds() 104 { 105 return array_unique(array_merge(array_keys($this->getServiceDefinitions()), array_keys($this->aliases), parent::getServiceIds())); 106 } 107 108 /** 109 * Sets an alias for an existing service. 110 * 111 * @param string $alias The alias to create 112 * @param string $id The service to alias 113 */ 114 public function setAlias($alias, $id) 115 { 116 $this->aliases[$alias] = $id; 117 } 118 119 /** 120 * Gets all defined aliases. 121 * 122 * @return array An array of aliases 123 */ 124 public function getAliases() 125 { 126 return $this->aliases; 127 } 128 129 /** 130 * Registers a service definition. 131 * 132 * This methods allows for simple registration of service definition 133 * with a fluid interface. 134 * 135 * @param string $id The service identifier 136 * @param string $class The service class 137 * 138 * @return sfServiceDefinition A sfServiceDefinition instance 139 */ 140 public function register($id, $class) 141 { 142 return $this->setServiceDefinition($id, new sfServiceDefinition($class)); 143 } 144 145 /** 146 * Adds the service definitions. 147 * 148 * @param array $definitions An array of service definitions 149 */ 150 public function addServiceDefinitions(array $definitions) 151 { 152 foreach ($definitions as $id => $definition) 153 { 154 $this->setServiceDefinition($id, $definition); 155 } 156 } 157 158 /** 159 * Sets the service definitions. 160 * 161 * @param array $definitions An array of service definitions 162 */ 163 public function setServiceDefinitions(array $definitions) 164 { 165 $this->definitions = array(); 166 $this->addServiceDefinitions($definitions); 167 } 168 169 /** 170 * Gets all service definitions. 171 * 172 * @return array An array of sfServiceDefinition instances 173 */ 174 public function getServiceDefinitions() 175 { 176 return $this->definitions; 177 } 178 179 /** 180 * Sets a service definition. 181 * 182 * @param string $id The service identifier 183 * @param sfServiceDefinition $definition A sfServiceDefinition instance 184 * 185 * @return sfServiceDefinition 186 */ 187 public function setServiceDefinition($id, sfServiceDefinition $definition) 188 { 189 unset($this->aliases[$id]); 190 191 return $this->definitions[$id] = $definition; 192 } 193 194 /** 195 * Returns true if a service definition exists under the given identifier. 196 * 197 * @param string $id The service identifier 198 * 199 * @return Boolean true if the service definition exists, false otherwise 200 */ 201 public function hasServiceDefinition($id) 202 { 203 return array_key_exists($id, $this->definitions); 204 } 205 206 /** 207 * Gets a service definition. 208 * 209 * @param string $id The service identifier 210 * 211 * @return sfServiceDefinition A sfServiceDefinition instance 212 * 213 * @throw InvalidArgumentException if the service definition does not exist 214 */ 215 public function getServiceDefinition($id) 216 { 217 if (!$this->hasServiceDefinition($id)) 218 { 219 throw new InvalidArgumentException(sprintf('The service definition "%s" does not exist.', $id)); 220 } 221 222 return $this->definitions[$id]; 223 } 224 225 /** 226 * Creates a service for a service definition. 227 * 228 * @param sfServiceDefinition $definition A service definition instance 229 * 230 * @return object The service described by the service definition 231 */ 232 protected function createService(sfServiceDefinition $definition) 233 { 234 if (null !== $definition->getFile()) 235 { 236 require_once $this->resolveValue($definition->getFile()); 237 } 238 239 $r = new ReflectionClass($this->resolveValue($definition->getClass())); 240 241 $arguments = $this->resolveServices($this->resolveValue($definition->getArguments())); 242 243 if (null !== $definition->getConstructor()) 244 { 245 $service = call_user_func_array(array($this->resolveValue($definition->getClass()), $definition->getConstructor()), $arguments); 246 } 247 else 248 { 249 $service = null === $r->getConstructor() ? $r->newInstance() : $r->newInstanceArgs($arguments); 250 } 251 252 foreach ($definition->getMethodCalls() as $call) 253 { 254 call_user_func_array(array($service, $call[0]), $this->resolveServices($this->resolveValue($call[1]))); 255 } 256 257 if ($callable = $definition->getConfigurator()) 258 { 259 if (is_array($callable) && is_object($callable[0]) && $callable[0] instanceof sfServiceReference) 260 { 261 $callable[0] = $this->getService((string) $callable[0]); 262 } 263 elseif (is_array($callable)) 264 { 265 $callable[0] = $this->resolveValue($callable[0]); 266 } 267 268 if (!is_callable($callable)) 269 { 270 throw new InvalidArgumentException(sprintf('The configure callable for class "%s" is not a callable.', get_class($service))); 271 } 272 273 call_user_func($callable, $service); 274 } 275 276 return $service; 277 } 278 279 /** 280 * Replaces parameter placeholders (%name%) by their values. 281 * 282 * @param mixed $value A value 283 * 284 * @return mixed The same value with all placeholders replaced by their values 285 * 286 * @throw RuntimeException if a placeholder references a parameter that does not exist 287 */ 288 public function resolveValue($value) 289 { 290 if (is_array($value)) 291 { 292 $args = array(); 293 foreach ($value as $k => $v) 294 { 295 $args[$this->resolveValue($k)] = $this->resolveValue($v); 296 } 297 298 $value = $args; 299 } 300 else if (is_string($value)) 301 { 302 if (preg_match('/^%([^%]+)%$/', $value, $match)) 303 { 304 $value = $this->getParameter($match[1]); 305 } 306 else 307 { 308 $value = str_replace('%%', '%', preg_replace_callback('/(?<!%)(%)([^%]+)\1/', array($this, 'replaceParameter'), $value)); 309 } 310 } 311 312 return $value; 313 } 314 315 /** 316 * Replaces service references by the real service instance. 317 * 318 * @param mixed $value A value 319 * 320 * @return mixed The same value with all service references replaced by the real service instances 321 */ 322 public function resolveServices($value) 323 { 324 if (is_array($value)) 325 { 326 $value = array_map(array($this, 'resolveServices'), $value); 327 } 328 else if (is_object($value) && $value instanceof sfServiceReference) 329 { 330 $value = $this->getService((string) $value); 331 } 332 333 return $value; 334 } 335 336 protected function replaceParameter($match) 337 { 338 return $this->getParameter($match[2]); 339 } 340} 341