Drupal investigation

ContainerBuilder.php 35KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181
  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 $this
  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 $this
  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 $this
  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 $this
  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 $this
  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 $this
  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. $set = isset($this->definitions[$id]);
  332. if ($this->isFrozen() && ($set || isset($this->obsoleteDefinitions[$id])) && !$this->{$set ? 'definitions' : 'obsoleteDefinitions'}[$id]->isSynthetic()) {
  333. // setting a synthetic service on a frozen container is alright
  334. throw new BadMethodCallException(sprintf('Setting service "%s" on a frozen container is not allowed.', $id));
  335. }
  336. if ($set) {
  337. $this->obsoleteDefinitions[$id] = $this->definitions[$id];
  338. }
  339. unset($this->definitions[$id], $this->aliasDefinitions[$id]);
  340. parent::set($id, $service, $scope);
  341. if (isset($this->obsoleteDefinitions[$id]) && $this->obsoleteDefinitions[$id]->isSynchronized(false)) {
  342. $this->synchronize($id);
  343. }
  344. }
  345. /**
  346. * Removes a service definition.
  347. *
  348. * @param string $id The service identifier
  349. */
  350. public function removeDefinition($id)
  351. {
  352. unset($this->definitions[strtolower($id)]);
  353. }
  354. /**
  355. * Returns true if the given service is defined.
  356. *
  357. * @param string $id The service identifier
  358. *
  359. * @return bool true if the service is defined, false otherwise
  360. */
  361. public function has($id)
  362. {
  363. $id = strtolower($id);
  364. return isset($this->definitions[$id]) || isset($this->aliasDefinitions[$id]) || parent::has($id);
  365. }
  366. /**
  367. * Gets a service.
  368. *
  369. * @param string $id The service identifier
  370. * @param int $invalidBehavior The behavior when the service does not exist
  371. *
  372. * @return object The associated service
  373. *
  374. * @throws InvalidArgumentException when no definitions are available
  375. * @throws ServiceCircularReferenceException When a circular reference is detected
  376. * @throws ServiceNotFoundException When the service is not defined
  377. * @throws \Exception
  378. *
  379. * @see Reference
  380. */
  381. public function get($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE)
  382. {
  383. $id = strtolower($id);
  384. if ($service = parent::get($id, ContainerInterface::NULL_ON_INVALID_REFERENCE)) {
  385. return $service;
  386. }
  387. if (!array_key_exists($id, $this->definitions) && isset($this->aliasDefinitions[$id])) {
  388. return $this->get((string) $this->aliasDefinitions[$id], $invalidBehavior);
  389. }
  390. try {
  391. $definition = $this->getDefinition($id);
  392. } catch (ServiceNotFoundException $e) {
  393. if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $invalidBehavior) {
  394. return;
  395. }
  396. throw $e;
  397. }
  398. $this->loading[$id] = true;
  399. try {
  400. $service = $this->createService($definition, $id);
  401. } catch (\Exception $e) {
  402. unset($this->loading[$id]);
  403. if ($e instanceof InactiveScopeException && self::EXCEPTION_ON_INVALID_REFERENCE !== $invalidBehavior) {
  404. return;
  405. }
  406. throw $e;
  407. } catch (\Throwable $e) {
  408. unset($this->loading[$id]);
  409. throw $e;
  410. }
  411. unset($this->loading[$id]);
  412. return $service;
  413. }
  414. /**
  415. * Merges a ContainerBuilder with the current ContainerBuilder configuration.
  416. *
  417. * Service definitions overrides the current defined ones.
  418. *
  419. * But for parameters, they are overridden by the current ones. It allows
  420. * the parameters passed to the container constructor to have precedence
  421. * over the loaded ones.
  422. *
  423. * $container = new ContainerBuilder(array('foo' => 'bar'));
  424. * $loader = new LoaderXXX($container);
  425. * $loader->load('resource_name');
  426. * $container->register('foo', new stdClass());
  427. *
  428. * In the above example, even if the loaded resource defines a foo
  429. * parameter, the value will still be 'bar' as defined in the ContainerBuilder
  430. * constructor.
  431. *
  432. * @param ContainerBuilder $container The ContainerBuilder instance to merge
  433. *
  434. * @throws BadMethodCallException When this ContainerBuilder is frozen
  435. */
  436. public function merge(ContainerBuilder $container)
  437. {
  438. if ($this->isFrozen()) {
  439. throw new BadMethodCallException('Cannot merge on a frozen container.');
  440. }
  441. $this->addDefinitions($container->getDefinitions());
  442. $this->addAliases($container->getAliases());
  443. $this->getParameterBag()->add($container->getParameterBag()->all());
  444. if ($this->trackResources) {
  445. foreach ($container->getResources() as $resource) {
  446. $this->addResource($resource);
  447. }
  448. }
  449. foreach ($this->extensions as $name => $extension) {
  450. if (!isset($this->extensionConfigs[$name])) {
  451. $this->extensionConfigs[$name] = array();
  452. }
  453. $this->extensionConfigs[$name] = array_merge($this->extensionConfigs[$name], $container->getExtensionConfig($name));
  454. }
  455. }
  456. /**
  457. * Returns the configuration array for the given extension.
  458. *
  459. * @param string $name The name of the extension
  460. *
  461. * @return array An array of configuration
  462. */
  463. public function getExtensionConfig($name)
  464. {
  465. if (!isset($this->extensionConfigs[$name])) {
  466. $this->extensionConfigs[$name] = array();
  467. }
  468. return $this->extensionConfigs[$name];
  469. }
  470. /**
  471. * Prepends a config array to the configs of the given extension.
  472. *
  473. * @param string $name The name of the extension
  474. * @param array $config The config to set
  475. */
  476. public function prependExtensionConfig($name, array $config)
  477. {
  478. if (!isset($this->extensionConfigs[$name])) {
  479. $this->extensionConfigs[$name] = array();
  480. }
  481. array_unshift($this->extensionConfigs[$name], $config);
  482. }
  483. /**
  484. * Compiles the container.
  485. *
  486. * This method passes the container to compiler
  487. * passes whose job is to manipulate and optimize
  488. * the container.
  489. *
  490. * The main compiler passes roughly do four things:
  491. *
  492. * * The extension configurations are merged;
  493. * * Parameter values are resolved;
  494. * * The parameter bag is frozen;
  495. * * Extension loading is disabled.
  496. */
  497. public function compile()
  498. {
  499. $compiler = $this->getCompiler();
  500. if ($this->trackResources) {
  501. foreach ($compiler->getPassConfig()->getPasses() as $pass) {
  502. $this->addObjectResource($pass);
  503. }
  504. }
  505. $compiler->compile($this);
  506. if ($this->trackResources) {
  507. foreach ($this->definitions as $definition) {
  508. if ($definition->isLazy() && ($class = $definition->getClass()) && class_exists($class)) {
  509. $this->addClassResource(new \ReflectionClass($class));
  510. }
  511. }
  512. }
  513. $this->extensionConfigs = array();
  514. parent::compile();
  515. }
  516. /**
  517. * Gets all service ids.
  518. *
  519. * @return array An array of all defined service ids
  520. */
  521. public function getServiceIds()
  522. {
  523. return array_unique(array_merge(array_keys($this->getDefinitions()), array_keys($this->aliasDefinitions), parent::getServiceIds()));
  524. }
  525. /**
  526. * Adds the service aliases.
  527. *
  528. * @param array $aliases An array of aliases
  529. */
  530. public function addAliases(array $aliases)
  531. {
  532. foreach ($aliases as $alias => $id) {
  533. $this->setAlias($alias, $id);
  534. }
  535. }
  536. /**
  537. * Sets the service aliases.
  538. *
  539. * @param array $aliases An array of aliases
  540. */
  541. public function setAliases(array $aliases)
  542. {
  543. $this->aliasDefinitions = array();
  544. $this->addAliases($aliases);
  545. }
  546. /**
  547. * Sets an alias for an existing service.
  548. *
  549. * @param string $alias The alias to create
  550. * @param string|Alias $id The service to alias
  551. *
  552. * @throws InvalidArgumentException if the id is not a string or an Alias
  553. * @throws InvalidArgumentException if the alias is for itself
  554. */
  555. public function setAlias($alias, $id)
  556. {
  557. $alias = strtolower($alias);
  558. if (is_string($id)) {
  559. $id = new Alias($id);
  560. } elseif (!$id instanceof Alias) {
  561. throw new InvalidArgumentException('$id must be a string, or an Alias object.');
  562. }
  563. if ($alias === (string) $id) {
  564. throw new InvalidArgumentException(sprintf('An alias can not reference itself, got a circular reference on "%s".', $alias));
  565. }
  566. unset($this->definitions[$alias]);
  567. $this->aliasDefinitions[$alias] = $id;
  568. }
  569. /**
  570. * Removes an alias.
  571. *
  572. * @param string $alias The alias to remove
  573. */
  574. public function removeAlias($alias)
  575. {
  576. unset($this->aliasDefinitions[strtolower($alias)]);
  577. }
  578. /**
  579. * Returns true if an alias exists under the given identifier.
  580. *
  581. * @param string $id The service identifier
  582. *
  583. * @return bool true if the alias exists, false otherwise
  584. */
  585. public function hasAlias($id)
  586. {
  587. return isset($this->aliasDefinitions[strtolower($id)]);
  588. }
  589. /**
  590. * Gets all defined aliases.
  591. *
  592. * @return Alias[] An array of aliases
  593. */
  594. public function getAliases()
  595. {
  596. return $this->aliasDefinitions;
  597. }
  598. /**
  599. * Gets an alias.
  600. *
  601. * @param string $id The service identifier
  602. *
  603. * @return Alias An Alias instance
  604. *
  605. * @throws InvalidArgumentException if the alias does not exist
  606. */
  607. public function getAlias($id)
  608. {
  609. $id = strtolower($id);
  610. if (!isset($this->aliasDefinitions[$id])) {
  611. throw new InvalidArgumentException(sprintf('The service alias "%s" does not exist.', $id));
  612. }
  613. return $this->aliasDefinitions[$id];
  614. }
  615. /**
  616. * Registers a service definition.
  617. *
  618. * This methods allows for simple registration of service definition
  619. * with a fluid interface.
  620. *
  621. * @param string $id The service identifier
  622. * @param string $class The service class
  623. *
  624. * @return Definition A Definition instance
  625. */
  626. public function register($id, $class = null)
  627. {
  628. return $this->setDefinition($id, new Definition($class));
  629. }
  630. /**
  631. * Adds the service definitions.
  632. *
  633. * @param Definition[] $definitions An array of service definitions
  634. */
  635. public function addDefinitions(array $definitions)
  636. {
  637. foreach ($definitions as $id => $definition) {
  638. $this->setDefinition($id, $definition);
  639. }
  640. }
  641. /**
  642. * Sets the service definitions.
  643. *
  644. * @param Definition[] $definitions An array of service definitions
  645. */
  646. public function setDefinitions(array $definitions)
  647. {
  648. $this->definitions = array();
  649. $this->addDefinitions($definitions);
  650. }
  651. /**
  652. * Gets all service definitions.
  653. *
  654. * @return Definition[] An array of Definition instances
  655. */
  656. public function getDefinitions()
  657. {
  658. return $this->definitions;
  659. }
  660. /**
  661. * Sets a service definition.
  662. *
  663. * @param string $id The service identifier
  664. * @param Definition $definition A Definition instance
  665. *
  666. * @return Definition the service definition
  667. *
  668. * @throws BadMethodCallException When this ContainerBuilder is frozen
  669. */
  670. public function setDefinition($id, Definition $definition)
  671. {
  672. if ($this->isFrozen()) {
  673. throw new BadMethodCallException('Adding definition to a frozen container is not allowed');
  674. }
  675. $id = strtolower($id);
  676. unset($this->aliasDefinitions[$id]);
  677. return $this->definitions[$id] = $definition;
  678. }
  679. /**
  680. * Returns true if a service definition exists under the given identifier.
  681. *
  682. * @param string $id The service identifier
  683. *
  684. * @return bool true if the service definition exists, false otherwise
  685. */
  686. public function hasDefinition($id)
  687. {
  688. return array_key_exists(strtolower($id), $this->definitions);
  689. }
  690. /**
  691. * Gets a service definition.
  692. *
  693. * @param string $id The service identifier
  694. *
  695. * @return Definition A Definition instance
  696. *
  697. * @throws ServiceNotFoundException if the service definition does not exist
  698. */
  699. public function getDefinition($id)
  700. {
  701. $id = strtolower($id);
  702. if (!array_key_exists($id, $this->definitions)) {
  703. throw new ServiceNotFoundException($id);
  704. }
  705. return $this->definitions[$id];
  706. }
  707. /**
  708. * Gets a service definition by id or alias.
  709. *
  710. * The method "unaliases" recursively to return a Definition instance.
  711. *
  712. * @param string $id The service identifier or alias
  713. *
  714. * @return Definition A Definition instance
  715. *
  716. * @throws ServiceNotFoundException if the service definition does not exist
  717. */
  718. public function findDefinition($id)
  719. {
  720. $id = strtolower($id);
  721. while (isset($this->aliasDefinitions[$id])) {
  722. $id = (string) $this->aliasDefinitions[$id];
  723. }
  724. return $this->getDefinition($id);
  725. }
  726. /**
  727. * Creates a service for a service definition.
  728. *
  729. * @param Definition $definition A service definition instance
  730. * @param string $id The service identifier
  731. * @param bool $tryProxy Whether to try proxying the service with a lazy proxy
  732. *
  733. * @return object The service described by the service definition
  734. *
  735. * @throws RuntimeException When the scope is inactive
  736. * @throws RuntimeException When the factory definition is incomplete
  737. * @throws RuntimeException When the service is a synthetic service
  738. * @throws InvalidArgumentException When configure callable is not callable
  739. *
  740. * @internal this method is public because of PHP 5.3 limitations, do not use it explicitly in your code
  741. */
  742. public function createService(Definition $definition, $id, $tryProxy = true)
  743. {
  744. if ($definition instanceof DefinitionDecorator) {
  745. throw new RuntimeException(sprintf('Constructing service "%s" from a parent definition is not supported at build time.', $id));
  746. }
  747. if ($definition->isSynthetic()) {
  748. throw new RuntimeException(sprintf('You have requested a synthetic service ("%s"). The DIC does not know how to construct this service.', $id));
  749. }
  750. if ($definition->isDeprecated()) {
  751. @trigger_error($definition->getDeprecationMessage($id), E_USER_DEPRECATED);
  752. }
  753. if ($tryProxy && $definition->isLazy()) {
  754. $container = $this;
  755. $proxy = $this
  756. ->getProxyInstantiator()
  757. ->instantiateProxy(
  758. $container,
  759. $definition,
  760. $id, function () use ($definition, $id, $container) {
  761. return $container->createService($definition, $id, false);
  762. }
  763. );
  764. $this->shareService($definition, $proxy, $id);
  765. return $proxy;
  766. }
  767. $parameterBag = $this->getParameterBag();
  768. if (null !== $definition->getFile()) {
  769. require_once $parameterBag->resolveValue($definition->getFile());
  770. }
  771. $arguments = $this->resolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getArguments())));
  772. if (null !== $factory = $definition->getFactory()) {
  773. if (is_array($factory)) {
  774. $factory = array($this->resolveServices($parameterBag->resolveValue($factory[0])), $factory[1]);
  775. } elseif (!is_string($factory)) {
  776. throw new RuntimeException(sprintf('Cannot create service "%s" because of invalid factory', $id));
  777. }
  778. $service = call_user_func_array($factory, $arguments);
  779. if (!$definition->isDeprecated() && is_array($factory) && is_string($factory[0])) {
  780. $r = new \ReflectionClass($factory[0]);
  781. if (0 < strpos($r->getDocComment(), "\n * @deprecated ")) {
  782. @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);
  783. }
  784. }
  785. } elseif (null !== $definition->getFactoryMethod(false)) {
  786. if (null !== $definition->getFactoryClass(false)) {
  787. $factory = $parameterBag->resolveValue($definition->getFactoryClass(false));
  788. } elseif (null !== $definition->getFactoryService(false)) {
  789. $factory = $this->get($parameterBag->resolveValue($definition->getFactoryService(false)));
  790. } else {
  791. throw new RuntimeException(sprintf('Cannot create service "%s" from factory method without a factory service or factory class.', $id));
  792. }
  793. $service = call_user_func_array(array($factory, $definition->getFactoryMethod(false)), $arguments);
  794. } else {
  795. $r = new \ReflectionClass($parameterBag->resolveValue($definition->getClass()));
  796. $service = null === $r->getConstructor() ? $r->newInstance() : $r->newInstanceArgs($arguments);
  797. if (!$definition->isDeprecated() && 0 < strpos($r->getDocComment(), "\n * @deprecated ")) {
  798. @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);
  799. }
  800. }
  801. if ($tryProxy || !$definition->isLazy()) {
  802. // share only if proxying failed, or if not a proxy
  803. $this->shareService($definition, $service, $id);
  804. }
  805. $properties = $this->resolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getProperties())));
  806. foreach ($properties as $name => $value) {
  807. $service->$name = $value;
  808. }
  809. foreach ($definition->getMethodCalls() as $call) {
  810. $this->callMethod($service, $call);
  811. }
  812. if ($callable = $definition->getConfigurator()) {
  813. if (is_array($callable)) {
  814. $callable[0] = $parameterBag->resolveValue($callable[0]);
  815. if ($callable[0] instanceof Reference) {
  816. $callable[0] = $this->get((string) $callable[0], $callable[0]->getInvalidBehavior());
  817. } elseif ($callable[0] instanceof Definition) {
  818. $callable[0] = $this->createService($callable[0], null);
  819. }
  820. }
  821. if (!is_callable($callable)) {
  822. throw new InvalidArgumentException(sprintf('The configure callable for class "%s" is not a callable.', get_class($service)));
  823. }
  824. call_user_func($callable, $service);
  825. }
  826. return $service;
  827. }
  828. /**
  829. * Replaces service references by the real service instance and evaluates expressions.
  830. *
  831. * @param mixed $value A value
  832. *
  833. * @return mixed The same value with all service references replaced by
  834. * the real service instances and all expressions evaluated
  835. */
  836. public function resolveServices($value)
  837. {
  838. if (is_array($value)) {
  839. foreach ($value as $k => $v) {
  840. $value[$k] = $this->resolveServices($v);
  841. }
  842. } elseif ($value instanceof Reference) {
  843. $value = $this->get((string) $value, $value->getInvalidBehavior());
  844. } elseif ($value instanceof Definition) {
  845. $value = $this->createService($value, null);
  846. } elseif ($value instanceof Expression) {
  847. $value = $this->getExpressionLanguage()->evaluate($value, array('container' => $this));
  848. }
  849. return $value;
  850. }
  851. /**
  852. * Returns service ids for a given tag.
  853. *
  854. * Example:
  855. *
  856. * $container->register('foo')->addTag('my.tag', array('hello' => 'world'));
  857. *
  858. * $serviceIds = $container->findTaggedServiceIds('my.tag');
  859. * foreach ($serviceIds as $serviceId => $tags) {
  860. * foreach ($tags as $tag) {
  861. * echo $tag['hello'];
  862. * }
  863. * }
  864. *
  865. * @param string $name The tag name
  866. *
  867. * @return array An array of tags with the tagged service as key, holding a list of attribute arrays
  868. */
  869. public function findTaggedServiceIds($name)
  870. {
  871. $this->usedTags[] = $name;
  872. $tags = array();
  873. foreach ($this->getDefinitions() as $id => $definition) {
  874. if ($definition->hasTag($name)) {
  875. $tags[$id] = $definition->getTag($name);
  876. }
  877. }
  878. return $tags;
  879. }
  880. /**
  881. * Returns all tags the defined services use.
  882. *
  883. * @return array An array of tags
  884. */
  885. public function findTags()
  886. {
  887. $tags = array();
  888. foreach ($this->getDefinitions() as $id => $definition) {
  889. $tags = array_merge(array_keys($definition->getTags()), $tags);
  890. }
  891. return array_unique($tags);
  892. }
  893. /**
  894. * Returns all tags not queried by findTaggedServiceIds.
  895. *
  896. * @return string[] An array of tags
  897. */
  898. public function findUnusedTags()
  899. {
  900. return array_values(array_diff($this->findTags(), $this->usedTags));
  901. }
  902. public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider)
  903. {
  904. $this->expressionLanguageProviders[] = $provider;
  905. }
  906. /**
  907. * @return ExpressionFunctionProviderInterface[]
  908. */
  909. public function getExpressionLanguageProviders()
  910. {
  911. return $this->expressionLanguageProviders;
  912. }
  913. /**
  914. * Returns the Service Conditionals.
  915. *
  916. * @param mixed $value An array of conditionals to return
  917. *
  918. * @return array An array of Service conditionals
  919. */
  920. public static function getServiceConditionals($value)
  921. {
  922. $services = array();
  923. if (is_array($value)) {
  924. foreach ($value as $v) {
  925. $services = array_unique(array_merge($services, self::getServiceConditionals($v)));
  926. }
  927. } elseif ($value instanceof Reference && $value->getInvalidBehavior() === ContainerInterface::IGNORE_ON_INVALID_REFERENCE) {
  928. $services[] = (string) $value;
  929. }
  930. return $services;
  931. }
  932. /**
  933. * Retrieves the currently set proxy instantiator or instantiates one.
  934. *
  935. * @return InstantiatorInterface
  936. */
  937. private function getProxyInstantiator()
  938. {
  939. if (!$this->proxyInstantiator) {
  940. $this->proxyInstantiator = new RealServiceInstantiator();
  941. }
  942. return $this->proxyInstantiator;
  943. }
  944. /**
  945. * Synchronizes a service change.
  946. *
  947. * This method updates all services that depend on the given
  948. * service by calling all methods referencing it.
  949. *
  950. * @param string $id A service id
  951. *
  952. * @deprecated since version 2.7, will be removed in 3.0.
  953. */
  954. private function synchronize($id)
  955. {
  956. if ('request' !== $id) {
  957. @trigger_error('The '.__METHOD__.' method is deprecated in version 2.7 and will be removed in version 3.0.', E_USER_DEPRECATED);
  958. }
  959. foreach ($this->definitions as $definitionId => $definition) {
  960. // only check initialized services
  961. if (!$this->initialized($definitionId)) {
  962. continue;
  963. }
  964. foreach ($definition->getMethodCalls() as $call) {
  965. foreach ($call[1] as $argument) {
  966. if ($argument instanceof Reference && $id == (string) $argument) {
  967. $this->callMethod($this->get($definitionId), $call);
  968. }
  969. }
  970. }
  971. }
  972. }
  973. private function callMethod($service, $call)
  974. {
  975. $services = self::getServiceConditionals($call[1]);
  976. foreach ($services as $s) {
  977. if (!$this->has($s)) {
  978. return;
  979. }
  980. }
  981. call_user_func_array(array($service, $call[0]), $this->resolveServices($this->getParameterBag()->unescapeValue($this->getParameterBag()->resolveValue($call[1]))));
  982. }
  983. /**
  984. * Shares a given service in the container.
  985. *
  986. * @param Definition $definition
  987. * @param mixed $service
  988. * @param string|null $id
  989. *
  990. * @throws InactiveScopeException
  991. */
  992. private function shareService(Definition $definition, $service, $id)
  993. {
  994. if (null !== $id && $definition->isShared() && self::SCOPE_PROTOTYPE !== $scope = $definition->getScope(false)) {
  995. if (self::SCOPE_CONTAINER !== $scope && !isset($this->scopedServices[$scope])) {
  996. throw new InactiveScopeException($id, $scope);
  997. }
  998. $this->services[$lowerId = strtolower($id)] = $service;
  999. if (self::SCOPE_CONTAINER !== $scope) {
  1000. $this->scopedServices[$scope][$lowerId] = $service;
  1001. }
  1002. }
  1003. }
  1004. private function getExpressionLanguage()
  1005. {
  1006. if (null === $this->expressionLanguage) {
  1007. if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
  1008. throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
  1009. }
  1010. $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders);
  1011. }
  1012. return $this->expressionLanguage;
  1013. }
  1014. }