ContainerBuilder.php 55 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672
  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 Psr\Container\ContainerInterface as PsrContainerInterface;
  12. use Symfony\Component\Config\Resource\ClassExistenceResource;
  13. use Symfony\Component\Config\Resource\ComposerResource;
  14. use Symfony\Component\Config\Resource\DirectoryResource;
  15. use Symfony\Component\Config\Resource\FileExistenceResource;
  16. use Symfony\Component\Config\Resource\FileResource;
  17. use Symfony\Component\Config\Resource\GlobResource;
  18. use Symfony\Component\Config\Resource\ReflectionClassResource;
  19. use Symfony\Component\Config\Resource\ResourceInterface;
  20. use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
  21. use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
  22. use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
  23. use Symfony\Component\DependencyInjection\Argument\ServiceLocator;
  24. use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
  25. use Symfony\Component\DependencyInjection\Compiler\Compiler;
  26. use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
  27. use Symfony\Component\DependencyInjection\Compiler\PassConfig;
  28. use Symfony\Component\DependencyInjection\Compiler\ResolveEnvPlaceholdersPass;
  29. use Symfony\Component\DependencyInjection\Exception\BadMethodCallException;
  30. use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
  31. use Symfony\Component\DependencyInjection\Exception\LogicException;
  32. use Symfony\Component\DependencyInjection\Exception\RuntimeException;
  33. use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
  34. use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
  35. use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
  36. use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface;
  37. use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\RealServiceInstantiator;
  38. use Symfony\Component\DependencyInjection\ParameterBag\EnvPlaceholderParameterBag;
  39. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
  40. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  41. use Symfony\Component\ExpressionLanguage\Expression;
  42. use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
  43. /**
  44. * ContainerBuilder is a DI container that provides an API to easily describe services.
  45. *
  46. * @author Fabien Potencier <fabien@symfony.com>
  47. */
  48. class ContainerBuilder extends Container implements TaggedContainerInterface
  49. {
  50. /**
  51. * @var ExtensionInterface[]
  52. */
  53. private $extensions = [];
  54. /**
  55. * @var ExtensionInterface[]
  56. */
  57. private $extensionsByNs = [];
  58. /**
  59. * @var Definition[]
  60. */
  61. private $definitions = [];
  62. /**
  63. * @var Alias[]
  64. */
  65. private $aliasDefinitions = [];
  66. /**
  67. * @var ResourceInterface[]
  68. */
  69. private $resources = [];
  70. private $extensionConfigs = [];
  71. /**
  72. * @var Compiler
  73. */
  74. private $compiler;
  75. private $trackResources;
  76. /**
  77. * @var InstantiatorInterface|null
  78. */
  79. private $proxyInstantiator;
  80. /**
  81. * @var ExpressionLanguage|null
  82. */
  83. private $expressionLanguage;
  84. /**
  85. * @var ExpressionFunctionProviderInterface[]
  86. */
  87. private $expressionLanguageProviders = [];
  88. /**
  89. * @var string[] with tag names used by findTaggedServiceIds
  90. */
  91. private $usedTags = [];
  92. /**
  93. * @var string[][] a map of env var names to their placeholders
  94. */
  95. private $envPlaceholders = [];
  96. /**
  97. * @var int[] a map of env vars to their resolution counter
  98. */
  99. private $envCounters = [];
  100. /**
  101. * @var string[] the list of vendor directories
  102. */
  103. private $vendors;
  104. private $autoconfiguredInstanceof = [];
  105. private $removedIds = [];
  106. private $removedBindingIds = [];
  107. private const INTERNAL_TYPES = [
  108. 'int' => true,
  109. 'float' => true,
  110. 'string' => true,
  111. 'bool' => true,
  112. 'resource' => true,
  113. 'object' => true,
  114. 'array' => true,
  115. 'null' => true,
  116. 'callable' => true,
  117. 'iterable' => true,
  118. 'mixed' => true,
  119. ];
  120. public function __construct(ParameterBagInterface $parameterBag = null)
  121. {
  122. parent::__construct($parameterBag);
  123. $this->trackResources = interface_exists(ResourceInterface::class);
  124. $this->setDefinition('service_container', (new Definition(ContainerInterface::class))->setSynthetic(true)->setPublic(true));
  125. $this->setAlias(PsrContainerInterface::class, new Alias('service_container', false));
  126. $this->setAlias(ContainerInterface::class, new Alias('service_container', false));
  127. }
  128. /**
  129. * @var \ReflectionClass[] a list of class reflectors
  130. */
  131. private $classReflectors;
  132. /**
  133. * Sets the track resources flag.
  134. *
  135. * If you are not using the loaders and therefore don't want
  136. * to depend on the Config component, set this flag to false.
  137. *
  138. * @param bool $track True if you want to track resources, false otherwise
  139. */
  140. public function setResourceTracking($track)
  141. {
  142. $this->trackResources = (bool) $track;
  143. }
  144. /**
  145. * Checks if resources are tracked.
  146. *
  147. * @return bool true If resources are tracked, false otherwise
  148. */
  149. public function isTrackingResources()
  150. {
  151. return $this->trackResources;
  152. }
  153. /**
  154. * Sets the instantiator to be used when fetching proxies.
  155. */
  156. public function setProxyInstantiator(InstantiatorInterface $proxyInstantiator)
  157. {
  158. $this->proxyInstantiator = $proxyInstantiator;
  159. }
  160. public function registerExtension(ExtensionInterface $extension)
  161. {
  162. $this->extensions[$extension->getAlias()] = $extension;
  163. if (false !== $extension->getNamespace()) {
  164. $this->extensionsByNs[$extension->getNamespace()] = $extension;
  165. }
  166. }
  167. /**
  168. * Returns an extension by alias or namespace.
  169. *
  170. * @param string $name An alias or a namespace
  171. *
  172. * @return ExtensionInterface An extension instance
  173. *
  174. * @throws LogicException if the extension is not registered
  175. */
  176. public function getExtension($name)
  177. {
  178. if (isset($this->extensions[$name])) {
  179. return $this->extensions[$name];
  180. }
  181. if (isset($this->extensionsByNs[$name])) {
  182. return $this->extensionsByNs[$name];
  183. }
  184. throw new LogicException(sprintf('Container extension "%s" is not registered.', $name));
  185. }
  186. /**
  187. * Returns all registered extensions.
  188. *
  189. * @return ExtensionInterface[] An array of ExtensionInterface
  190. */
  191. public function getExtensions()
  192. {
  193. return $this->extensions;
  194. }
  195. /**
  196. * Checks if we have an extension.
  197. *
  198. * @param string $name The name of the extension
  199. *
  200. * @return bool If the extension exists
  201. */
  202. public function hasExtension($name)
  203. {
  204. return isset($this->extensions[$name]) || isset($this->extensionsByNs[$name]);
  205. }
  206. /**
  207. * Returns an array of resources loaded to build this configuration.
  208. *
  209. * @return ResourceInterface[] An array of resources
  210. */
  211. public function getResources()
  212. {
  213. return array_values($this->resources);
  214. }
  215. /**
  216. * @return $this
  217. */
  218. public function addResource(ResourceInterface $resource)
  219. {
  220. if (!$this->trackResources) {
  221. return $this;
  222. }
  223. if ($resource instanceof GlobResource && $this->inVendors($resource->getPrefix())) {
  224. return $this;
  225. }
  226. $this->resources[(string) $resource] = $resource;
  227. return $this;
  228. }
  229. /**
  230. * Sets the resources for this configuration.
  231. *
  232. * @param ResourceInterface[] $resources An array of resources
  233. *
  234. * @return $this
  235. */
  236. public function setResources(array $resources)
  237. {
  238. if (!$this->trackResources) {
  239. return $this;
  240. }
  241. $this->resources = $resources;
  242. return $this;
  243. }
  244. /**
  245. * Adds the object class hierarchy as resources.
  246. *
  247. * @param object|string $object An object instance or class name
  248. *
  249. * @return $this
  250. */
  251. public function addObjectResource($object)
  252. {
  253. if ($this->trackResources) {
  254. if (\is_object($object)) {
  255. $object = \get_class($object);
  256. }
  257. if (!isset($this->classReflectors[$object])) {
  258. $this->classReflectors[$object] = new \ReflectionClass($object);
  259. }
  260. $class = $this->classReflectors[$object];
  261. foreach ($class->getInterfaceNames() as $name) {
  262. if (null === $interface = &$this->classReflectors[$name]) {
  263. $interface = new \ReflectionClass($name);
  264. }
  265. $file = $interface->getFileName();
  266. if (false !== $file && file_exists($file)) {
  267. $this->fileExists($file);
  268. }
  269. }
  270. do {
  271. $file = $class->getFileName();
  272. if (false !== $file && file_exists($file)) {
  273. $this->fileExists($file);
  274. }
  275. foreach ($class->getTraitNames() as $name) {
  276. $this->addObjectResource($name);
  277. }
  278. } while ($class = $class->getParentClass());
  279. }
  280. return $this;
  281. }
  282. /**
  283. * Retrieves the requested reflection class and registers it for resource tracking.
  284. *
  285. * @throws \ReflectionException when a parent class/interface/trait is not found and $throw is true
  286. *
  287. * @final
  288. */
  289. public function getReflectionClass(?string $class, bool $throw = true): ?\ReflectionClass
  290. {
  291. if (!$class = $this->getParameterBag()->resolveValue($class)) {
  292. return null;
  293. }
  294. if (isset(self::INTERNAL_TYPES[$class])) {
  295. return null;
  296. }
  297. $resource = $classReflector = null;
  298. try {
  299. if (isset($this->classReflectors[$class])) {
  300. $classReflector = $this->classReflectors[$class];
  301. } elseif (class_exists(ClassExistenceResource::class)) {
  302. $resource = new ClassExistenceResource($class, false);
  303. $classReflector = $resource->isFresh(0) ? false : new \ReflectionClass($class);
  304. } else {
  305. $classReflector = class_exists($class) ? new \ReflectionClass($class) : false;
  306. }
  307. } catch (\ReflectionException $e) {
  308. if ($throw) {
  309. throw $e;
  310. }
  311. }
  312. if ($this->trackResources) {
  313. if (!$classReflector) {
  314. $this->addResource($resource ?? new ClassExistenceResource($class, false));
  315. } elseif (!$classReflector->isInternal()) {
  316. $path = $classReflector->getFileName();
  317. if (!$this->inVendors($path)) {
  318. $this->addResource(new ReflectionClassResource($classReflector, $this->vendors));
  319. }
  320. }
  321. $this->classReflectors[$class] = $classReflector;
  322. }
  323. return $classReflector ?: null;
  324. }
  325. /**
  326. * Checks whether the requested file or directory exists and registers the result for resource tracking.
  327. *
  328. * @param string $path The file or directory path for which to check the existence
  329. * @param bool|string $trackContents Whether to track contents of the given resource. If a string is passed,
  330. * it will be used as pattern for tracking contents of the requested directory
  331. *
  332. * @final
  333. */
  334. public function fileExists(string $path, $trackContents = true): bool
  335. {
  336. $exists = file_exists($path);
  337. if (!$this->trackResources || $this->inVendors($path)) {
  338. return $exists;
  339. }
  340. if (!$exists) {
  341. $this->addResource(new FileExistenceResource($path));
  342. return $exists;
  343. }
  344. if (is_dir($path)) {
  345. if ($trackContents) {
  346. $this->addResource(new DirectoryResource($path, \is_string($trackContents) ? $trackContents : null));
  347. } else {
  348. $this->addResource(new GlobResource($path, '/*', false));
  349. }
  350. } elseif ($trackContents) {
  351. $this->addResource(new FileResource($path));
  352. }
  353. return $exists;
  354. }
  355. /**
  356. * Loads the configuration for an extension.
  357. *
  358. * @param string $extension The extension alias or namespace
  359. * @param array $values An array of values that customizes the extension
  360. *
  361. * @return $this
  362. *
  363. * @throws BadMethodCallException When this ContainerBuilder is compiled
  364. * @throws \LogicException if the extension is not registered
  365. */
  366. public function loadFromExtension($extension, array $values = null)
  367. {
  368. if ($this->isCompiled()) {
  369. throw new BadMethodCallException('Cannot load from an extension on a compiled container.');
  370. }
  371. if (\func_num_args() < 2) {
  372. $values = [];
  373. }
  374. $namespace = $this->getExtension($extension)->getAlias();
  375. $this->extensionConfigs[$namespace][] = $values;
  376. return $this;
  377. }
  378. /**
  379. * Adds a compiler pass.
  380. *
  381. * @param string $type The type of compiler pass
  382. * @param int $priority Used to sort the passes
  383. *
  384. * @return $this
  385. */
  386. public function addCompilerPass(CompilerPassInterface $pass, $type = PassConfig::TYPE_BEFORE_OPTIMIZATION, int $priority = 0)
  387. {
  388. $this->getCompiler()->addPass($pass, $type, $priority);
  389. $this->addObjectResource($pass);
  390. return $this;
  391. }
  392. /**
  393. * Returns the compiler pass config which can then be modified.
  394. *
  395. * @return PassConfig The compiler pass config
  396. */
  397. public function getCompilerPassConfig()
  398. {
  399. return $this->getCompiler()->getPassConfig();
  400. }
  401. /**
  402. * Returns the compiler.
  403. *
  404. * @return Compiler The compiler
  405. */
  406. public function getCompiler()
  407. {
  408. if (null === $this->compiler) {
  409. $this->compiler = new Compiler();
  410. }
  411. return $this->compiler;
  412. }
  413. /**
  414. * Sets a service.
  415. *
  416. * @param string $id The service identifier
  417. * @param object|null $service The service instance
  418. *
  419. * @throws BadMethodCallException When this ContainerBuilder is compiled
  420. */
  421. public function set($id, $service)
  422. {
  423. if (!\is_object($service) && null !== $service) {
  424. @trigger_error(sprintf('Non-object services are deprecated since Symfony 4.4, setting the "%s" service to a value of type "%s" should be avoided.', $id, \gettype($service)), \E_USER_DEPRECATED);
  425. }
  426. $id = (string) $id;
  427. if ($this->isCompiled() && (isset($this->definitions[$id]) && !$this->definitions[$id]->isSynthetic())) {
  428. // setting a synthetic service on a compiled container is alright
  429. throw new BadMethodCallException(sprintf('Setting service "%s" for an unknown or non-synthetic service definition on a compiled container is not allowed.', $id));
  430. }
  431. unset($this->definitions[$id], $this->aliasDefinitions[$id], $this->removedIds[$id]);
  432. parent::set($id, $service);
  433. }
  434. /**
  435. * Removes a service definition.
  436. *
  437. * @param string $id The service identifier
  438. */
  439. public function removeDefinition($id)
  440. {
  441. if (isset($this->definitions[$id = (string) $id])) {
  442. unset($this->definitions[$id]);
  443. $this->removedIds[$id] = true;
  444. }
  445. }
  446. /**
  447. * Returns true if the given service is defined.
  448. *
  449. * @param string $id The service identifier
  450. *
  451. * @return bool true if the service is defined, false otherwise
  452. */
  453. public function has($id)
  454. {
  455. $id = (string) $id;
  456. return isset($this->definitions[$id]) || isset($this->aliasDefinitions[$id]) || parent::has($id);
  457. }
  458. /**
  459. * Gets a service.
  460. *
  461. * @param string $id The service identifier
  462. * @param int $invalidBehavior The behavior when the service does not exist
  463. *
  464. * @return object|null The associated service
  465. *
  466. * @throws InvalidArgumentException when no definitions are available
  467. * @throws ServiceCircularReferenceException When a circular reference is detected
  468. * @throws ServiceNotFoundException When the service is not defined
  469. * @throws \Exception
  470. *
  471. * @see Reference
  472. */
  473. public function get($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE)
  474. {
  475. if ($this->isCompiled() && isset($this->removedIds[$id = (string) $id]) && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $invalidBehavior) {
  476. return parent::get($id);
  477. }
  478. $service = $this->doGet($id, $invalidBehavior);
  479. if (!\is_object($service) && null !== $service) {
  480. @trigger_error(sprintf('Non-object services are deprecated since Symfony 4.4, please fix the "%s" service which is of type "%s" right now.', $id, \gettype($service)), \E_USER_DEPRECATED);
  481. }
  482. return $service;
  483. }
  484. private function doGet(string $id, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, array &$inlineServices = null, bool $isConstructorArgument = false)
  485. {
  486. if (isset($inlineServices[$id])) {
  487. return $inlineServices[$id];
  488. }
  489. if (null === $inlineServices) {
  490. $isConstructorArgument = true;
  491. $inlineServices = [];
  492. }
  493. try {
  494. if (ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $invalidBehavior) {
  495. return parent::get($id, $invalidBehavior);
  496. }
  497. if ($service = parent::get($id, ContainerInterface::NULL_ON_INVALID_REFERENCE)) {
  498. return $service;
  499. }
  500. } catch (ServiceCircularReferenceException $e) {
  501. if ($isConstructorArgument) {
  502. throw $e;
  503. }
  504. }
  505. if (!isset($this->definitions[$id]) && isset($this->aliasDefinitions[$id])) {
  506. $alias = $this->aliasDefinitions[$id];
  507. if ($alias->isDeprecated()) {
  508. @trigger_error($alias->getDeprecationMessage($id), \E_USER_DEPRECATED);
  509. }
  510. return $this->doGet((string) $alias, $invalidBehavior, $inlineServices, $isConstructorArgument);
  511. }
  512. try {
  513. $definition = $this->getDefinition($id);
  514. } catch (ServiceNotFoundException $e) {
  515. if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE < $invalidBehavior) {
  516. return null;
  517. }
  518. throw $e;
  519. }
  520. if ($definition->hasErrors() && $e = $definition->getErrors()) {
  521. throw new RuntimeException(reset($e));
  522. }
  523. if ($isConstructorArgument) {
  524. $this->loading[$id] = true;
  525. }
  526. try {
  527. return $this->createService($definition, $inlineServices, $isConstructorArgument, $id);
  528. } finally {
  529. if ($isConstructorArgument) {
  530. unset($this->loading[$id]);
  531. }
  532. }
  533. }
  534. /**
  535. * Merges a ContainerBuilder with the current ContainerBuilder configuration.
  536. *
  537. * Service definitions overrides the current defined ones.
  538. *
  539. * But for parameters, they are overridden by the current ones. It allows
  540. * the parameters passed to the container constructor to have precedence
  541. * over the loaded ones.
  542. *
  543. * $container = new ContainerBuilder(new ParameterBag(['foo' => 'bar']));
  544. * $loader = new LoaderXXX($container);
  545. * $loader->load('resource_name');
  546. * $container->register('foo', 'stdClass');
  547. *
  548. * In the above example, even if the loaded resource defines a foo
  549. * parameter, the value will still be 'bar' as defined in the ContainerBuilder
  550. * constructor.
  551. *
  552. * @throws BadMethodCallException When this ContainerBuilder is compiled
  553. */
  554. public function merge(self $container)
  555. {
  556. if ($this->isCompiled()) {
  557. throw new BadMethodCallException('Cannot merge on a compiled container.');
  558. }
  559. $this->addDefinitions($container->getDefinitions());
  560. $this->addAliases($container->getAliases());
  561. $this->getParameterBag()->add($container->getParameterBag()->all());
  562. if ($this->trackResources) {
  563. foreach ($container->getResources() as $resource) {
  564. $this->addResource($resource);
  565. }
  566. }
  567. foreach ($this->extensions as $name => $extension) {
  568. if (!isset($this->extensionConfigs[$name])) {
  569. $this->extensionConfigs[$name] = [];
  570. }
  571. $this->extensionConfigs[$name] = array_merge($this->extensionConfigs[$name], $container->getExtensionConfig($name));
  572. }
  573. if ($this->getParameterBag() instanceof EnvPlaceholderParameterBag && $container->getParameterBag() instanceof EnvPlaceholderParameterBag) {
  574. $envPlaceholders = $container->getParameterBag()->getEnvPlaceholders();
  575. $this->getParameterBag()->mergeEnvPlaceholders($container->getParameterBag());
  576. } else {
  577. $envPlaceholders = [];
  578. }
  579. foreach ($container->envCounters as $env => $count) {
  580. if (!$count && !isset($envPlaceholders[$env])) {
  581. continue;
  582. }
  583. if (!isset($this->envCounters[$env])) {
  584. $this->envCounters[$env] = $count;
  585. } else {
  586. $this->envCounters[$env] += $count;
  587. }
  588. }
  589. foreach ($container->getAutoconfiguredInstanceof() as $interface => $childDefinition) {
  590. if (isset($this->autoconfiguredInstanceof[$interface])) {
  591. throw new InvalidArgumentException(sprintf('"%s" has already been autoconfigured and merge() does not support merging autoconfiguration for the same class/interface.', $interface));
  592. }
  593. $this->autoconfiguredInstanceof[$interface] = $childDefinition;
  594. }
  595. }
  596. /**
  597. * Returns the configuration array for the given extension.
  598. *
  599. * @param string $name The name of the extension
  600. *
  601. * @return array An array of configuration
  602. */
  603. public function getExtensionConfig($name)
  604. {
  605. if (!isset($this->extensionConfigs[$name])) {
  606. $this->extensionConfigs[$name] = [];
  607. }
  608. return $this->extensionConfigs[$name];
  609. }
  610. /**
  611. * Prepends a config array to the configs of the given extension.
  612. *
  613. * @param string $name The name of the extension
  614. * @param array $config The config to set
  615. */
  616. public function prependExtensionConfig($name, array $config)
  617. {
  618. if (!isset($this->extensionConfigs[$name])) {
  619. $this->extensionConfigs[$name] = [];
  620. }
  621. array_unshift($this->extensionConfigs[$name], $config);
  622. }
  623. /**
  624. * Compiles the container.
  625. *
  626. * This method passes the container to compiler
  627. * passes whose job is to manipulate and optimize
  628. * the container.
  629. *
  630. * The main compiler passes roughly do four things:
  631. *
  632. * * The extension configurations are merged;
  633. * * Parameter values are resolved;
  634. * * The parameter bag is frozen;
  635. * * Extension loading is disabled.
  636. *
  637. * @param bool $resolveEnvPlaceholders Whether %env()% parameters should be resolved using the current
  638. * env vars or be replaced by uniquely identifiable placeholders.
  639. * Set to "true" when you want to use the current ContainerBuilder
  640. * directly, keep to "false" when the container is dumped instead.
  641. */
  642. public function compile(bool $resolveEnvPlaceholders = false)
  643. {
  644. $compiler = $this->getCompiler();
  645. if ($this->trackResources) {
  646. foreach ($compiler->getPassConfig()->getPasses() as $pass) {
  647. $this->addObjectResource($pass);
  648. }
  649. }
  650. $bag = $this->getParameterBag();
  651. if ($resolveEnvPlaceholders && $bag instanceof EnvPlaceholderParameterBag) {
  652. $compiler->addPass(new ResolveEnvPlaceholdersPass(), PassConfig::TYPE_AFTER_REMOVING, -1000);
  653. }
  654. $compiler->compile($this);
  655. foreach ($this->definitions as $id => $definition) {
  656. if ($this->trackResources && $definition->isLazy()) {
  657. $this->getReflectionClass($definition->getClass());
  658. }
  659. }
  660. $this->extensionConfigs = [];
  661. if ($bag instanceof EnvPlaceholderParameterBag) {
  662. if ($resolveEnvPlaceholders) {
  663. $this->parameterBag = new ParameterBag($this->resolveEnvPlaceholders($bag->all(), true));
  664. }
  665. $this->envPlaceholders = $bag->getEnvPlaceholders();
  666. }
  667. parent::compile();
  668. foreach ($this->definitions + $this->aliasDefinitions as $id => $definition) {
  669. if (!$definition->isPublic() || $definition->isPrivate()) {
  670. $this->removedIds[$id] = true;
  671. }
  672. }
  673. }
  674. /**
  675. * {@inheritdoc}
  676. */
  677. public function getServiceIds()
  678. {
  679. return array_map('strval', array_unique(array_merge(array_keys($this->getDefinitions()), array_keys($this->aliasDefinitions), parent::getServiceIds())));
  680. }
  681. /**
  682. * Gets removed service or alias ids.
  683. *
  684. * @return array
  685. */
  686. public function getRemovedIds()
  687. {
  688. return $this->removedIds;
  689. }
  690. /**
  691. * Adds the service aliases.
  692. */
  693. public function addAliases(array $aliases)
  694. {
  695. foreach ($aliases as $alias => $id) {
  696. $this->setAlias($alias, $id);
  697. }
  698. }
  699. /**
  700. * Sets the service aliases.
  701. */
  702. public function setAliases(array $aliases)
  703. {
  704. $this->aliasDefinitions = [];
  705. $this->addAliases($aliases);
  706. }
  707. /**
  708. * Sets an alias for an existing service.
  709. *
  710. * @param string $alias The alias to create
  711. * @param string|Alias $id The service to alias
  712. *
  713. * @return Alias
  714. *
  715. * @throws InvalidArgumentException if the id is not a string or an Alias
  716. * @throws InvalidArgumentException if the alias is for itself
  717. */
  718. public function setAlias($alias, $id)
  719. {
  720. $alias = (string) $alias;
  721. if ('' === $alias || '\\' === $alias[-1] || \strlen($alias) !== strcspn($alias, "\0\r\n'")) {
  722. throw new InvalidArgumentException(sprintf('Invalid alias id: "%s".', $alias));
  723. }
  724. if (\is_string($id)) {
  725. $id = new Alias($id);
  726. } elseif (!$id instanceof Alias) {
  727. throw new InvalidArgumentException('$id must be a string, or an Alias object.');
  728. }
  729. if ($alias === (string) $id) {
  730. throw new InvalidArgumentException(sprintf('An alias can not reference itself, got a circular reference on "%s".', $alias));
  731. }
  732. unset($this->definitions[$alias], $this->removedIds[$alias]);
  733. return $this->aliasDefinitions[$alias] = $id;
  734. }
  735. /**
  736. * Removes an alias.
  737. *
  738. * @param string $alias The alias to remove
  739. */
  740. public function removeAlias($alias)
  741. {
  742. if (isset($this->aliasDefinitions[$alias = (string) $alias])) {
  743. unset($this->aliasDefinitions[$alias]);
  744. $this->removedIds[$alias] = true;
  745. }
  746. }
  747. /**
  748. * Returns true if an alias exists under the given identifier.
  749. *
  750. * @param string $id The service identifier
  751. *
  752. * @return bool true if the alias exists, false otherwise
  753. */
  754. public function hasAlias($id)
  755. {
  756. return isset($this->aliasDefinitions[$id = (string) $id]);
  757. }
  758. /**
  759. * Gets all defined aliases.
  760. *
  761. * @return Alias[] An array of aliases
  762. */
  763. public function getAliases()
  764. {
  765. return $this->aliasDefinitions;
  766. }
  767. /**
  768. * Gets an alias.
  769. *
  770. * @param string $id The service identifier
  771. *
  772. * @return Alias An Alias instance
  773. *
  774. * @throws InvalidArgumentException if the alias does not exist
  775. */
  776. public function getAlias($id)
  777. {
  778. $id = (string) $id;
  779. if (!isset($this->aliasDefinitions[$id])) {
  780. throw new InvalidArgumentException(sprintf('The service alias "%s" does not exist.', $id));
  781. }
  782. return $this->aliasDefinitions[$id];
  783. }
  784. /**
  785. * Registers a service definition.
  786. *
  787. * This methods allows for simple registration of service definition
  788. * with a fluid interface.
  789. *
  790. * @param string $id The service identifier
  791. * @param string|null $class The service class
  792. *
  793. * @return Definition A Definition instance
  794. */
  795. public function register($id, $class = null)
  796. {
  797. return $this->setDefinition($id, new Definition($class));
  798. }
  799. /**
  800. * Registers an autowired service definition.
  801. *
  802. * This method implements a shortcut for using setDefinition() with
  803. * an autowired definition.
  804. *
  805. * @param string $id The service identifier
  806. * @param string|null $class The service class
  807. *
  808. * @return Definition The created definition
  809. */
  810. public function autowire($id, $class = null)
  811. {
  812. return $this->setDefinition($id, (new Definition($class))->setAutowired(true));
  813. }
  814. /**
  815. * Adds the service definitions.
  816. *
  817. * @param Definition[] $definitions An array of service definitions
  818. */
  819. public function addDefinitions(array $definitions)
  820. {
  821. foreach ($definitions as $id => $definition) {
  822. $this->setDefinition($id, $definition);
  823. }
  824. }
  825. /**
  826. * Sets the service definitions.
  827. *
  828. * @param Definition[] $definitions An array of service definitions
  829. */
  830. public function setDefinitions(array $definitions)
  831. {
  832. $this->definitions = [];
  833. $this->addDefinitions($definitions);
  834. }
  835. /**
  836. * Gets all service definitions.
  837. *
  838. * @return Definition[] An array of Definition instances
  839. */
  840. public function getDefinitions()
  841. {
  842. return $this->definitions;
  843. }
  844. /**
  845. * Sets a service definition.
  846. *
  847. * @param string $id The service identifier
  848. *
  849. * @return Definition the service definition
  850. *
  851. * @throws BadMethodCallException When this ContainerBuilder is compiled
  852. */
  853. public function setDefinition($id, Definition $definition)
  854. {
  855. if ($this->isCompiled()) {
  856. throw new BadMethodCallException('Adding definition to a compiled container is not allowed.');
  857. }
  858. $id = (string) $id;
  859. if ('' === $id || '\\' === $id[-1] || \strlen($id) !== strcspn($id, "\0\r\n'")) {
  860. throw new InvalidArgumentException(sprintf('Invalid service id: "%s".', $id));
  861. }
  862. unset($this->aliasDefinitions[$id], $this->removedIds[$id]);
  863. return $this->definitions[$id] = $definition;
  864. }
  865. /**
  866. * Returns true if a service definition exists under the given identifier.
  867. *
  868. * @param string $id The service identifier
  869. *
  870. * @return bool true if the service definition exists, false otherwise
  871. */
  872. public function hasDefinition($id)
  873. {
  874. return isset($this->definitions[(string) $id]);
  875. }
  876. /**
  877. * Gets a service definition.
  878. *
  879. * @param string $id The service identifier
  880. *
  881. * @return Definition A Definition instance
  882. *
  883. * @throws ServiceNotFoundException if the service definition does not exist
  884. */
  885. public function getDefinition($id)
  886. {
  887. $id = (string) $id;
  888. if (!isset($this->definitions[$id])) {
  889. throw new ServiceNotFoundException($id);
  890. }
  891. return $this->definitions[$id];
  892. }
  893. /**
  894. * Gets a service definition by id or alias.
  895. *
  896. * The method "unaliases" recursively to return a Definition instance.
  897. *
  898. * @param string $id The service identifier or alias
  899. *
  900. * @return Definition A Definition instance
  901. *
  902. * @throws ServiceNotFoundException if the service definition does not exist
  903. */
  904. public function findDefinition($id)
  905. {
  906. $id = (string) $id;
  907. $seen = [];
  908. while (isset($this->aliasDefinitions[$id])) {
  909. $id = (string) $this->aliasDefinitions[$id];
  910. if (isset($seen[$id])) {
  911. $seen = array_values($seen);
  912. $seen = \array_slice($seen, array_search($id, $seen));
  913. $seen[] = $id;
  914. throw new ServiceCircularReferenceException($id, $seen);
  915. }
  916. $seen[$id] = $id;
  917. }
  918. return $this->getDefinition($id);
  919. }
  920. /**
  921. * Creates a service for a service definition.
  922. *
  923. * @return mixed The service described by the service definition
  924. *
  925. * @throws RuntimeException When the factory definition is incomplete
  926. * @throws RuntimeException When the service is a synthetic service
  927. * @throws InvalidArgumentException When configure callable is not callable
  928. */
  929. private function createService(Definition $definition, array &$inlineServices, bool $isConstructorArgument = false, string $id = null, bool $tryProxy = true)
  930. {
  931. if (null === $id && isset($inlineServices[$h = spl_object_hash($definition)])) {
  932. return $inlineServices[$h];
  933. }
  934. if ($definition instanceof ChildDefinition) {
  935. throw new RuntimeException(sprintf('Constructing service "%s" from a parent definition is not supported at build time.', $id));
  936. }
  937. if ($definition->isSynthetic()) {
  938. throw new RuntimeException(sprintf('You have requested a synthetic service ("%s"). The DIC does not know how to construct this service.', $id));
  939. }
  940. if ($definition->isDeprecated()) {
  941. @trigger_error($definition->getDeprecationMessage($id), \E_USER_DEPRECATED);
  942. }
  943. if ($tryProxy && $definition->isLazy() && !$tryProxy = !($proxy = $this->proxyInstantiator) || $proxy instanceof RealServiceInstantiator) {
  944. $proxy = $proxy->instantiateProxy(
  945. $this,
  946. $definition,
  947. $id, function () use ($definition, &$inlineServices, $id) {
  948. return $this->createService($definition, $inlineServices, true, $id, false);
  949. }
  950. );
  951. $this->shareService($definition, $proxy, $id, $inlineServices);
  952. return $proxy;
  953. }
  954. $parameterBag = $this->getParameterBag();
  955. if (null !== $definition->getFile()) {
  956. require_once $parameterBag->resolveValue($definition->getFile());
  957. }
  958. $arguments = $this->doResolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getArguments())), $inlineServices, $isConstructorArgument);
  959. if (null !== $factory = $definition->getFactory()) {
  960. if (\is_array($factory)) {
  961. $factory = [$this->doResolveServices($parameterBag->resolveValue($factory[0]), $inlineServices, $isConstructorArgument), $factory[1]];
  962. } elseif (!\is_string($factory)) {
  963. throw new RuntimeException(sprintf('Cannot create service "%s" because of invalid factory.', $id));
  964. }
  965. }
  966. if (null !== $id && $definition->isShared() && isset($this->services[$id]) && ($tryProxy || !$definition->isLazy())) {
  967. return $this->services[$id];
  968. }
  969. if (null !== $factory) {
  970. $service = $factory(...$arguments);
  971. if (!$definition->isDeprecated() && \is_array($factory) && \is_string($factory[0])) {
  972. $r = new \ReflectionClass($factory[0]);
  973. if (0 < strpos($r->getDocComment(), "\n * @deprecated ")) {
  974. @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);
  975. }
  976. }
  977. } else {
  978. $r = new \ReflectionClass($parameterBag->resolveValue($definition->getClass()));
  979. $service = null === $r->getConstructor() ? $r->newInstance() : $r->newInstanceArgs(array_values($arguments));
  980. if (!$definition->isDeprecated() && 0 < strpos($r->getDocComment(), "\n * @deprecated ")) {
  981. @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);
  982. }
  983. }
  984. $lastWitherIndex = null;
  985. foreach ($definition->getMethodCalls() as $k => $call) {
  986. if ($call[2] ?? false) {
  987. $lastWitherIndex = $k;
  988. }
  989. }
  990. if (null === $lastWitherIndex && ($tryProxy || !$definition->isLazy())) {
  991. // share only if proxying failed, or if not a proxy, and if no withers are found
  992. $this->shareService($definition, $service, $id, $inlineServices);
  993. }
  994. $properties = $this->doResolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getProperties())), $inlineServices);
  995. foreach ($properties as $name => $value) {
  996. $service->$name = $value;
  997. }
  998. foreach ($definition->getMethodCalls() as $k => $call) {
  999. $service = $this->callMethod($service, $call, $inlineServices);
  1000. if ($lastWitherIndex === $k && ($tryProxy || !$definition->isLazy())) {
  1001. // share only if proxying failed, or if not a proxy, and this is the last wither
  1002. $this->shareService($definition, $service, $id, $inlineServices);
  1003. }
  1004. }
  1005. if ($callable = $definition->getConfigurator()) {
  1006. if (\is_array($callable)) {
  1007. $callable[0] = $parameterBag->resolveValue($callable[0]);
  1008. if ($callable[0] instanceof Reference) {
  1009. $callable[0] = $this->doGet((string) $callable[0], $callable[0]->getInvalidBehavior(), $inlineServices);
  1010. } elseif ($callable[0] instanceof Definition) {
  1011. $callable[0] = $this->createService($callable[0], $inlineServices);
  1012. }
  1013. }
  1014. if (!\is_callable($callable)) {
  1015. throw new InvalidArgumentException(sprintf('The configure callable for class "%s" is not a callable.', \get_class($service)));
  1016. }
  1017. $callable($service);
  1018. }
  1019. return $service;
  1020. }
  1021. /**
  1022. * Replaces service references by the real service instance and evaluates expressions.
  1023. *
  1024. * @param mixed $value A value
  1025. *
  1026. * @return mixed The same value with all service references replaced by
  1027. * the real service instances and all expressions evaluated
  1028. */
  1029. public function resolveServices($value)
  1030. {
  1031. return $this->doResolveServices($value);
  1032. }
  1033. private function doResolveServices($value, array &$inlineServices = [], bool $isConstructorArgument = false)
  1034. {
  1035. if (\is_array($value)) {
  1036. foreach ($value as $k => $v) {
  1037. $value[$k] = $this->doResolveServices($v, $inlineServices, $isConstructorArgument);
  1038. }
  1039. } elseif ($value instanceof ServiceClosureArgument) {
  1040. $reference = $value->getValues()[0];
  1041. $value = function () use ($reference) {
  1042. return $this->resolveServices($reference);
  1043. };
  1044. } elseif ($value instanceof IteratorArgument) {
  1045. $value = new RewindableGenerator(function () use ($value, &$inlineServices) {
  1046. foreach ($value->getValues() as $k => $v) {
  1047. foreach (self::getServiceConditionals($v) as $s) {
  1048. if (!$this->has($s)) {
  1049. continue 2;
  1050. }
  1051. }
  1052. foreach (self::getInitializedConditionals($v) as $s) {
  1053. if (!$this->doGet($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE, $inlineServices)) {
  1054. continue 2;
  1055. }
  1056. }
  1057. yield $k => $this->doResolveServices($v, $inlineServices);
  1058. }
  1059. }, function () use ($value): int {
  1060. $count = 0;
  1061. foreach ($value->getValues() as $v) {
  1062. foreach (self::getServiceConditionals($v) as $s) {
  1063. if (!$this->has($s)) {
  1064. continue 2;
  1065. }
  1066. }
  1067. foreach (self::getInitializedConditionals($v) as $s) {
  1068. if (!$this->doGet($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE)) {
  1069. continue 2;
  1070. }
  1071. }
  1072. ++$count;
  1073. }
  1074. return $count;
  1075. });
  1076. } elseif ($value instanceof ServiceLocatorArgument) {
  1077. $refs = $types = [];
  1078. foreach ($value->getValues() as $k => $v) {
  1079. if ($v) {
  1080. $refs[$k] = [$v];
  1081. $types[$k] = $v instanceof TypedReference ? $v->getType() : '?';
  1082. }
  1083. }
  1084. $value = new ServiceLocator(\Closure::fromCallable([$this, 'resolveServices']), $refs, $types);
  1085. } elseif ($value instanceof Reference) {
  1086. $value = $this->doGet((string) $value, $value->getInvalidBehavior(), $inlineServices, $isConstructorArgument);
  1087. } elseif ($value instanceof Definition) {
  1088. $value = $this->createService($value, $inlineServices, $isConstructorArgument);
  1089. } elseif ($value instanceof Parameter) {
  1090. $value = $this->getParameter((string) $value);
  1091. } elseif ($value instanceof Expression) {
  1092. $value = $this->getExpressionLanguage()->evaluate($value, ['container' => $this]);
  1093. }
  1094. return $value;
  1095. }
  1096. /**
  1097. * Returns service ids for a given tag.
  1098. *
  1099. * Example:
  1100. *
  1101. * $container->register('foo')->addTag('my.tag', ['hello' => 'world']);
  1102. *
  1103. * $serviceIds = $container->findTaggedServiceIds('my.tag');
  1104. * foreach ($serviceIds as $serviceId => $tags) {
  1105. * foreach ($tags as $tag) {
  1106. * echo $tag['hello'];
  1107. * }
  1108. * }
  1109. *
  1110. * @param string $name
  1111. * @param bool $throwOnAbstract
  1112. *
  1113. * @return array An array of tags with the tagged service as key, holding a list of attribute arrays
  1114. */
  1115. public function findTaggedServiceIds($name, $throwOnAbstract = false)
  1116. {
  1117. $this->usedTags[] = $name;
  1118. $tags = [];
  1119. foreach ($this->getDefinitions() as $id => $definition) {
  1120. if ($definition->hasTag($name)) {
  1121. if ($throwOnAbstract && $definition->isAbstract()) {
  1122. throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must not be abstract.', $id, $name));
  1123. }
  1124. $tags[$id] = $definition->getTag($name);
  1125. }
  1126. }
  1127. return $tags;
  1128. }
  1129. /**
  1130. * Returns all tags the defined services use.
  1131. *
  1132. * @return array An array of tags
  1133. */
  1134. public function findTags()
  1135. {
  1136. $tags = [];
  1137. foreach ($this->getDefinitions() as $id => $definition) {
  1138. $tags = array_merge(array_keys($definition->getTags()), $tags);
  1139. }
  1140. return array_unique($tags);
  1141. }
  1142. /**
  1143. * Returns all tags not queried by findTaggedServiceIds.
  1144. *
  1145. * @return string[] An array of tags
  1146. */
  1147. public function findUnusedTags()
  1148. {
  1149. return array_values(array_diff($this->findTags(), $this->usedTags));
  1150. }
  1151. public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider)
  1152. {
  1153. $this->expressionLanguageProviders[] = $provider;
  1154. }
  1155. /**
  1156. * @return ExpressionFunctionProviderInterface[]
  1157. */
  1158. public function getExpressionLanguageProviders()
  1159. {
  1160. return $this->expressionLanguageProviders;
  1161. }
  1162. /**
  1163. * Returns a ChildDefinition that will be used for autoconfiguring the interface/class.
  1164. *
  1165. * @param string $interface The class or interface to match
  1166. *
  1167. * @return ChildDefinition
  1168. */
  1169. public function registerForAutoconfiguration($interface)
  1170. {
  1171. if (!isset($this->autoconfiguredInstanceof[$interface])) {
  1172. $this->autoconfiguredInstanceof[$interface] = new ChildDefinition('');
  1173. }
  1174. return $this->autoconfiguredInstanceof[$interface];
  1175. }
  1176. /**
  1177. * Registers an autowiring alias that only binds to a specific argument name.
  1178. *
  1179. * The argument name is derived from $name if provided (from $id otherwise)
  1180. * using camel case: "foo.bar" or "foo_bar" creates an alias bound to
  1181. * "$fooBar"-named arguments with $type as type-hint. Such arguments will
  1182. * receive the service $id when autowiring is used.
  1183. */
  1184. public function registerAliasForArgument(string $id, string $type, string $name = null): Alias
  1185. {
  1186. $name = lcfirst(str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $name ?? $id))));
  1187. if (!preg_match('/^[a-zA-Z_\x7f-\xff]/', $name)) {
  1188. throw new InvalidArgumentException(sprintf('Invalid argument name "%s" for service "%s": the first character must be a letter.', $name, $id));
  1189. }
  1190. return $this->setAlias($type.' $'.$name, $id);
  1191. }
  1192. /**
  1193. * Returns an array of ChildDefinition[] keyed by interface.
  1194. *
  1195. * @return ChildDefinition[]
  1196. */
  1197. public function getAutoconfiguredInstanceof()
  1198. {
  1199. return $this->autoconfiguredInstanceof;
  1200. }
  1201. /**
  1202. * Resolves env parameter placeholders in a string or an array.
  1203. *
  1204. * @param mixed $value The value to resolve
  1205. * @param string|true|null $format A sprintf() format returning the replacement for each env var name or
  1206. * null to resolve back to the original "%env(VAR)%" format or
  1207. * true to resolve to the actual values of the referenced env vars
  1208. * @param array &$usedEnvs Env vars found while resolving are added to this array
  1209. *
  1210. * @return mixed The value with env parameters resolved if a string or an array is passed
  1211. */
  1212. public function resolveEnvPlaceholders($value, $format = null, array &$usedEnvs = null)
  1213. {
  1214. if (null === $format) {
  1215. $format = '%%env(%s)%%';
  1216. }
  1217. $bag = $this->getParameterBag();
  1218. if (true === $format) {
  1219. $value = $bag->resolveValue($value);
  1220. }
  1221. if ($value instanceof Definition) {
  1222. $value = (array) $value;
  1223. }
  1224. if (\is_array($value)) {
  1225. $result = [];
  1226. foreach ($value as $k => $v) {
  1227. $result[\is_string($k) ? $this->resolveEnvPlaceholders($k, $format, $usedEnvs) : $k] = $this->resolveEnvPlaceholders($v, $format, $usedEnvs);
  1228. }
  1229. return $result;
  1230. }
  1231. if (!\is_string($value) || 38 > \strlen($value)) {
  1232. return $value;
  1233. }
  1234. $envPlaceholders = $bag instanceof EnvPlaceholderParameterBag ? $bag->getEnvPlaceholders() : $this->envPlaceholders;
  1235. $completed = false;
  1236. foreach ($envPlaceholders as $env => $placeholders) {
  1237. foreach ($placeholders as $placeholder) {
  1238. if (false !== stripos($value, $placeholder)) {
  1239. if (true === $format) {
  1240. $resolved = $bag->escapeValue($this->getEnv($env));
  1241. } else {
  1242. $resolved = sprintf($format, $env);
  1243. }
  1244. if ($placeholder === $value) {
  1245. $value = $resolved;
  1246. $completed = true;
  1247. } else {
  1248. if (!\is_string($resolved) && !is_numeric($resolved)) {
  1249. throw new RuntimeException(sprintf('A string value must be composed of strings and/or numbers, but found parameter "env(%s)" of type "%s" inside string value "%s".', $env, \gettype($resolved), $this->resolveEnvPlaceholders($value)));
  1250. }
  1251. $value = str_ireplace($placeholder, $resolved, $value);
  1252. }
  1253. $usedEnvs[$env] = $env;
  1254. $this->envCounters[$env] = isset($this->envCounters[$env]) ? 1 + $this->envCounters[$env] : 1;
  1255. if ($completed) {
  1256. break 2;
  1257. }
  1258. }
  1259. }
  1260. }
  1261. return $value;
  1262. }
  1263. /**
  1264. * Get statistics about env usage.
  1265. *
  1266. * @return int[] The number of time each env vars has been resolved
  1267. */
  1268. public function getEnvCounters()
  1269. {
  1270. $bag = $this->getParameterBag();
  1271. $envPlaceholders = $bag instanceof EnvPlaceholderParameterBag ? $bag->getEnvPlaceholders() : $this->envPlaceholders;
  1272. foreach ($envPlaceholders as $env => $placeholders) {
  1273. if (!isset($this->envCounters[$env])) {
  1274. $this->envCounters[$env] = 0;
  1275. }
  1276. }
  1277. return $this->envCounters;
  1278. }
  1279. /**
  1280. * @final
  1281. */
  1282. public function log(CompilerPassInterface $pass, string $message)
  1283. {
  1284. $this->getCompiler()->log($pass, $this->resolveEnvPlaceholders($message));
  1285. }
  1286. /**
  1287. * Gets removed binding ids.
  1288. *
  1289. * @internal
  1290. */
  1291. public function getRemovedBindingIds(): array
  1292. {
  1293. return $this->removedBindingIds;
  1294. }
  1295. /**
  1296. * Removes bindings for a service.
  1297. *
  1298. * @internal
  1299. */
  1300. public function removeBindings(string $id)
  1301. {
  1302. if ($this->hasDefinition($id)) {
  1303. foreach ($this->getDefinition($id)->getBindings() as $key => $binding) {
  1304. [, $bindingId] = $binding->getValues();
  1305. $this->removedBindingIds[(int) $bindingId] = true;
  1306. }
  1307. }
  1308. }
  1309. /**
  1310. * Returns the Service Conditionals.
  1311. *
  1312. * @param mixed $value An array of conditionals to return
  1313. *
  1314. * @internal
  1315. */
  1316. public static function getServiceConditionals($value): array
  1317. {
  1318. $services = [];
  1319. if (\is_array($value)) {
  1320. foreach ($value as $v) {
  1321. $services = array_unique(array_merge($services, self::getServiceConditionals($v)));
  1322. }
  1323. } elseif ($value instanceof Reference && ContainerInterface::IGNORE_ON_INVALID_REFERENCE === $value->getInvalidBehavior()) {
  1324. $services[] = (string) $value;
  1325. }
  1326. return $services;
  1327. }
  1328. /**
  1329. * Returns the initialized conditionals.
  1330. *
  1331. * @param mixed $value An array of conditionals to return
  1332. *
  1333. * @internal
  1334. */
  1335. public static function getInitializedConditionals($value): array
  1336. {
  1337. $services = [];
  1338. if (\is_array($value)) {
  1339. foreach ($value as $v) {
  1340. $services = array_unique(array_merge($services, self::getInitializedConditionals($v)));
  1341. }
  1342. } elseif ($value instanceof Reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $value->getInvalidBehavior()) {
  1343. $services[] = (string) $value;
  1344. }
  1345. return $services;
  1346. }
  1347. /**
  1348. * Computes a reasonably unique hash of a value.
  1349. *
  1350. * @param mixed $value A serializable value
  1351. *
  1352. * @return string
  1353. */
  1354. public static function hash($value)
  1355. {
  1356. $hash = substr(base64_encode(hash('sha256', serialize($value), true)), 0, 7);
  1357. return str_replace(['/', '+'], ['.', '_'], $hash);
  1358. }
  1359. /**
  1360. * {@inheritdoc}
  1361. */
  1362. protected function getEnv($name)
  1363. {
  1364. $value = parent::getEnv($name);
  1365. $bag = $this->getParameterBag();
  1366. if (!\is_string($value) || !$bag instanceof EnvPlaceholderParameterBag) {
  1367. return $value;
  1368. }
  1369. $envPlaceholders = $bag->getEnvPlaceholders();
  1370. if (isset($envPlaceholders[$name][$value])) {
  1371. $bag = new ParameterBag($bag->all());
  1372. return $bag->unescapeValue($bag->get("env($name)"));
  1373. }
  1374. foreach ($envPlaceholders as $env => $placeholders) {
  1375. if (isset($placeholders[$value])) {
  1376. return $this->getEnv($env);
  1377. }
  1378. }
  1379. $this->resolving["env($name)"] = true;
  1380. try {
  1381. return $bag->unescapeValue($this->resolveEnvPlaceholders($bag->escapeValue($value), true));
  1382. } finally {
  1383. unset($this->resolving["env($name)"]);
  1384. }
  1385. }
  1386. private function callMethod($service, array $call, array &$inlineServices)
  1387. {
  1388. foreach (self::getServiceConditionals($call[1]) as $s) {
  1389. if (!$this->has($s)) {
  1390. return $service;
  1391. }
  1392. }
  1393. foreach (self::getInitializedConditionals($call[1]) as $s) {
  1394. if (!$this->doGet($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE, $inlineServices)) {
  1395. return $service;
  1396. }
  1397. }
  1398. $result = $service->{$call[0]}(...$this->doResolveServices($this->getParameterBag()->unescapeValue($this->getParameterBag()->resolveValue($call[1])), $inlineServices));
  1399. return empty($call[2]) ? $service : $result;
  1400. }
  1401. /**
  1402. * Shares a given service in the container.
  1403. *
  1404. * @param mixed $service
  1405. */
  1406. private function shareService(Definition $definition, $service, ?string $id, array &$inlineServices)
  1407. {
  1408. $inlineServices[null !== $id ? $id : spl_object_hash($definition)] = $service;
  1409. if (null !== $id && $definition->isShared()) {
  1410. $this->services[$id] = $service;
  1411. unset($this->loading[$id]);
  1412. }
  1413. }
  1414. private function getExpressionLanguage(): ExpressionLanguage
  1415. {
  1416. if (null === $this->expressionLanguage) {
  1417. if (!class_exists(\Symfony\Component\ExpressionLanguage\ExpressionLanguage::class)) {
  1418. throw new LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');
  1419. }
  1420. $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders);
  1421. }
  1422. return $this->expressionLanguage;
  1423. }
  1424. private function inVendors(string $path): bool
  1425. {
  1426. if (null === $this->vendors) {
  1427. $this->vendors = (new ComposerResource())->getVendors();
  1428. }
  1429. $path = realpath($path) ?: $path;
  1430. foreach ($this->vendors as $vendor) {
  1431. if (0 === strpos($path, $vendor) && false !== strpbrk(substr($path, \strlen($vendor), 1), '/'.\DIRECTORY_SEPARATOR)) {
  1432. $this->addResource(new FileResource($vendor.'/composer/installed.json'));
  1433. return true;
  1434. }
  1435. }
  1436. return false;
  1437. }
  1438. }