[FEATURE] Add symfony dependency injection for core and extbase
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / Unit / DependencyInjection / ServiceProviderCompilationPassTest.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Core\Tests\Unit\DependencyInjection;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use Prophecy\Argument;
19 use Psr\Container\ContainerInterface;
20 use Psr\Log\NullLogger;
21 use Symfony\Component\DependencyInjection\ContainerBuilder;
22 use Symfony\Component\DependencyInjection\Definition;
23 use TYPO3\CMS\Core\DependencyInjection\ServiceProviderCompilationPass;
24 use TYPO3\CMS\Core\DependencyInjection\ServiceProviderInterface;
25 use TYPO3\CMS\Core\DependencyInjection\ServiceProviderRegistry;
26 use TYPO3\CMS\Core\Tests\Unit\DependencyInjection\Fixtures\TestServiceProvider;
27 use TYPO3\CMS\Core\Tests\Unit\DependencyInjection\Fixtures\TestServiceProviderFactoryOverride;
28 use TYPO3\CMS\Core\Tests\Unit\DependencyInjection\Fixtures\TestServiceProviderOverride;
29 use TYPO3\CMS\Core\Tests\Unit\DependencyInjection\Fixtures\TestServiceProviderOverride2;
30 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
31
32 class ServiceProviderCompilationPassTest extends UnitTestCase
33 {
34 protected function getServiceProviderRegistry(array $serviceProviders)
35 {
36 $serviceProviderRegistryProphecy = $this->prophesize(ServiceProviderRegistry::class);
37 $serviceProviderRegistryProphecy->getIterator()->will(function () use ($serviceProviders): \Generator {
38 foreach ($serviceProviders as $id => $serviceProvider) {
39 yield (string)$id => new $serviceProvider;
40 }
41 });
42
43 foreach ($serviceProviders as $id => $serviceProvider) {
44 $packageKey = (string)$id;
45
46 $instance = new $serviceProvider;
47 $factories = $instance->getFactories();
48 $extensions = $instance->getExtensions();
49
50 $serviceProviderRegistryProphecy->getFactories($id)->willReturn($factories);
51 $serviceProviderRegistryProphecy->getExtensions($id)->willReturn($extensions);
52
53 foreach ($factories as $serviceName => $factory) {
54 $serviceProviderRegistryProphecy->createService($packageKey, $serviceName, Argument::type(ContainerInterface::class))->will(function ($args) use ($factory) {
55 return $factory($args[2]);
56 });
57 }
58 foreach ($extensions as $serviceName => $extension) {
59 $serviceProviderRegistryProphecy->extendService($packageKey, $serviceName, Argument::type(ContainerInterface::class), Argument::cetera())->will(function ($args) use ($extension) {
60 return $extension($args[2], $args[3] ?? null);
61 });
62 }
63 }
64
65 return $serviceProviderRegistryProphecy->reveal();
66 }
67
68 protected function getContainer(array $serviceProviders, callable $configure = null)
69 {
70 static $id = 0;
71
72 $registry = $this->getServiceProviderRegistry($serviceProviders);
73 $registryServiceName = 'service_provider_registry_' . ++$id;
74
75 $container = new ContainerBuilder();
76 if ($configure !== null) {
77 $configure($container);
78 }
79 $logger = new Definition(NullLogger::class);
80 $logger->setPublic(true);
81 $container->setDefinition('logger', $logger);
82
83 $container->addCompilerPass(new ServiceProviderCompilationPass($registry, $registryServiceName));
84 $container->compile();
85 $container->set($registryServiceName, $registry);
86
87 return $container;
88 }
89
90 public function testSimpleServiceProvider()
91 {
92 $container = $this->getContainer([
93 TestServiceProvider::class
94 ]);
95
96 $serviceA = $container->get('serviceA');
97 $serviceD = $container->get('serviceD');
98
99 $this->assertInstanceOf(\stdClass::class, $serviceA);
100 $this->assertInstanceOf(\stdClass::class, $serviceD);
101 $this->assertEquals(42, $container->get('function'));
102 }
103
104 public function testServiceProviderOverrides()
105 {
106 $container = $this->getContainer([
107 TestServiceProvider::class,
108 TestServiceProviderOverride::class,
109 TestServiceProviderOverride2::class
110 ]);
111
112 $serviceA = $container->get('serviceA');
113 $serviceC = $container->get('serviceC');
114
115 $this->assertInstanceOf(\stdClass::class, $serviceA);
116 $this->assertEquals('foo', $serviceA->newProperty);
117 $this->assertEquals('bar', $serviceA->newProperty2);
118 $this->assertEquals('localhost', $serviceC->serviceB->parameter);
119 }
120
121 public function testServiceProviderFactoryOverrides()
122 {
123 $container = $this->getContainer([
124 TestServiceProvider::class,
125 TestServiceProviderFactoryOverride::class,
126 ]);
127
128 $serviceA = $container->get('serviceA');
129
130 $this->assertInstanceOf(\stdClass::class, $serviceA);
131 $this->assertEquals('remotehost', $serviceA->serviceB->parameter);
132 }
133
134 public function testServiceProviderFactoryOverridesForSymfonyDefinedServices()
135 {
136 $container = $this->getContainer(
137 [
138 TestServiceProvider::class,
139 TestServiceProviderFactoryOverride::class,
140 ],
141 function (ContainerBuilder $container) {
142 $definition = new \Symfony\Component\DependencyInjection\Definition('stdClass');
143 // property should be overriden by service provider
144 $definition->setProperty('parameter', 'remotehost');
145 // property should not be "deleted" by service provider
146 $definition->setProperty('symfony_defined_parameter', 'foobar');
147 $container->setDefinition('serviceB', $definition);
148 }
149 );
150
151 $serviceA = $container->get('serviceA');
152
153 $this->assertInstanceOf(\stdClass::class, $serviceA);
154 $this->assertEquals('remotehost', $serviceA->serviceB->parameter);
155 $this->assertEquals('foobar', $serviceA->serviceB->symfony_defined_parameter);
156 }
157
158 public function testServiceProviderFactoryOverrideResetsAutowiring()
159 {
160 $container = $this->getContainer(
161 [
162 TestServiceProvider::class,
163 TestServiceProviderFactoryOverride::class,
164 ],
165 function (ContainerBuilder $container) {
166 $definition = new \Symfony\Component\DependencyInjection\Definition('stdClass');
167 // property should be overriden by service provider
168 $definition->setProperty('parameter', 'remotehost');
169 // property should not be "deleted" by service provider
170 $definition->setProperty('symfony_defined_parameter', 'foobar');
171 $definition->setAutowired(true);
172 $container->setDefinition('serviceB', $definition);
173 }
174 );
175
176 $serviceA = $container->get('serviceA');
177
178 $this->assertInstanceOf(\stdClass::class, $serviceA);
179 $this->assertEquals('remotehost', $serviceA->serviceB->parameter);
180 $this->assertEquals('foobar', $serviceA->serviceB->symfony_defined_parameter);
181 $this->assertFalse($container->getDefinition('serviceB')->isAutowired());
182 }
183
184 public function testExceptionForNonNullableExtensionArgument()
185 {
186 $this->expectException(\Exception::class);
187 $this->expectExceptionMessage('A registered extension for the service "serviceA" requires the service to be available, which is missing.');
188
189 $container = $this->getContainer([
190 TestServiceProviderOverride::class,
191 ]);
192 }
193
194 public function testExceptionForInvalidFactories()
195 {
196 $this->expectException(\TypeError::class);
197
198 $registry = new ServiceProviderRegistry([
199 new class implements ServiceProviderInterface {
200 public function getFactories()
201 {
202 return [
203 'invalid' => 2
204 ];
205 }
206 public function getExtensions()
207 {
208 return [];
209 }
210 }
211 ]);
212 $container = new ContainerBuilder();
213 $registryServiceName = 'service_provider_registry_test';
214 $container->addCompilerPass(new ServiceProviderCompilationPass($registry, $registryServiceName));
215 $container->compile();
216 }
217 }