ContainerBuilder.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853
  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\Extension\ExtensionInterface;
  15. use Symfony\Component\Config\Resource\FileResource;
  16. use Symfony\Component\Config\Resource\ResourceInterface;
  17. /**
  18. * ContainerBuilder is a DI container that provides an API to easily describe services.
  19. *
  20. * @author Fabien Potencier <fabien@symfony.com>
  21. *
  22. * @api
  23. */
  24. class ContainerBuilder extends Container implements TaggedContainerInterface
  25. {
  26. private $extensions = array();
  27. private $extensionsByNs = array();
  28. private $definitions = array();
  29. private $aliases = array();
  30. private $resources = array();
  31. private $extensionConfigs = array();
  32. private $compiler;
  33. /**
  34. * Registers an extension.
  35. *
  36. * @param ExtensionInterface $extension An extension instance
  37. *
  38. * @api
  39. */
  40. public function registerExtension(ExtensionInterface $extension)
  41. {
  42. $this->extensions[$extension->getAlias()] = $extension;
  43. if (false !== $extension->getNamespace()) {
  44. $this->extensionsByNs[$extension->getNamespace()] = $extension;
  45. }
  46. }
  47. /**
  48. * Returns an extension by alias or namespace.
  49. *
  50. * @param string $name An alias or a namespace
  51. *
  52. * @return ExtensionInterface An extension instance
  53. *
  54. * @api
  55. */
  56. public function getExtension($name)
  57. {
  58. if (isset($this->extensions[$name])) {
  59. return $this->extensions[$name];
  60. }
  61. if (isset($this->extensionsByNs[$name])) {
  62. return $this->extensionsByNs[$name];
  63. }
  64. throw new \LogicException(sprintf('Container extension "%s" is not registered', $name));
  65. }
  66. /**
  67. * Returns all registered extensions.
  68. *
  69. * @return array An array of ExtensionInterface
  70. *
  71. * @api
  72. */
  73. public function getExtensions()
  74. {
  75. return $this->extensions;
  76. }
  77. /**
  78. * Checks if we have an extension.
  79. *
  80. * @param string $name The name of the extension
  81. *
  82. * @return Boolean If the extension exists
  83. *
  84. * @api
  85. */
  86. public function hasExtension($name)
  87. {
  88. return isset($this->extensions[$name]) || isset($this->extensionsByNs[$name]);
  89. }
  90. /**
  91. * Returns an array of resources loaded to build this configuration.
  92. *
  93. * @return ResourceInterface[] An array of resources
  94. *
  95. * @api
  96. */
  97. public function getResources()
  98. {
  99. return array_unique($this->resources);
  100. }
  101. /**
  102. * Adds a resource for this configuration.
  103. *
  104. * @param ResourceInterface $resource A resource instance
  105. *
  106. * @return ContainerBuilder The current instance
  107. *
  108. * @api
  109. */
  110. public function addResource(ResourceInterface $resource)
  111. {
  112. $this->resources[] = $resource;
  113. return $this;
  114. }
  115. /**
  116. * Adds the object class hierarchy as resources.
  117. *
  118. * @param object $object An object instance
  119. *
  120. * @api
  121. */
  122. public function addObjectResource($object)
  123. {
  124. $parent = new \ReflectionObject($object);
  125. do {
  126. $this->addResource(new FileResource($parent->getFileName()));
  127. } while ($parent = $parent->getParentClass());
  128. }
  129. /**
  130. * Loads the configuration for an extension.
  131. *
  132. * @param string $extension The extension alias or namespace
  133. * @param array $values An array of values that customizes the extension
  134. *
  135. * @return ContainerBuilder The current instance
  136. *
  137. * @api
  138. */
  139. public function loadFromExtension($extension, array $values = array())
  140. {
  141. if (true === $this->isFrozen()) {
  142. throw new \LogicException('Cannot load from an extension on a frozen container.');
  143. }
  144. $namespace = $this->getExtension($extension)->getAlias();
  145. $this->extensionConfigs[$namespace][] = $values;
  146. return $this;
  147. }
  148. /**
  149. * Adds a compiler pass.
  150. *
  151. * @param CompilerPassInterface $pass A compiler pass
  152. * @param string $type The type of compiler pass
  153. *
  154. * @api
  155. */
  156. public function addCompilerPass(CompilerPassInterface $pass, $type = PassConfig::TYPE_BEFORE_OPTIMIZATION)
  157. {
  158. if (null === $this->compiler) {
  159. $this->compiler = new Compiler();
  160. }
  161. $this->compiler->addPass($pass, $type);
  162. $this->addObjectResource($pass);
  163. }
  164. /**
  165. * Returns the compiler pass config which can then be modified.
  166. *
  167. * @return PassConfig The compiler pass config
  168. *
  169. * @api
  170. */
  171. public function getCompilerPassConfig()
  172. {
  173. if (null === $this->compiler) {
  174. $this->compiler = new Compiler();
  175. }
  176. return $this->compiler->getPassConfig();
  177. }
  178. /**
  179. * Returns the compiler.
  180. *
  181. * @return Compiler The compiler
  182. *
  183. * @api
  184. */
  185. public function getCompiler()
  186. {
  187. if (null === $this->compiler) {
  188. $this->compiler = new Compiler();
  189. }
  190. return $this->compiler;
  191. }
  192. /**
  193. * Returns all Scopes.
  194. *
  195. * @return array An array of scopes
  196. *
  197. * @api
  198. */
  199. public function getScopes()
  200. {
  201. return $this->scopes;
  202. }
  203. /**
  204. * Returns all Scope children.
  205. *
  206. * @return array An array of scope children.
  207. *
  208. * @api
  209. */
  210. public function getScopeChildren()
  211. {
  212. return $this->scopeChildren;
  213. }
  214. /**
  215. * Sets a service.
  216. *
  217. * @param string $id The service identifier
  218. * @param object $service The service instance
  219. * @param string $scope The scope
  220. *
  221. * @throws BadMethodCallException
  222. *
  223. * @api
  224. */
  225. public function set($id, $service, $scope = self::SCOPE_CONTAINER)
  226. {
  227. if ($this->isFrozen()) {
  228. throw new \BadMethodCallException('Setting service on a frozen container is not allowed');
  229. }
  230. $id = strtolower($id);
  231. unset($this->definitions[$id], $this->aliases[$id]);
  232. parent::set($id, $service, $scope);
  233. }
  234. /**
  235. * Removes a service definition.
  236. *
  237. * @param string $id The service identifier
  238. *
  239. * @api
  240. */
  241. public function removeDefinition($id)
  242. {
  243. unset($this->definitions[strtolower($id)]);
  244. }
  245. /**
  246. * Returns true if the given service is defined.
  247. *
  248. * @param string $id The service identifier
  249. *
  250. * @return Boolean true if the service is defined, false otherwise
  251. *
  252. * @api
  253. */
  254. public function has($id)
  255. {
  256. $id = strtolower($id);
  257. return isset($this->definitions[$id]) || isset($this->aliases[$id]) || parent::has($id);
  258. }
  259. /**
  260. * Gets a service.
  261. *
  262. * @param string $id The service identifier
  263. * @param integer $invalidBehavior The behavior when the service does not exist
  264. *
  265. * @return object The associated service
  266. *
  267. * @throws \InvalidArgumentException if the service is not defined
  268. * @throws \LogicException if the service has a circular reference to itself
  269. *
  270. * @see Reference
  271. *
  272. * @api
  273. */
  274. public function get($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE)
  275. {
  276. $id = strtolower($id);
  277. try {
  278. return parent::get($id, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE);
  279. } catch (\InvalidArgumentException $e) {
  280. if (isset($this->loading[$id])) {
  281. throw new \LogicException(sprintf('The service "%s" has a circular reference to itself.', $id), 0, $e);
  282. }
  283. if (!$this->hasDefinition($id) && isset($this->aliases[$id])) {
  284. return $this->get($this->aliases[$id]);
  285. }
  286. try {
  287. $definition = $this->getDefinition($id);
  288. } catch (\InvalidArgumentException $e) {
  289. if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $invalidBehavior) {
  290. return null;
  291. }
  292. throw $e;
  293. }
  294. $this->loading[$id] = true;
  295. $service = $this->createService($definition, $id);
  296. unset($this->loading[$id]);
  297. return $service;
  298. }
  299. }
  300. /**
  301. * Merges a ContainerBuilder with the current ContainerBuilder configuration.
  302. *
  303. * Service definitions overrides the current defined ones.
  304. *
  305. * But for parameters, they are overridden by the current ones. It allows
  306. * the parameters passed to the container constructor to have precedence
  307. * over the loaded ones.
  308. *
  309. * $container = new ContainerBuilder(array('foo' => 'bar'));
  310. * $loader = new LoaderXXX($container);
  311. * $loader->load('resource_name');
  312. * $container->register('foo', new stdClass());
  313. *
  314. * In the above example, even if the loaded resource defines a foo
  315. * parameter, the value will still be 'bar' as defined in the ContainerBuilder
  316. * constructor.
  317. *
  318. * @param ContainerBuilder $container The ContainerBuilder instance to merge.
  319. *
  320. * @throws \LogicException when this ContainerBuilder is frozen
  321. *
  322. * @api
  323. */
  324. public function merge(ContainerBuilder $container)
  325. {
  326. if (true === $this->isFrozen()) {
  327. throw new \LogicException('Cannot merge on a frozen container.');
  328. }
  329. $this->addDefinitions($container->getDefinitions());
  330. $this->addAliases($container->getAliases());
  331. $this->getParameterBag()->add($container->getParameterBag()->all());
  332. foreach ($container->getResources() as $resource) {
  333. $this->addResource($resource);
  334. }
  335. foreach ($this->extensions as $name => $extension) {
  336. if (!isset($this->extensionConfigs[$name])) {
  337. $this->extensionConfigs[$name] = array();
  338. }
  339. $this->extensionConfigs[$name] = array_merge($this->extensionConfigs[$name], $container->getExtensionConfig($name));
  340. }
  341. }
  342. /**
  343. * Returns the configuration array for the given extension.
  344. *
  345. * @param string $name The name of the extension
  346. *
  347. * @return array An array of configuration
  348. *
  349. * @api
  350. */
  351. public function getExtensionConfig($name)
  352. {
  353. if (!isset($this->extensionConfigs[$name])) {
  354. $this->extensionConfigs[$name] = array();
  355. }
  356. return $this->extensionConfigs[$name];
  357. }
  358. /**
  359. * Compiles the container.
  360. *
  361. * This method passes the container to compiler
  362. * passes whose job is to manipulate and optimize
  363. * the container.
  364. *
  365. * The main compiler passes roughly do four things:
  366. *
  367. * * The extension configurations are merged;
  368. * * Parameter values are resolved;
  369. * * The parameter bag is frozen;
  370. * * Extension loading is disabled.
  371. *
  372. * @api
  373. */
  374. public function compile()
  375. {
  376. if (null === $this->compiler) {
  377. $this->compiler = new Compiler();
  378. }
  379. foreach ($this->compiler->getPassConfig()->getPasses() as $pass) {
  380. $this->addObjectResource($pass);
  381. }
  382. $this->compiler->compile($this);
  383. $this->extensionConfigs = array();
  384. parent::compile();
  385. }
  386. /**
  387. * Gets all service ids.
  388. *
  389. * @return array An array of all defined service ids
  390. */
  391. public function getServiceIds()
  392. {
  393. return array_unique(array_merge(array_keys($this->getDefinitions()), array_keys($this->aliases), parent::getServiceIds()));
  394. }
  395. /**
  396. * Adds the service aliases.
  397. *
  398. * @param array $aliases An array of aliases
  399. *
  400. * @api
  401. */
  402. public function addAliases(array $aliases)
  403. {
  404. foreach ($aliases as $alias => $id) {
  405. $this->setAlias($alias, $id);
  406. }
  407. }
  408. /**
  409. * Sets the service aliases.
  410. *
  411. * @param array $aliases An array of service definitions
  412. *
  413. * @api
  414. */
  415. public function setAliases(array $aliases)
  416. {
  417. $this->aliases = array();
  418. $this->addAliases($aliases);
  419. }
  420. /**
  421. * Sets an alias for an existing service.
  422. *
  423. * @param string $alias The alias to create
  424. * @param mixed $id The service to alias
  425. *
  426. * @api
  427. */
  428. public function setAlias($alias, $id)
  429. {
  430. $alias = strtolower($alias);
  431. if (is_string($id)) {
  432. $id = new Alias($id);
  433. } elseif (!$id instanceof Alias) {
  434. throw new \InvalidArgumentException('$id must be a string, or an Alias object.');
  435. }
  436. if ($alias === strtolower($id)) {
  437. throw new \InvalidArgumentException('An alias can not reference itself, got a circular reference on "'.$alias.'".');
  438. }
  439. unset($this->definitions[$alias]);
  440. $this->aliases[$alias] = $id;
  441. }
  442. /**
  443. * Removes an alias.
  444. *
  445. * @param string $alias The alias to remove
  446. *
  447. * @api
  448. */
  449. public function removeAlias($alias)
  450. {
  451. unset($this->aliases[strtolower($alias)]);
  452. }
  453. /**
  454. * Returns true if an alias exists under the given identifier.
  455. *
  456. * @param string $id The service identifier
  457. *
  458. * @return Boolean true if the alias exists, false otherwise
  459. *
  460. * @api
  461. */
  462. public function hasAlias($id)
  463. {
  464. return isset($this->aliases[strtolower($id)]);
  465. }
  466. /**
  467. * Gets all defined aliases.
  468. *
  469. * @return array An array of aliases
  470. *
  471. * @api
  472. */
  473. public function getAliases()
  474. {
  475. return $this->aliases;
  476. }
  477. /**
  478. * Gets an alias.
  479. *
  480. * @param string $id The service identifier
  481. *
  482. * @return Alias An Alias instance
  483. *
  484. * @throws \InvalidArgumentException if the alias does not exist
  485. *
  486. * @api
  487. */
  488. public function getAlias($id)
  489. {
  490. $id = strtolower($id);
  491. if (!$this->hasAlias($id)) {
  492. throw new \InvalidArgumentException(sprintf('The service alias "%s" does not exist.', $id));
  493. }
  494. return $this->aliases[$id];
  495. }
  496. /**
  497. * Registers a service definition.
  498. *
  499. * This methods allows for simple registration of service definition
  500. * with a fluid interface.
  501. *
  502. * @param string $id The service identifier
  503. * @param string $class The service class
  504. *
  505. * @return Definition A Definition instance
  506. *
  507. * @api
  508. */
  509. public function register($id, $class = null)
  510. {
  511. return $this->setDefinition(strtolower($id), new Definition($class));
  512. }
  513. /**
  514. * Adds the service definitions.
  515. *
  516. * @param Definition[] $definitions An array of service definitions
  517. *
  518. * @api
  519. */
  520. public function addDefinitions(array $definitions)
  521. {
  522. foreach ($definitions as $id => $definition) {
  523. $this->setDefinition($id, $definition);
  524. }
  525. }
  526. /**
  527. * Sets the service definitions.
  528. *
  529. * @param array $definitions An array of service definitions
  530. *
  531. * @api
  532. */
  533. public function setDefinitions(array $definitions)
  534. {
  535. $this->definitions = array();
  536. $this->addDefinitions($definitions);
  537. }
  538. /**
  539. * Gets all service definitions.
  540. *
  541. * @return array An array of Definition instances
  542. *
  543. * @api
  544. */
  545. public function getDefinitions()
  546. {
  547. return $this->definitions;
  548. }
  549. /**
  550. * Sets a service definition.
  551. *
  552. * @param string $id The service identifier
  553. * @param Definition $definition A Definition instance
  554. *
  555. * @throws BadMethodCallException
  556. *
  557. * @api
  558. */
  559. public function setDefinition($id, Definition $definition)
  560. {
  561. if ($this->isFrozen()) {
  562. throw new \BadMethodCallException('Adding definition to a frozen container is not allowed');
  563. }
  564. $id = strtolower($id);
  565. unset($this->aliases[$id]);
  566. return $this->definitions[$id] = $definition;
  567. }
  568. /**
  569. * Returns true if a service definition exists under the given identifier.
  570. *
  571. * @param string $id The service identifier
  572. *
  573. * @return Boolean true if the service definition exists, false otherwise
  574. *
  575. * @api
  576. */
  577. public function hasDefinition($id)
  578. {
  579. return array_key_exists(strtolower($id), $this->definitions);
  580. }
  581. /**
  582. * Gets a service definition.
  583. *
  584. * @param string $id The service identifier
  585. *
  586. * @return Definition A Definition instance
  587. *
  588. * @throws \InvalidArgumentException if the service definition does not exist
  589. *
  590. * @api
  591. */
  592. public function getDefinition($id)
  593. {
  594. $id = strtolower($id);
  595. if (!$this->hasDefinition($id)) {
  596. throw new \InvalidArgumentException(sprintf('The service definition "%s" does not exist.', $id));
  597. }
  598. return $this->definitions[$id];
  599. }
  600. /**
  601. * Gets a service definition by id or alias.
  602. *
  603. * The method "unaliases" recursively to return a Definition instance.
  604. *
  605. * @param string $id The service identifier or alias
  606. *
  607. * @return Definition A Definition instance
  608. *
  609. * @throws \InvalidArgumentException if the service definition does not exist
  610. *
  611. * @api
  612. */
  613. public function findDefinition($id)
  614. {
  615. while ($this->hasAlias($id)) {
  616. $id = (string) $this->getAlias($id);
  617. }
  618. return $this->getDefinition($id);
  619. }
  620. /**
  621. * Creates a service for a service definition.
  622. *
  623. * @param Definition $definition A service definition instance
  624. * @param string $id The service identifier
  625. *
  626. * @return object The service described by the service definition
  627. *
  628. * @throws \InvalidArgumentException When configure callable is not callable
  629. */
  630. private function createService(Definition $definition, $id)
  631. {
  632. if (null !== $definition->getFile()) {
  633. require_once $this->getParameterBag()->resolveValue($definition->getFile());
  634. }
  635. $arguments = $this->resolveServices($this->getParameterBag()->resolveValue($definition->getArguments()));
  636. if (null !== $definition->getFactoryMethod()) {
  637. if (null !== $definition->getFactoryClass()) {
  638. $factory = $this->getParameterBag()->resolveValue($definition->getFactoryClass());
  639. } elseif (null !== $definition->getFactoryService()) {
  640. $factory = $this->get($this->getParameterBag()->resolveValue($definition->getFactoryService()));
  641. } else {
  642. throw new \RuntimeException('Cannot create service from factory method without a factory service or factory class.');
  643. }
  644. $service = call_user_func_array(array($factory, $definition->getFactoryMethod()), $arguments);
  645. } else {
  646. $r = new \ReflectionClass($this->getParameterBag()->resolveValue($definition->getClass()));
  647. $service = null === $r->getConstructor() ? $r->newInstance() : $r->newInstanceArgs($arguments);
  648. }
  649. if (self::SCOPE_PROTOTYPE !== $scope = $definition->getScope()) {
  650. if (self::SCOPE_CONTAINER !== $scope && !isset($this->scopedServices[$scope])) {
  651. throw new \RuntimeException('You tried to create a service of an inactive scope.');
  652. }
  653. $this->services[$lowerId = strtolower($id)] = $service;
  654. if (self::SCOPE_CONTAINER !== $scope) {
  655. $this->scopedServices[$scope][$lowerId] = $service;
  656. }
  657. }
  658. foreach ($definition->getMethodCalls() as $call) {
  659. $services = self::getServiceConditionals($call[1]);
  660. $ok = true;
  661. foreach ($services as $s) {
  662. if (!$this->has($s)) {
  663. $ok = false;
  664. break;
  665. }
  666. }
  667. if ($ok) {
  668. call_user_func_array(array($service, $call[0]), $this->resolveServices($this->getParameterBag()->resolveValue($call[1])));
  669. }
  670. }
  671. $properties = $this->resolveServices($this->getParameterBag()->resolveValue($definition->getProperties()));
  672. foreach ($properties as $name => $value) {
  673. $service->$name = $value;
  674. }
  675. if ($callable = $definition->getConfigurator()) {
  676. if (is_array($callable) && is_object($callable[0]) && $callable[0] instanceof Reference) {
  677. $callable[0] = $this->get((string) $callable[0]);
  678. } elseif (is_array($callable)) {
  679. $callable[0] = $this->getParameterBag()->resolveValue($callable[0]);
  680. }
  681. if (!is_callable($callable)) {
  682. throw new \InvalidArgumentException(sprintf('The configure callable for class "%s" is not a callable.', get_class($service)));
  683. }
  684. call_user_func($callable, $service);
  685. }
  686. return $service;
  687. }
  688. /**
  689. * Replaces service references by the real service instance.
  690. *
  691. * @param mixed $value A value
  692. *
  693. * @return mixed The same value with all service references replaced by the real service instances
  694. */
  695. public function resolveServices($value)
  696. {
  697. if (is_array($value)) {
  698. foreach ($value as &$v) {
  699. $v = $this->resolveServices($v);
  700. }
  701. } elseif (is_object($value) && $value instanceof Reference) {
  702. $value = $this->get((string) $value, $value->getInvalidBehavior());
  703. } elseif (is_object($value) && $value instanceof Definition) {
  704. $value = $this->createService($value, null);
  705. }
  706. return $value;
  707. }
  708. /**
  709. * Returns service ids for a given tag.
  710. *
  711. * @param string $name The tag name
  712. *
  713. * @return array An array of tags
  714. *
  715. * @api
  716. */
  717. public function findTaggedServiceIds($name)
  718. {
  719. $tags = array();
  720. foreach ($this->getDefinitions() as $id => $definition) {
  721. if ($definition->getTag($name)) {
  722. $tags[$id] = $definition->getTag($name);
  723. }
  724. }
  725. return $tags;
  726. }
  727. /**
  728. * Returns the Service Conditionals.
  729. *
  730. * @param mixed $value An array of conditionals to return.
  731. *
  732. * @return array An array of Service conditionals
  733. */
  734. public static function getServiceConditionals($value)
  735. {
  736. $services = array();
  737. if (is_array($value)) {
  738. foreach ($value as $v) {
  739. $services = array_unique(array_merge($services, self::getServiceConditionals($v)));
  740. }
  741. } elseif (is_object($value) && $value instanceof Reference && $value->getInvalidBehavior() === ContainerInterface::IGNORE_ON_INVALID_REFERENCE) {
  742. $services[] = (string) $value;
  743. }
  744. return $services;
  745. }
  746. }