1<?php 2 3/* 4 * This file is part of the Symfony package. 5 * 6 * (c) Fabien Potencier <fabien@symfony.com> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Symfony\Component\DependencyInjection\Compiler; 13 14use Symfony\Component\DependencyInjection\Alias; 15use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; 16use Symfony\Component\DependencyInjection\ContainerBuilder; 17use Symfony\Component\DependencyInjection\Definition; 18use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; 19use Symfony\Component\DependencyInjection\Reference; 20use Symfony\Component\DependencyInjection\ServiceLocator; 21 22/** 23 * Applies the "container.service_locator" tag by wrapping references into ServiceClosureArgument instances. 24 * 25 * @author Nicolas Grekas <p@tchwork.com> 26 */ 27final class ServiceLocatorTagPass extends AbstractRecursivePass 28{ 29 protected function processValue($value, $isRoot = false) 30 { 31 if (!$value instanceof Definition || !$value->hasTag('container.service_locator')) { 32 return parent::processValue($value, $isRoot); 33 } 34 35 if (!$value->getClass()) { 36 $value->setClass(ServiceLocator::class); 37 } 38 39 $arguments = $value->getArguments(); 40 if (!isset($arguments[0]) || !\is_array($arguments[0])) { 41 throw new InvalidArgumentException(sprintf('Invalid definition for service "%s": an array of references is expected as first argument when the "container.service_locator" tag is set.', $this->currentId)); 42 } 43 44 $i = 0; 45 46 foreach ($arguments[0] as $k => $v) { 47 if ($v instanceof ServiceClosureArgument) { 48 continue; 49 } 50 if (!$v instanceof Reference) { 51 throw new InvalidArgumentException(sprintf('Invalid definition for service "%s": an array of references is expected as first argument when the "container.service_locator" tag is set, "%s" found for key "%s".', $this->currentId, \is_object($v) ? \get_class($v) : \gettype($v), $k)); 52 } 53 54 if ($i === $k) { 55 unset($arguments[0][$k]); 56 57 $k = (string) $v; 58 ++$i; 59 } elseif (\is_int($k)) { 60 $i = null; 61 } 62 $arguments[0][$k] = new ServiceClosureArgument($v); 63 } 64 ksort($arguments[0]); 65 66 $value->setArguments($arguments); 67 68 $id = 'service_locator.'.ContainerBuilder::hash($value); 69 70 if ($isRoot) { 71 if ($id !== $this->currentId) { 72 $this->container->setAlias($id, new Alias($this->currentId, false)); 73 } 74 75 return $value; 76 } 77 78 $this->container->setDefinition($id, $value->setPublic(false)); 79 80 return new Reference($id); 81 } 82 83 /** 84 * @param Reference[] $refMap 85 * @param string|null $callerId 86 * 87 * @return Reference 88 */ 89 public static function register(ContainerBuilder $container, array $refMap, $callerId = null) 90 { 91 foreach ($refMap as $id => $ref) { 92 if (!$ref instanceof Reference) { 93 throw new InvalidArgumentException(sprintf('Invalid service locator definition: only services can be referenced, "%s" found for key "%s". Inject parameter values using constructors instead.', \is_object($ref) ? \get_class($ref) : \gettype($ref), $id)); 94 } 95 $refMap[$id] = new ServiceClosureArgument($ref); 96 } 97 ksort($refMap); 98 99 $locator = (new Definition(ServiceLocator::class)) 100 ->addArgument($refMap) 101 ->setPublic(false) 102 ->addTag('container.service_locator'); 103 104 if (null !== $callerId && $container->hasDefinition($callerId)) { 105 $locator->setBindings($container->getDefinition($callerId)->getBindings()); 106 } 107 108 if (!$container->hasDefinition($id = 'service_locator.'.ContainerBuilder::hash($locator))) { 109 $container->setDefinition($id, $locator); 110 } 111 112 if (null !== $callerId) { 113 $locatorId = $id; 114 // Locators are shared when they hold the exact same list of factories; 115 // to have them specialized per consumer service, we use a cloning factory 116 // to derivate customized instances from the prototype one. 117 $container->register($id .= '.'.$callerId, ServiceLocator::class) 118 ->setPublic(false) 119 ->setFactory([new Reference($locatorId), 'withContext']) 120 ->addArgument($callerId) 121 ->addArgument(new Reference('service_container')); 122 } 123 124 return new Reference($id); 125 } 126} 127