ContainerBuilder.php 32 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.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. namespace Symfony\Component\DependencyInjection;
  11. use Symfony\Component\DependencyInjection\Compiler\Compiler;
  12. use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
  13. use Symfony\Component\DependencyInjection\Compiler\PassConfig;
  14. use Symfony\Component\DependencyInjection\Exception\BadMethodCallException;
  15. use Symfony\Component\DependencyInjection\Exception\InactiveScopeException;
  16. use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
  17. use Symfony\Component\DependencyInjection\Exception\LogicException;
  18. use Symfony\Component\DependencyInjection\Exception\RuntimeException;
  19. use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
  20. use Symfony\Component\Config\Resource\FileResource;
  21. use Symfony\Component\Config\Resource\ResourceInterface;
  22. use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface;
  23. use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\RealServiceInstantiator;
  24. /**
  25. * ContainerBuilder is a DI container that provides an API to easily describe services.
  26. *
  27. * @author Fabien Potencier <fabien@symfony.com>
  28. *
  29. * @api
  30. */
  31. class ContainerBuilder extends Container implements TaggedContainerInterface
  32. {
  33. /**
  34. * @var ExtensionInterface[]
  35. */
  36. private $extensions = array();
  37. /**
  38. * @var ExtensionInterface[]
  39. */
  40. private $extensionsByNs = array();
  41. /**
  42. * @var Definition[]
  43. */
  44. private $definitions = array();
  45. /**
  46. * @var Definition[]
  47. */
  48. private $obsoleteDefinitions = array();
  49. /**
  50. * @var Alias[]
  51. */
  52. private $aliasDefinitions = array();
  53. /**
  54. * @var ResourceInterface[]
  55. */
  56. private $resources = array();
  57. private $extensionConfigs = array();
  58. /**
  59. * @var Compiler
  60. */
  61. private $compiler;
  62. private $trackResources = true;
  63. /**
  64. * @var InstantiatorInterface|null
  65. */
  66. private $proxyInstantiator;
  67. /**
  68. * Sets the track resources flag.
  69. *
  70. * If you are not using the loaders and therefore don't want
  71. * to depend on the Config component, set this flag to false.
  72. *
  73. * @param Boolean $track true if you want to track resources, false otherwise
  74. */
  75. public function setResourceTracking($track)
  76. {
  77. $this->trackResources = (Boolean) $track;
  78. }
  79. /**
  80. * Checks if resources are tracked.
  81. *
  82. * @return Boolean true if resources are tracked, false otherwise
  83. */
  84. public function isTrackingResources()
  85. {
  86. return $this->trackResources;
  87. }
  88. /**
  89. * Sets the instantiator to be used when fetching proxies.
  90. *
  91. * @param InstantiatorInterface $proxyInstantiator
  92. */
  93. public function setProxyInstantiator(InstantiatorInterface $proxyInstantiator)
  94. {
  95. $this->proxyInstantiator = $proxyInstantiator;
  96. }
  97. /**
  98. * Registers an extension.
  99. *
  100. * @param ExtensionInterface $extension An extension instance
  101. *
  102. * @api
  103. */
  104. public function registerExtension(ExtensionInterface $extension)
  105. {
  106. $this->extensions[$extension->getAlias()] = $extension;
  107. if (false !== $extension->getNamespace()) {
  108. $this->extensionsByNs[$extension->getNamespace()] = $extension;
  109. }
  110. }
  111. /**
  112. * Returns an extension by alias or namespace.
  113. *
  114. * @param string $name An alias or a namespace
  115. *
  116. * @return ExtensionInterface An extension instance
  117. *
  118. * @throws LogicException if the extension is not registered
  119. *
  120. * @api
  121. */
  122. public function getExtension($name)
  123. {
  124. if (isset($this->extensions[$name])) {
  125. return $this->extensions[$name];
  126. }
  127. if (isset($this->extensionsByNs[$name])) {
  128. return $this->extensionsByNs[$name];
  129. }
  130. throw new LogicException(sprintf('Container extension "%s" is not registered', $name));
  131. }
  132. /**
  133. * Returns all registered extensions.
  134. *
  135. * @return ExtensionInterface[] An array of ExtensionInterface
  136. *
  137. * @api
  138. */
  139. public function getExtensions()
  140. {
  141. return $this->extensions;
  142. }
  143. /**
  144. * Checks if we have an extension.
  145. *
  146. * @param string $name The name of the extension
  147. *
  148. * @return Boolean If the extension exists
  149. *
  150. * @api
  151. */
  152. public function hasExtension($name)
  153. {
  154. return isset($this->extensions[$name]) || isset($this->extensionsByNs[$name]);
  155. }
  156. /**
  157. * Returns an array of resources loaded to build this configuration.
  158. *
  159. * @return ResourceInterface[] An array of resources
  160. *
  161. * @api
  162. */
  163. public function getResources()
  164. {
  165. return array_unique($this->resources);
  166. }
  167. /**
  168. * Adds a resource for this configuration.
  169. *
  170. * @param ResourceInterface $resource A resource instance
  171. *
  172. * @return ContainerBuilder The current instance
  173. *
  174. * @api
  175. */
  176. public function addResource(ResourceInterface $resource)
  177. {
  178. if (!$this->trackResources) {
  179. return $this;
  180. }
  181. $this->resources[] = $resource;
  182. return $this;
  183. }
  184. /**
  185. * Sets the resources for this configuration.
  186. *
  187. * @param ResourceInterface[] $resources An array of resources
  188. *
  189. * @return ContainerBuilder The current instance
  190. *
  191. * @api
  192. */
  193. public function setResources(array $resources)
  194. {
  195. if (!$this->trackResources) {
  196. return $this;
  197. }
  198. $this->resources = $resources;
  199. return $this;
  200. }
  201. /**
  202. * Adds the object class hierarchy as resources.
  203. *
  204. * @param object $object An object instance
  205. *
  206. * @return ContainerBuilder The current instance
  207. *
  208. * @api
  209. */
  210. public function addObjectResource($object)
  211. {
  212. if ($this->trackResources) {
  213. $this->addClassResource(new \ReflectionClass($object));
  214. }
  215. return $this;
  216. }
  217. /**
  218. * Adds the given class hierarchy as resources.
  219. *
  220. * @param \ReflectionClass $class
  221. *
  222. * @return ContainerBuilder The current instance
  223. */
  224. public function addClassResource(\ReflectionClass $class)
  225. {
  226. if (!$this->trackResources) {
  227. return $this;
  228. }
  229. do {
  230. $this->addResource(new FileResource($class->getFileName()));
  231. } while ($class = $class->getParentClass());
  232. return $this;
  233. }
  234. /**
  235. * Loads the configuration for an extension.
  236. *
  237. * @param string $extension The extension alias or namespace
  238. * @param array $values An array of values that customizes the extension
  239. *
  240. * @return ContainerBuilder The current instance
  241. * @throws BadMethodCallException When this ContainerBuilder is frozen
  242. *
  243. * @throws \LogicException if the container is frozen
  244. *
  245. * @api
  246. */
  247. public function loadFromExtension($extension, array $values = array())
  248. {
  249. if ($this->isFrozen()) {
  250. throw new BadMethodCallException('Cannot load from an extension on a frozen container.');
  251. }
  252. $namespace = $this->getExtension($extension)->getAlias();
  253. $this->extensionConfigs[$namespace][] = $values;
  254. return $this;
  255. }
  256. /**
  257. * Adds a compiler pass.
  258. *
  259. * @param CompilerPassInterface $pass A compiler pass
  260. * @param string $type The type of compiler pass
  261. *
  262. * @return ContainerBuilder The current instance
  263. *
  264. * @api
  265. */
  266. public function addCompilerPass(CompilerPassInterface $pass, $type = PassConfig::TYPE_BEFORE_OPTIMIZATION)
  267. {
  268. if (null === $this->compiler) {
  269. $this->compiler = new Compiler();
  270. }
  271. $this->compiler->addPass($pass, $type);
  272. $this->addObjectResource($pass);
  273. return $this;
  274. }
  275. /**
  276. * Returns the compiler pass config which can then be modified.
  277. *
  278. * @return PassConfig The compiler pass config
  279. *
  280. * @api
  281. */
  282. public function getCompilerPassConfig()
  283. {
  284. if (null === $this->compiler) {
  285. $this->compiler = new Compiler();
  286. }
  287. return $this->compiler->getPassConfig();
  288. }
  289. /**
  290. * Returns the compiler.
  291. *
  292. * @return Compiler The compiler
  293. *
  294. * @api
  295. */
  296. public function getCompiler()
  297. {
  298. if (null === $this->compiler) {
  299. $this->compiler = new Compiler();
  300. }
  301. return $this->compiler;
  302. }
  303. /**
  304. * Returns all Scopes.
  305. *
  306. * @return array An array of scopes
  307. *
  308. * @api
  309. */
  310. public function getScopes()
  311. {
  312. return $this->scopes;
  313. }
  314. /**
  315. * Returns all Scope children.
  316. *
  317. * @return array An array of scope children.
  318. *
  319. * @api
  320. */
  321. public function getScopeChildren()
  322. {
  323. return $this->scopeChildren;
  324. }
  325. /**
  326. * Sets a service.
  327. *
  328. * @param string $id The service identifier
  329. * @param object $service The service instance
  330. * @param string $scope The scope
  331. *
  332. * @throws BadMethodCallException When this ContainerBuilder is frozen
  333. *
  334. * @api
  335. */
  336. public function set($id, $service, $scope = self::SCOPE_CONTAINER)
  337. {
  338. $id = strtolower($id);
  339. if ($this->isFrozen()) {
  340. // setting a synthetic service on a frozen container is alright
  341. if (
  342. (!isset($this->definitions[$id]) && !isset($this->obsoleteDefinitions[$id]))
  343. ||
  344. (isset($this->definitions[$id]) && !$this->definitions[$id]->isSynthetic())
  345. ||
  346. (isset($this->obsoleteDefinitions[$id]) && !$this->obsoleteDefinitions[$id]->isSynthetic())
  347. ) {
  348. throw new BadMethodCallException(sprintf('Setting service "%s" on a frozen container is not allowed.', $id));
  349. }
  350. }
  351. if (isset($this->definitions[$id])) {
  352. $this->obsoleteDefinitions[$id] = $this->definitions[$id];
  353. }
  354. unset($this->definitions[$id], $this->aliasDefinitions[$id]);
  355. parent::set($id, $service, $scope);
  356. if (isset($this->obsoleteDefinitions[$id]) && $this->obsoleteDefinitions[$id]->isSynchronized()) {
  357. $this->synchronize($id);
  358. }
  359. }
  360. /**
  361. * Removes a service definition.
  362. *
  363. * @param string $id The service identifier
  364. *
  365. * @api
  366. */
  367. public function removeDefinition($id)
  368. {
  369. unset($this->definitions[strtolower($id)]);
  370. }
  371. /**
  372. * Returns true if the given service is defined.
  373. *
  374. * @param string $id The service identifier
  375. *
  376. * @return Boolean true if the service is defined, false otherwise
  377. *
  378. * @api
  379. */
  380. public function has($id)
  381. {
  382. $id = strtolower($id);
  383. return isset($this->definitions[$id]) || isset($this->aliasDefinitions[$id]) || parent::has($id);
  384. }
  385. /**
  386. * Gets a service.
  387. *
  388. * @param string $id The service identifier
  389. * @param integer $invalidBehavior The behavior when the service does not exist
  390. *
  391. * @return object The associated service
  392. *
  393. * @throws InvalidArgumentException when no definitions are available
  394. * @throws InactiveScopeException when the current scope is not active
  395. * @throws LogicException when a circular dependency is detected
  396. * @throws \Exception
  397. *
  398. * @see Reference
  399. *
  400. * @api
  401. */
  402. public function get($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE)
  403. {
  404. $id = strtolower($id);
  405. try {
  406. return parent::get($id, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE);
  407. } catch (InactiveScopeException $e) {
  408. if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $invalidBehavior) {
  409. return null;
  410. }
  411. throw $e;
  412. } catch (InvalidArgumentException $e) {
  413. if (isset($this->loading[$id])) {
  414. throw new LogicException(sprintf('The service "%s" has a circular reference to itself.', $id), 0, $e);
  415. }
  416. if (!$this->hasDefinition($id) && isset($this->aliasDefinitions[$id])) {
  417. return $this->get($this->aliasDefinitions[$id]);
  418. }
  419. try {
  420. $definition = $this->getDefinition($id);
  421. } catch (InvalidArgumentException $e) {
  422. if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $invalidBehavior) {
  423. return null;
  424. }
  425. throw $e;
  426. }
  427. $this->loading[$id] = true;
  428. try {
  429. $service = $this->createService($definition, $id);
  430. } catch (\Exception $e) {
  431. unset($this->loading[$id]);
  432. if ($e instanceof InactiveScopeException && self::EXCEPTION_ON_INVALID_REFERENCE !== $invalidBehavior) {
  433. return null;
  434. }
  435. throw $e;
  436. }
  437. unset($this->loading[$id]);
  438. return $service;
  439. }
  440. }
  441. /**
  442. * Merges a ContainerBuilder with the current ContainerBuilder configuration.
  443. *
  444. * Service definitions overrides the current defined ones.
  445. *
  446. * But for parameters, they are overridden by the current ones. It allows
  447. * the parameters passed to the container constructor to have precedence
  448. * over the loaded ones.
  449. *
  450. * $container = new ContainerBuilder(array('foo' => 'bar'));
  451. * $loader = new LoaderXXX($container);
  452. * $loader->load('resource_name');
  453. * $container->register('foo', new stdClass());
  454. *
  455. * In the above example, even if the loaded resource defines a foo
  456. * parameter, the value will still be 'bar' as defined in the ContainerBuilder
  457. * constructor.
  458. *
  459. * @param ContainerBuilder $container The ContainerBuilder instance to merge.
  460. *
  461. *
  462. * @throws BadMethodCallException When this ContainerBuilder is frozen
  463. *
  464. * @api
  465. */
  466. public function merge(ContainerBuilder $container)
  467. {
  468. if ($this->isFrozen()) {
  469. throw new BadMethodCallException('Cannot merge on a frozen container.');
  470. }
  471. $this->addDefinitions($container->getDefinitions());
  472. $this->addAliases($container->getAliases());
  473. $this->getParameterBag()->add($container->getParameterBag()->all());
  474. if ($this->trackResources) {
  475. foreach ($container->getResources() as $resource) {
  476. $this->addResource($resource);
  477. }
  478. }
  479. foreach ($this->extensions as $name => $extension) {
  480. if (!isset($this->extensionConfigs[$name])) {
  481. $this->extensionConfigs[$name] = array();
  482. }
  483. $this->extensionConfigs[$name] = array_merge($this->extensionConfigs[$name], $container->getExtensionConfig($name));
  484. }
  485. }
  486. /**
  487. * Returns the configuration array for the given extension.
  488. *
  489. * @param string $name The name of the extension
  490. *
  491. * @return array An array of configuration
  492. *
  493. * @api
  494. */
  495. public function getExtensionConfig($name)
  496. {
  497. if (!isset($this->extensionConfigs[$name])) {
  498. $this->extensionConfigs[$name] = array();
  499. }
  500. return $this->extensionConfigs[$name];
  501. }
  502. /**
  503. * Prepends a config array to the configs of the given extension.
  504. *
  505. * @param string $name The name of the extension
  506. * @param array $config The config to set
  507. */
  508. public function prependExtensionConfig($name, array $config)
  509. {
  510. if (!isset($this->extensionConfigs[$name])) {
  511. $this->extensionConfigs[$name] = array();
  512. }
  513. array_unshift($this->extensionConfigs[$name], $config);
  514. }
  515. /**
  516. * Compiles the container.
  517. *
  518. * This method passes the container to compiler
  519. * passes whose job is to manipulate and optimize
  520. * the container.
  521. *
  522. * The main compiler passes roughly do four things:
  523. *
  524. * * The extension configurations are merged;
  525. * * Parameter values are resolved;
  526. * * The parameter bag is frozen;
  527. * * Extension loading is disabled.
  528. *
  529. * @api
  530. */
  531. public function compile()
  532. {
  533. if (null === $this->compiler) {
  534. $this->compiler = new Compiler();
  535. }
  536. if ($this->trackResources) {
  537. foreach ($this->compiler->getPassConfig()->getPasses() as $pass) {
  538. $this->addObjectResource($pass);
  539. }
  540. foreach ($this->definitions as $definition) {
  541. if ($definition->isLazy() && ($class = $definition->getClass()) && class_exists($class)) {
  542. $this->addClassResource(new \ReflectionClass($class));
  543. }
  544. }
  545. }
  546. $this->compiler->compile($this);
  547. $this->extensionConfigs = array();
  548. parent::compile();
  549. }
  550. /**
  551. * Gets all service ids.
  552. *
  553. * @return array An array of all defined service ids
  554. */
  555. public function getServiceIds()
  556. {
  557. return array_unique(array_merge(array_keys($this->getDefinitions()), array_keys($this->aliasDefinitions), parent::getServiceIds()));
  558. }
  559. /**
  560. * Adds the service aliases.
  561. *
  562. * @param array $aliases An array of aliases
  563. *
  564. * @api
  565. */
  566. public function addAliases(array $aliases)
  567. {
  568. foreach ($aliases as $alias => $id) {
  569. $this->setAlias($alias, $id);
  570. }
  571. }
  572. /**
  573. * Sets the service aliases.
  574. *
  575. * @param array $aliases An array of aliases
  576. *
  577. * @api
  578. */
  579. public function setAliases(array $aliases)
  580. {
  581. $this->aliasDefinitions = array();
  582. $this->addAliases($aliases);
  583. }
  584. /**
  585. * Sets an alias for an existing service.
  586. *
  587. * @param string $alias The alias to create
  588. * @param string|Alias $id The service to alias
  589. *
  590. * @throws InvalidArgumentException if the id is not a string or an Alias
  591. * @throws InvalidArgumentException if the alias is for itself
  592. *
  593. * @api
  594. */
  595. public function setAlias($alias, $id)
  596. {
  597. $alias = strtolower($alias);
  598. if (is_string($id)) {
  599. $id = new Alias($id);
  600. } elseif (!$id instanceof Alias) {
  601. throw new InvalidArgumentException('$id must be a string, or an Alias object.');
  602. }
  603. if ($alias === strtolower($id)) {
  604. throw new InvalidArgumentException(sprintf('An alias can not reference itself, got a circular reference on "%s".', $alias));
  605. }
  606. unset($this->definitions[$alias]);
  607. $this->aliasDefinitions[$alias] = $id;
  608. }
  609. /**
  610. * Removes an alias.
  611. *
  612. * @param string $alias The alias to remove
  613. *
  614. * @api
  615. */
  616. public function removeAlias($alias)
  617. {
  618. unset($this->aliasDefinitions[strtolower($alias)]);
  619. }
  620. /**
  621. * Returns true if an alias exists under the given identifier.
  622. *
  623. * @param string $id The service identifier
  624. *
  625. * @return Boolean true if the alias exists, false otherwise
  626. *
  627. * @api
  628. */
  629. public function hasAlias($id)
  630. {
  631. return isset($this->aliasDefinitions[strtolower($id)]);
  632. }
  633. /**
  634. * Gets all defined aliases.
  635. *
  636. * @return Alias[] An array of aliases
  637. *
  638. * @api
  639. */
  640. public function getAliases()
  641. {
  642. return $this->aliasDefinitions;
  643. }
  644. /**
  645. * Gets an alias.
  646. *
  647. * @param string $id The service identifier
  648. *
  649. * @return Alias An Alias instance
  650. *
  651. * @throws InvalidArgumentException if the alias does not exist
  652. *
  653. * @api
  654. */
  655. public function getAlias($id)
  656. {
  657. $id = strtolower($id);
  658. if (!$this->hasAlias($id)) {
  659. throw new InvalidArgumentException(sprintf('The service alias "%s" does not exist.', $id));
  660. }
  661. return $this->aliasDefinitions[$id];
  662. }
  663. /**
  664. * Registers a service definition.
  665. *
  666. * This methods allows for simple registration of service definition
  667. * with a fluid interface.
  668. *
  669. * @param string $id The service identifier
  670. * @param string $class The service class
  671. *
  672. * @return Definition A Definition instance
  673. *
  674. * @api
  675. */
  676. public function register($id, $class = null)
  677. {
  678. return $this->setDefinition(strtolower($id), new Definition($class));
  679. }
  680. /**
  681. * Adds the service definitions.
  682. *
  683. * @param Definition[] $definitions An array of service definitions
  684. *
  685. * @api
  686. */
  687. public function addDefinitions(array $definitions)
  688. {
  689. foreach ($definitions as $id => $definition) {
  690. $this->setDefinition($id, $definition);
  691. }
  692. }
  693. /**
  694. * Sets the service definitions.
  695. *
  696. * @param Definition[] $definitions An array of service definitions
  697. *
  698. * @api
  699. */
  700. public function setDefinitions(array $definitions)
  701. {
  702. $this->definitions = array();
  703. $this->addDefinitions($definitions);
  704. }
  705. /**
  706. * Gets all service definitions.
  707. *
  708. * @return Definition[] An array of Definition instances
  709. *
  710. * @api
  711. */
  712. public function getDefinitions()
  713. {
  714. return $this->definitions;
  715. }
  716. /**
  717. * Sets a service definition.
  718. *
  719. * @param string $id The service identifier
  720. * @param Definition $definition A Definition instance
  721. *
  722. * @return Definition the service definition
  723. *
  724. * @throws BadMethodCallException When this ContainerBuilder is frozen
  725. *
  726. * @api
  727. */
  728. public function setDefinition($id, Definition $definition)
  729. {
  730. if ($this->isFrozen()) {
  731. throw new BadMethodCallException('Adding definition to a frozen container is not allowed');
  732. }
  733. $id = strtolower($id);
  734. unset($this->aliasDefinitions[$id]);
  735. return $this->definitions[$id] = $definition;
  736. }
  737. /**
  738. * Returns true if a service definition exists under the given identifier.
  739. *
  740. * @param string $id The service identifier
  741. *
  742. * @return Boolean true if the service definition exists, false otherwise
  743. *
  744. * @api
  745. */
  746. public function hasDefinition($id)
  747. {
  748. return array_key_exists(strtolower($id), $this->definitions);
  749. }
  750. /**
  751. * Gets a service definition.
  752. *
  753. * @param string $id The service identifier
  754. *
  755. * @return Definition A Definition instance
  756. *
  757. * @throws InvalidArgumentException if the service definition does not exist
  758. *
  759. * @api
  760. */
  761. public function getDefinition($id)
  762. {
  763. $id = strtolower($id);
  764. if (!$this->hasDefinition($id)) {
  765. throw new InvalidArgumentException(sprintf('The service definition "%s" does not exist.', $id));
  766. }
  767. return $this->definitions[$id];
  768. }
  769. /**
  770. * Gets a service definition by id or alias.
  771. *
  772. * The method "unaliases" recursively to return a Definition instance.
  773. *
  774. * @param string $id The service identifier or alias
  775. *
  776. * @return Definition A Definition instance
  777. *
  778. * @throws InvalidArgumentException if the service definition does not exist
  779. *
  780. * @api
  781. */
  782. public function findDefinition($id)
  783. {
  784. while ($this->hasAlias($id)) {
  785. $id = (string) $this->getAlias($id);
  786. }
  787. return $this->getDefinition($id);
  788. }
  789. /**
  790. * Creates a service for a service definition.
  791. *
  792. * @param Definition $definition A service definition instance
  793. * @param string $id The service identifier
  794. * @param Boolean $tryProxy Whether to try proxying the service with a lazy proxy
  795. *
  796. * @return object The service described by the service definition
  797. *
  798. * @throws RuntimeException When the scope is inactive
  799. * @throws RuntimeException When the factory definition is incomplete
  800. * @throws RuntimeException When the service is a synthetic service
  801. * @throws InvalidArgumentException When configure callable is not callable
  802. *
  803. * @internal this method is public because of PHP 5.3 limitations, do not use it explicitly in your code
  804. */
  805. public function createService(Definition $definition, $id, $tryProxy = true)
  806. {
  807. if ($definition->isSynthetic()) {
  808. throw new RuntimeException(sprintf('You have requested a synthetic service ("%s"). The DIC does not know how to construct this service.', $id));
  809. }
  810. if ($tryProxy && $definition->isLazy()) {
  811. $container = $this;
  812. $proxy = $this
  813. ->getProxyInstantiator()
  814. ->instantiateProxy(
  815. $container,
  816. $definition,
  817. $id, function () use ($definition, $id, $container) {
  818. return $container->createService($definition, $id, false);
  819. }
  820. );
  821. $this->shareService($definition, $proxy, $id);
  822. return $proxy;
  823. }
  824. $parameterBag = $this->getParameterBag();
  825. if (null !== $definition->getFile()) {
  826. require_once $parameterBag->resolveValue($definition->getFile());
  827. }
  828. $arguments = $this->resolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getArguments())));
  829. if (null !== $definition->getFactoryMethod()) {
  830. if (null !== $definition->getFactoryClass()) {
  831. $factory = $parameterBag->resolveValue($definition->getFactoryClass());
  832. } elseif (null !== $definition->getFactoryService()) {
  833. $factory = $this->get($parameterBag->resolveValue($definition->getFactoryService()));
  834. } else {
  835. throw new RuntimeException(sprintf('Cannot create service "%s" from factory method without a factory service or factory class.', $id));
  836. }
  837. $service = call_user_func_array(array($factory, $definition->getFactoryMethod()), $arguments);
  838. } else {
  839. $r = new \ReflectionClass($parameterBag->resolveValue($definition->getClass()));
  840. $service = null === $r->getConstructor() ? $r->newInstance() : $r->newInstanceArgs($arguments);
  841. }
  842. if ($tryProxy || !$definition->isLazy()) {
  843. // share only if proxying failed, or if not a proxy
  844. $this->shareService($definition, $service, $id);
  845. }
  846. foreach ($definition->getMethodCalls() as $call) {
  847. $this->callMethod($service, $call);
  848. }
  849. $properties = $this->resolveServices($parameterBag->resolveValue($definition->getProperties()));
  850. foreach ($properties as $name => $value) {
  851. $service->$name = $value;
  852. }
  853. if ($callable = $definition->getConfigurator()) {
  854. if (is_array($callable)) {
  855. $callable[0] = $callable[0] instanceof Reference ? $this->get((string) $callable[0]) : $parameterBag->resolveValue($callable[0]);
  856. }
  857. if (!is_callable($callable)) {
  858. throw new InvalidArgumentException(sprintf('The configure callable for class "%s" is not a callable.', get_class($service)));
  859. }
  860. call_user_func($callable, $service);
  861. }
  862. return $service;
  863. }
  864. /**
  865. * Replaces service references by the real service instance.
  866. *
  867. * @param mixed $value A value
  868. *
  869. * @return mixed The same value with all service references replaced by the real service instances
  870. */
  871. public function resolveServices($value)
  872. {
  873. if (is_array($value)) {
  874. foreach ($value as &$v) {
  875. $v = $this->resolveServices($v);
  876. }
  877. } elseif ($value instanceof Reference) {
  878. $value = $this->get((string) $value, $value->getInvalidBehavior());
  879. } elseif ($value instanceof Definition) {
  880. $value = $this->createService($value, null);
  881. }
  882. return $value;
  883. }
  884. /**
  885. * Returns service ids for a given tag.
  886. *
  887. * Example:
  888. *
  889. * $container->register('foo')->addTag('my.tag', array('hello' => 'world'));
  890. *
  891. * $serviceIds = $container->findTaggedServiceIds('my.tag');
  892. * foreach ($serviceIds as $serviceId => $tags) {
  893. * foreach ($tags as $tag) {
  894. * echo $tag['hello'];
  895. * }
  896. * }
  897. *
  898. * @param string $name The tag name
  899. *
  900. * @return array An array of tags with the tagged service as key, holding a list of attribute arrays.
  901. *
  902. * @api
  903. */
  904. public function findTaggedServiceIds($name)
  905. {
  906. $tags = array();
  907. foreach ($this->getDefinitions() as $id => $definition) {
  908. if ($definition->hasTag($name)) {
  909. $tags[$id] = $definition->getTag($name);
  910. }
  911. }
  912. return $tags;
  913. }
  914. /**
  915. * Returns all tags the defined services use.
  916. *
  917. * @return array An array of tags
  918. */
  919. public function findTags()
  920. {
  921. $tags = array();
  922. foreach ($this->getDefinitions() as $id => $definition) {
  923. $tags = array_merge(array_keys($definition->getTags()), $tags);
  924. }
  925. return array_unique($tags);
  926. }
  927. /**
  928. * Returns the Service Conditionals.
  929. *
  930. * @param mixed $value An array of conditionals to return.
  931. *
  932. * @return array An array of Service conditionals
  933. */
  934. public static function getServiceConditionals($value)
  935. {
  936. $services = array();
  937. if (is_array($value)) {
  938. foreach ($value as $v) {
  939. $services = array_unique(array_merge($services, self::getServiceConditionals($v)));
  940. }
  941. } elseif ($value instanceof Reference && $value->getInvalidBehavior() === ContainerInterface::IGNORE_ON_INVALID_REFERENCE) {
  942. $services[] = (string) $value;
  943. }
  944. return $services;
  945. }
  946. /**
  947. * Retrieves the currently set proxy instantiator or instantiates one.
  948. *
  949. * @return InstantiatorInterface
  950. */
  951. private function getProxyInstantiator()
  952. {
  953. if (!$this->proxyInstantiator) {
  954. $this->proxyInstantiator = new RealServiceInstantiator();
  955. }
  956. return $this->proxyInstantiator;
  957. }
  958. /**
  959. * Synchronizes a service change.
  960. *
  961. * This method updates all services that depend on the given
  962. * service by calling all methods referencing it.
  963. *
  964. * @param string $id A service id
  965. */
  966. private function synchronize($id)
  967. {
  968. foreach ($this->definitions as $definitionId => $definition) {
  969. // only check initialized services
  970. if (!$this->initialized($definitionId)) {
  971. continue;
  972. }
  973. foreach ($definition->getMethodCalls() as $call) {
  974. foreach ($call[1] as $argument) {
  975. if ($argument instanceof Reference && $id == (string) $argument) {
  976. $this->callMethod($this->get($definitionId), $call);
  977. }
  978. }
  979. }
  980. }
  981. }
  982. private function callMethod($service, $call)
  983. {
  984. $services = self::getServiceConditionals($call[1]);
  985. foreach ($services as $s) {
  986. if (!$this->has($s)) {
  987. return;
  988. }
  989. }
  990. call_user_func_array(array($service, $call[0]), $this->resolveServices($this->getParameterBag()->resolveValue($call[1])));
  991. }
  992. /**
  993. * Shares a given service in the container
  994. *
  995. * @param Definition $definition
  996. * @param mixed $service
  997. * @param string $id
  998. *
  999. * @throws InactiveScopeException
  1000. */
  1001. private function shareService(Definition $definition, $service, $id)
  1002. {
  1003. if (self::SCOPE_PROTOTYPE !== $scope = $definition->getScope()) {
  1004. if (self::SCOPE_CONTAINER !== $scope && !isset($this->scopedServices[$scope])) {
  1005. throw new InactiveScopeException($id, $scope);
  1006. }
  1007. $this->services[$lowerId = strtolower($id)] = $service;
  1008. if (self::SCOPE_CONTAINER !== $scope) {
  1009. $this->scopedServices[$scope][$lowerId] = $service;
  1010. }
  1011. }
  1012. }
  1013. }