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\Tests\Compiler;
13
14use PHPUnit\Framework\TestCase;
15use Symfony\Component\DependencyInjection\ChildDefinition;
16use Symfony\Component\DependencyInjection\Compiler\ResolveChildDefinitionsPass;
17use Symfony\Component\DependencyInjection\Compiler\ResolveDefinitionTemplatesPass;
18use Symfony\Component\DependencyInjection\ContainerBuilder;
19
20class ResolveChildDefinitionsPassTest extends TestCase
21{
22    public function testProcess()
23    {
24        $container = new ContainerBuilder();
25        $container->register('parent', 'foo')->setArguments(['moo', 'b'])->setProperty('foo', 'moo');
26        $container->setDefinition('child', new ChildDefinition('parent'))
27            ->replaceArgument(0, 'a')
28            ->setProperty('foo', 'bar')
29            ->setClass('bar')
30        ;
31
32        $this->process($container);
33
34        $def = $container->getDefinition('child');
35        $this->assertNotInstanceOf(ChildDefinition::class, $def);
36        $this->assertEquals('bar', $def->getClass());
37        $this->assertEquals(['a', 'b'], $def->getArguments());
38        $this->assertEquals(['foo' => 'bar'], $def->getProperties());
39    }
40
41    public function testProcessAppendsMethodCallsAlways()
42    {
43        $container = new ContainerBuilder();
44
45        $container
46            ->register('parent')
47            ->addMethodCall('foo', ['bar'])
48        ;
49
50        $container
51            ->setDefinition('child', new ChildDefinition('parent'))
52            ->addMethodCall('bar', ['foo'])
53        ;
54
55        $this->process($container);
56
57        $def = $container->getDefinition('child');
58        $this->assertEquals([
59            ['foo', ['bar']],
60            ['bar', ['foo']],
61        ], $def->getMethodCalls());
62    }
63
64    public function testProcessDoesNotCopyAbstract()
65    {
66        $container = new ContainerBuilder();
67
68        $container
69            ->register('parent')
70            ->setAbstract(true)
71        ;
72
73        $container
74            ->setDefinition('child', new ChildDefinition('parent'))
75        ;
76
77        $this->process($container);
78
79        $def = $container->getDefinition('child');
80        $this->assertFalse($def->isAbstract());
81    }
82
83    public function testProcessDoesNotCopyShared()
84    {
85        $container = new ContainerBuilder();
86
87        $container
88            ->register('parent')
89            ->setShared(false)
90        ;
91
92        $container
93            ->setDefinition('child', new ChildDefinition('parent'))
94        ;
95
96        $this->process($container);
97
98        $def = $container->getDefinition('child');
99        $this->assertTrue($def->isShared());
100    }
101
102    public function testProcessDoesNotCopyTags()
103    {
104        $container = new ContainerBuilder();
105
106        $container
107            ->register('parent')
108            ->addTag('foo')
109        ;
110
111        $container
112            ->setDefinition('child', new ChildDefinition('parent'))
113        ;
114
115        $this->process($container);
116
117        $def = $container->getDefinition('child');
118        $this->assertEquals([], $def->getTags());
119    }
120
121    public function testProcessDoesNotCopyDecoratedService()
122    {
123        $container = new ContainerBuilder();
124
125        $container
126            ->register('parent')
127            ->setDecoratedService('foo')
128        ;
129
130        $container
131            ->setDefinition('child', new ChildDefinition('parent'))
132        ;
133
134        $this->process($container);
135
136        $def = $container->getDefinition('child');
137        $this->assertNull($def->getDecoratedService());
138    }
139
140    public function testProcessDoesNotDropShared()
141    {
142        $container = new ContainerBuilder();
143
144        $container
145            ->register('parent')
146        ;
147
148        $container
149            ->setDefinition('child', new ChildDefinition('parent'))
150            ->setShared(false)
151        ;
152
153        $this->process($container);
154
155        $def = $container->getDefinition('child');
156        $this->assertFalse($def->isShared());
157    }
158
159    public function testProcessHandlesMultipleInheritance()
160    {
161        $container = new ContainerBuilder();
162
163        $container
164            ->register('parent', 'foo')
165            ->setArguments(['foo', 'bar', 'c'])
166        ;
167
168        $container
169            ->setDefinition('child2', new ChildDefinition('child1'))
170            ->replaceArgument(1, 'b')
171        ;
172
173        $container
174            ->setDefinition('child1', new ChildDefinition('parent'))
175            ->replaceArgument(0, 'a')
176        ;
177
178        $this->process($container);
179
180        $def = $container->getDefinition('child2');
181        $this->assertEquals(['a', 'b', 'c'], $def->getArguments());
182        $this->assertEquals('foo', $def->getClass());
183    }
184
185    public function testSetLazyOnServiceHasParent()
186    {
187        $container = new ContainerBuilder();
188
189        $container->register('parent', 'stdClass');
190
191        $container->setDefinition('child1', new ChildDefinition('parent'))
192            ->setLazy(true)
193        ;
194
195        $this->process($container);
196
197        $this->assertTrue($container->getDefinition('child1')->isLazy());
198    }
199
200    public function testSetLazyOnServiceIsParent()
201    {
202        $container = new ContainerBuilder();
203
204        $container->register('parent', 'stdClass')
205            ->setLazy(true)
206        ;
207
208        $container->setDefinition('child1', new ChildDefinition('parent'));
209
210        $this->process($container);
211
212        $this->assertTrue($container->getDefinition('child1')->isLazy());
213    }
214
215    public function testSetAutowiredOnServiceHasParent()
216    {
217        $container = new ContainerBuilder();
218
219        $container->register('parent', 'stdClass')
220            ->setAutowired(true)
221        ;
222
223        $container->setDefinition('child1', new ChildDefinition('parent'))
224            ->setAutowired(false)
225        ;
226
227        $this->process($container);
228
229        $this->assertFalse($container->getDefinition('child1')->isAutowired());
230    }
231
232    public function testSetAutowiredOnServiceIsParent()
233    {
234        $container = new ContainerBuilder();
235
236        $container->register('parent', 'stdClass')
237            ->setAutowired(true)
238        ;
239
240        $container->setDefinition('child1', new ChildDefinition('parent'));
241
242        $this->process($container);
243
244        $this->assertTrue($container->getDefinition('child1')->isAutowired());
245    }
246
247    public function testDeepDefinitionsResolving()
248    {
249        $container = new ContainerBuilder();
250
251        $container->register('parent', 'parentClass');
252        $container->register('sibling', 'siblingClass')
253            ->setConfigurator(new ChildDefinition('parent'), 'foo')
254            ->setFactory([new ChildDefinition('parent'), 'foo'])
255            ->addArgument(new ChildDefinition('parent'))
256            ->setProperty('prop', new ChildDefinition('parent'))
257            ->addMethodCall('meth', [new ChildDefinition('parent')])
258        ;
259
260        $this->process($container);
261
262        $configurator = $container->getDefinition('sibling')->getConfigurator();
263        $this->assertSame('Symfony\Component\DependencyInjection\Definition', \get_class($configurator));
264        $this->assertSame('parentClass', $configurator->getClass());
265
266        $factory = $container->getDefinition('sibling')->getFactory();
267        $this->assertSame('Symfony\Component\DependencyInjection\Definition', \get_class($factory[0]));
268        $this->assertSame('parentClass', $factory[0]->getClass());
269
270        $argument = $container->getDefinition('sibling')->getArgument(0);
271        $this->assertSame('Symfony\Component\DependencyInjection\Definition', \get_class($argument));
272        $this->assertSame('parentClass', $argument->getClass());
273
274        $properties = $container->getDefinition('sibling')->getProperties();
275        $this->assertSame('Symfony\Component\DependencyInjection\Definition', \get_class($properties['prop']));
276        $this->assertSame('parentClass', $properties['prop']->getClass());
277
278        $methodCalls = $container->getDefinition('sibling')->getMethodCalls();
279        $this->assertSame('Symfony\Component\DependencyInjection\Definition', \get_class($methodCalls[0][1][0]));
280        $this->assertSame('parentClass', $methodCalls[0][1][0]->getClass());
281    }
282
283    public function testSetDecoratedServiceOnServiceHasParent()
284    {
285        $container = new ContainerBuilder();
286
287        $container->register('parent', 'stdClass');
288
289        $container->setDefinition('child1', new ChildDefinition('parent'))
290            ->setDecoratedService('foo', 'foo_inner', 5)
291        ;
292
293        $this->process($container);
294
295        $this->assertEquals(['foo', 'foo_inner', 5], $container->getDefinition('child1')->getDecoratedService());
296    }
297
298    public function testDecoratedServiceCopiesDeprecatedStatusFromParent()
299    {
300        $container = new ContainerBuilder();
301        $container->register('deprecated_parent')
302            ->setDeprecated(true)
303        ;
304
305        $container->setDefinition('decorated_deprecated_parent', new ChildDefinition('deprecated_parent'));
306
307        $this->process($container);
308
309        $this->assertTrue($container->getDefinition('decorated_deprecated_parent')->isDeprecated());
310    }
311
312    public function testDecoratedServiceCanOverwriteDeprecatedParentStatus()
313    {
314        $container = new ContainerBuilder();
315        $container->register('deprecated_parent')
316            ->setDeprecated(true)
317        ;
318
319        $container->setDefinition('decorated_deprecated_parent', new ChildDefinition('deprecated_parent'))
320            ->setDeprecated(false)
321        ;
322
323        $this->process($container);
324
325        $this->assertFalse($container->getDefinition('decorated_deprecated_parent')->isDeprecated());
326    }
327
328    /**
329     * @group legacy
330     */
331    public function testProcessMergeAutowiringTypes()
332    {
333        $container = new ContainerBuilder();
334
335        $container
336            ->register('parent')
337            ->addAutowiringType('Foo')
338        ;
339
340        $container
341            ->setDefinition('child', new ChildDefinition('parent'))
342            ->addAutowiringType('Bar')
343        ;
344
345        $this->process($container);
346
347        $childDef = $container->getDefinition('child');
348        $this->assertEquals(['Foo', 'Bar'], $childDef->getAutowiringTypes());
349
350        $parentDef = $container->getDefinition('parent');
351        $this->assertSame(['Foo'], $parentDef->getAutowiringTypes());
352    }
353
354    public function testProcessResolvesAliases()
355    {
356        $container = new ContainerBuilder();
357
358        $container->register('parent', 'ParentClass');
359        $container->setAlias('parent_alias', 'parent');
360        $container->setDefinition('child', new ChildDefinition('parent_alias'));
361
362        $this->process($container);
363
364        $def = $container->getDefinition('child');
365        $this->assertSame('ParentClass', $def->getClass());
366    }
367
368    public function testProcessSetsArguments()
369    {
370        $container = new ContainerBuilder();
371
372        $container->register('parent', 'ParentClass')->setArguments([0]);
373        $container->setDefinition('child', (new ChildDefinition('parent'))->setArguments([
374            1,
375            'index_0' => 2,
376            'foo' => 3,
377        ]));
378
379        $this->process($container);
380
381        $def = $container->getDefinition('child');
382        $this->assertSame([2, 1, 'foo' => 3], $def->getArguments());
383    }
384
385    public function testBindings()
386    {
387        $container = new ContainerBuilder();
388
389        $container->register('parent', 'stdClass')
390            ->setBindings(['a' => '1', 'b' => '2'])
391        ;
392
393        $container->setDefinition('child', new ChildDefinition('parent'))
394            ->setBindings(['b' => 'B', 'c' => 'C'])
395        ;
396
397        $this->process($container);
398
399        $bindings = [];
400        foreach ($container->getDefinition('child')->getBindings() as $k => $v) {
401            $bindings[$k] = $v->getValues()[0];
402        }
403        $this->assertEquals(['b' => 'B', 'c' => 'C', 'a' => '1'], $bindings);
404    }
405
406    public function testSetAutoconfiguredOnServiceIsParent()
407    {
408        $container = new ContainerBuilder();
409
410        $container->register('parent', 'stdClass')
411            ->setAutoconfigured(true)
412        ;
413
414        $container->setDefinition('child1', new ChildDefinition('parent'));
415
416        $this->process($container);
417
418        $this->assertFalse($container->getDefinition('child1')->isAutoconfigured());
419    }
420
421    /**
422     * @group legacy
423     */
424    public function testAliasExistsForBackwardsCompatibility()
425    {
426        $this->assertInstanceOf(ResolveChildDefinitionsPass::class, new ResolveDefinitionTemplatesPass());
427    }
428
429    protected function process(ContainerBuilder $container)
430    {
431        $pass = new ResolveChildDefinitionsPass();
432        $pass->process($container);
433    }
434
435    public function testProcessDetectsChildDefinitionIndirectCircularReference()
436    {
437        $this->expectException('Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException');
438        $this->expectExceptionMessageMatches('/^Circular reference detected for service "c", path: "c -> b -> a -> c"./');
439        $container = new ContainerBuilder();
440
441        $container->register('a');
442
443        $container->setDefinition('b', new ChildDefinition('a'));
444        $container->setDefinition('c', new ChildDefinition('b'));
445        $container->setDefinition('a', new ChildDefinition('c'));
446
447        $this->process($container);
448    }
449}
450