ContainerBuilder.php 36 KB

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