ContainerBuilder.php 35 KB

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