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\ChildDefinition;
15use Symfony\Component\DependencyInjection\Definition;
16use Symfony\Component\DependencyInjection\Exception\ExceptionInterface;
17use Symfony\Component\DependencyInjection\Exception\RuntimeException;
18use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
19
20/**
21 * This replaces all ChildDefinition instances with their equivalent fully
22 * merged Definition instance.
23 *
24 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
25 * @author Nicolas Grekas <p@tchwork.com>
26 */
27class ResolveChildDefinitionsPass extends AbstractRecursivePass
28{
29    private $currentPath;
30
31    protected function processValue($value, $isRoot = false)
32    {
33        if (!$value instanceof Definition) {
34            return parent::processValue($value, $isRoot);
35        }
36        if ($isRoot) {
37            // yes, we are specifically fetching the definition from the
38            // container to ensure we are not operating on stale data
39            $value = $this->container->getDefinition($this->currentId);
40        }
41        if ($value instanceof ChildDefinition) {
42            $this->currentPath = [];
43            $value = $this->resolveDefinition($value);
44            if ($isRoot) {
45                $this->container->setDefinition($this->currentId, $value);
46            }
47        }
48
49        return parent::processValue($value, $isRoot);
50    }
51
52    /**
53     * Resolves the definition.
54     *
55     * @return Definition
56     *
57     * @throws RuntimeException When the definition is invalid
58     */
59    private function resolveDefinition(ChildDefinition $definition)
60    {
61        try {
62            return $this->doResolveDefinition($definition);
63        } catch (ServiceCircularReferenceException $e) {
64            throw $e;
65        } catch (ExceptionInterface $e) {
66            $r = new \ReflectionProperty($e, 'message');
67            $r->setAccessible(true);
68            $r->setValue($e, sprintf('Service "%s": %s', $this->currentId, $e->getMessage()));
69
70            throw $e;
71        }
72    }
73
74    private function doResolveDefinition(ChildDefinition $definition)
75    {
76        if (!$this->container->has($parent = $definition->getParent())) {
77            throw new RuntimeException(sprintf('Parent definition "%s" does not exist.', $parent));
78        }
79
80        $searchKey = array_search($parent, $this->currentPath);
81        $this->currentPath[] = $parent;
82
83        if (false !== $searchKey) {
84            throw new ServiceCircularReferenceException($parent, \array_slice($this->currentPath, $searchKey));
85        }
86
87        $parentDef = $this->container->findDefinition($parent);
88        if ($parentDef instanceof ChildDefinition) {
89            $id = $this->currentId;
90            $this->currentId = $parent;
91            $parentDef = $this->resolveDefinition($parentDef);
92            $this->container->setDefinition($parent, $parentDef);
93            $this->currentId = $id;
94        }
95
96        $this->container->log($this, sprintf('Resolving inheritance for "%s" (parent: %s).', $this->currentId, $parent));
97        $def = new Definition();
98
99        // merge in parent definition
100        // purposely ignored attributes: abstract, shared, tags, autoconfigured
101        $def->setClass($parentDef->getClass());
102        $def->setArguments($parentDef->getArguments());
103        $def->setMethodCalls($parentDef->getMethodCalls());
104        $def->setProperties($parentDef->getProperties());
105        if ($parentDef->getAutowiringTypes(false)) {
106            $def->setAutowiringTypes($parentDef->getAutowiringTypes(false));
107        }
108        if ($parentDef->isDeprecated()) {
109            $def->setDeprecated(true, $parentDef->getDeprecationMessage('%service_id%'));
110        }
111        $def->setFactory($parentDef->getFactory());
112        $def->setConfigurator($parentDef->getConfigurator());
113        $def->setFile($parentDef->getFile());
114        $def->setPublic($parentDef->isPublic());
115        $def->setLazy($parentDef->isLazy());
116        $def->setAutowired($parentDef->isAutowired());
117        $def->setChanges($parentDef->getChanges());
118
119        $def->setBindings($definition->getBindings() + $parentDef->getBindings());
120
121        // overwrite with values specified in the decorator
122        $changes = $definition->getChanges();
123        if (isset($changes['class'])) {
124            $def->setClass($definition->getClass());
125        }
126        if (isset($changes['factory'])) {
127            $def->setFactory($definition->getFactory());
128        }
129        if (isset($changes['configurator'])) {
130            $def->setConfigurator($definition->getConfigurator());
131        }
132        if (isset($changes['file'])) {
133            $def->setFile($definition->getFile());
134        }
135        if (isset($changes['public'])) {
136            $def->setPublic($definition->isPublic());
137        } else {
138            $def->setPrivate($definition->isPrivate() || $parentDef->isPrivate());
139        }
140        if (isset($changes['lazy'])) {
141            $def->setLazy($definition->isLazy());
142        }
143        if (isset($changes['deprecated'])) {
144            $def->setDeprecated($definition->isDeprecated(), $definition->getDeprecationMessage('%service_id%'));
145        }
146        if (isset($changes['autowired'])) {
147            $def->setAutowired($definition->isAutowired());
148        }
149        if (isset($changes['shared'])) {
150            $def->setShared($definition->isShared());
151        }
152        if (isset($changes['decorated_service'])) {
153            $decoratedService = $definition->getDecoratedService();
154            if (null === $decoratedService) {
155                $def->setDecoratedService($decoratedService);
156            } else {
157                $def->setDecoratedService($decoratedService[0], $decoratedService[1], $decoratedService[2]);
158            }
159        }
160
161        // merge arguments
162        foreach ($definition->getArguments() as $k => $v) {
163            if (is_numeric($k)) {
164                $def->addArgument($v);
165            } elseif (0 === strpos($k, 'index_')) {
166                $def->replaceArgument((int) substr($k, \strlen('index_')), $v);
167            } else {
168                $def->setArgument($k, $v);
169            }
170        }
171
172        // merge properties
173        foreach ($definition->getProperties() as $k => $v) {
174            $def->setProperty($k, $v);
175        }
176
177        // append method calls
178        if ($calls = $definition->getMethodCalls()) {
179            $def->setMethodCalls(array_merge($def->getMethodCalls(), $calls));
180        }
181
182        // merge autowiring types
183        foreach ($definition->getAutowiringTypes(false) as $autowiringType) {
184            $def->addAutowiringType($autowiringType);
185        }
186
187        // these attributes are always taken from the child
188        $def->setAbstract($definition->isAbstract());
189        $def->setTags($definition->getTags());
190        // autoconfigure is never taken from parent (on purpose)
191        // and it's not legal on an instanceof
192        $def->setAutoconfigured($definition->isAutoconfigured());
193
194        return $def;
195    }
196}
197
198class_alias(ResolveChildDefinitionsPass::class, ResolveDefinitionTemplatesPass::class);
199