|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
declare(strict_types=1); |
|
4
|
|
|
|
|
5
|
|
|
namespace FriendsOfBehat\SymfonyExtension\ServiceContainer; |
|
6
|
|
|
|
|
7
|
|
|
use Behat\MinkExtension\ServiceContainer\MinkExtension; |
|
8
|
|
|
use Behat\Testwork\EventDispatcher\ServiceContainer\EventDispatcherExtension; |
|
9
|
|
|
use Behat\Testwork\ServiceContainer\Extension; |
|
10
|
|
|
use Behat\Testwork\ServiceContainer\ExtensionManager; |
|
11
|
|
|
use FriendsOfBehat\CrossContainerExtension\CrossContainerProcessor; |
|
12
|
|
|
use FriendsOfBehat\CrossContainerExtension\KernelBasedContainerAccessor; |
|
13
|
|
|
use FriendsOfBehat\CrossContainerExtension\ServiceContainer\CrossContainerExtension; |
|
14
|
|
|
use FriendsOfBehat\SymfonyExtension\Driver\Factory\SymfonyDriverFactory; |
|
15
|
|
|
use FriendsOfBehat\SymfonyExtension\Listener\KernelRebooter; |
|
16
|
|
|
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; |
|
17
|
|
|
use Symfony\Component\DependencyInjection\Container; |
|
18
|
|
|
use Symfony\Component\DependencyInjection\ContainerBuilder; |
|
19
|
|
|
use Symfony\Component\DependencyInjection\Definition; |
|
20
|
|
|
use Symfony\Component\DependencyInjection\Reference; |
|
21
|
|
|
use Symfony\Component\Dotenv\Dotenv; |
|
22
|
|
|
use Symfony\Component\HttpKernel\KernelInterface; |
|
23
|
|
|
|
|
24
|
|
|
final class SymfonyExtension implements Extension |
|
25
|
|
|
{ |
|
26
|
|
|
/** |
|
27
|
|
|
* Kernel used inside Behat contexts or to create services injected to them. |
|
28
|
|
|
* Container is built before every scenario. |
|
29
|
|
|
*/ |
|
30
|
|
|
const KERNEL_ID = 'sylius_symfony_extension.kernel'; |
|
31
|
|
|
|
|
32
|
|
|
/** |
|
33
|
|
|
* The current container used in scenario contexts. |
|
34
|
|
|
* To be used as a factory for current injected application services. |
|
35
|
|
|
*/ |
|
36
|
|
|
const KERNEL_CONTAINER_ID = 'sylius_symfony_extension.kernel.container'; |
|
37
|
|
|
|
|
38
|
|
|
/** |
|
39
|
|
|
* Kernel used by Symfony2 driver to isolate web container from contexts' container. |
|
40
|
|
|
* Container is built before every request. |
|
41
|
|
|
*/ |
|
42
|
|
|
const DRIVER_KERNEL_ID = 'sylius_symfony_extension.driver_kernel'; |
|
43
|
|
|
|
|
44
|
|
|
/** |
|
45
|
|
|
* Kernel that should be used by extensions only. |
|
46
|
|
|
* Container is built only once at the first use. |
|
47
|
|
|
*/ |
|
48
|
|
|
const SHARED_KERNEL_ID = 'sylius_symfony_extension.shared_kernel'; |
|
49
|
|
|
|
|
50
|
|
|
/** |
|
51
|
|
|
* The only container built by shared kernel. |
|
52
|
|
|
* To be used as a factory for shared injected application services. |
|
53
|
|
|
*/ |
|
54
|
|
|
const SHARED_KERNEL_CONTAINER_ID = 'sylius_symfony_extension.shared_kernel.container'; |
|
55
|
|
|
|
|
56
|
|
|
/** |
|
57
|
|
|
* Default symfony environment used to run your suites. |
|
58
|
|
|
*/ |
|
59
|
|
|
private const DEFAULT_ENV = 'test'; |
|
60
|
|
|
|
|
61
|
|
|
/** |
|
62
|
|
|
* Enable or disable the debug mode |
|
63
|
|
|
*/ |
|
64
|
|
|
private const DEFAULT_DEBUG_MODE = true; |
|
65
|
|
|
|
|
66
|
|
|
/** |
|
67
|
|
|
* Default Symfony configuration |
|
68
|
|
|
*/ |
|
69
|
|
|
private const SYMFONY_DEFAULTS = [ |
|
70
|
|
|
'env_file' => null, |
|
71
|
|
|
'kernel' => [ |
|
72
|
|
|
'bootstrap' => 'app/autoload.php', |
|
73
|
|
|
'path' => 'app/AppKernel.php', |
|
74
|
|
|
'class' => 'AppKernel', |
|
75
|
|
|
'env' => self::DEFAULT_ENV, |
|
76
|
|
|
'debug' => self::DEFAULT_DEBUG_MODE |
|
77
|
|
|
] |
|
78
|
|
|
]; |
|
79
|
|
|
|
|
80
|
|
|
/** |
|
81
|
|
|
* Default Symfony 4 configuration |
|
82
|
|
|
*/ |
|
83
|
|
|
private const SYMFONY_4_DEFAULTS = [ |
|
84
|
|
|
'env_file' => '.env', |
|
85
|
|
|
'kernel' => [ |
|
86
|
|
|
'bootstrap' => null, |
|
87
|
|
|
'path' => 'src/Kernel.php', |
|
88
|
|
|
'class' => 'App\Kernel', |
|
89
|
|
|
'env' => self::DEFAULT_ENV, |
|
90
|
|
|
'debug' => self::DEFAULT_DEBUG_MODE |
|
91
|
|
|
] |
|
92
|
|
|
]; |
|
93
|
|
|
|
|
94
|
|
|
/** |
|
95
|
|
|
* @var CrossContainerProcessor|null |
|
96
|
|
|
*/ |
|
97
|
|
|
private $crossContainerProcessor; |
|
98
|
|
|
|
|
99
|
|
|
/** |
|
100
|
|
|
* {@inheritdoc} |
|
101
|
|
|
*/ |
|
102
|
|
|
public function getConfigKey(): string |
|
103
|
|
|
{ |
|
104
|
|
|
return 'fob_symfony'; |
|
105
|
|
|
} |
|
106
|
|
|
|
|
107
|
|
|
/** |
|
108
|
|
|
* {@inheritdoc} |
|
109
|
|
|
*/ |
|
110
|
|
|
public function initialize(ExtensionManager $extensionManager): void |
|
111
|
|
|
{ |
|
112
|
|
|
$this->registerSymfonyDriverFactory($extensionManager); |
|
113
|
|
|
$this->initializeCrossContainerProcessor($extensionManager); |
|
114
|
|
|
} |
|
115
|
|
|
|
|
116
|
|
|
/** |
|
117
|
|
|
* {@inheritdoc} |
|
118
|
|
|
*/ |
|
119
|
|
|
public function configure(ArrayNodeDefinition $builder): void |
|
120
|
|
|
{ |
|
121
|
|
|
$builder |
|
122
|
|
|
->children() |
|
123
|
|
|
->scalarNode('env_file')->end() |
|
124
|
|
|
->arrayNode('kernel') |
|
125
|
|
|
->addDefaultsIfNotSet() |
|
126
|
|
|
->children() |
|
127
|
|
|
->scalarNode('bootstrap')->defaultFalse()->end() |
|
128
|
|
|
->scalarNode('path')->end() |
|
129
|
|
|
->scalarNode('class')->end() |
|
130
|
|
|
->scalarNode('env')->end() |
|
131
|
|
|
->booleanNode('debug')->end() |
|
132
|
|
|
->end() |
|
133
|
|
|
->end() |
|
134
|
|
|
->end() |
|
135
|
|
|
; |
|
136
|
|
|
} |
|
137
|
|
|
|
|
138
|
|
|
/** |
|
139
|
|
|
* {@inheritdoc} |
|
140
|
|
|
*/ |
|
141
|
|
|
public function load(ContainerBuilder $container, array $config): void |
|
142
|
|
|
{ |
|
143
|
|
|
$config = $this->autoconfigure($container, $config); |
|
144
|
|
|
|
|
145
|
|
|
$this->loadKernel($container, $config['kernel']); |
|
146
|
|
|
$this->loadKernelContainer($container); |
|
147
|
|
|
|
|
148
|
|
|
$this->loadDriverKernel($container); |
|
149
|
|
|
|
|
150
|
|
|
$this->loadSharedKernel($container); |
|
151
|
|
|
$this->loadSharedKernelContainer($container); |
|
152
|
|
|
|
|
153
|
|
|
$this->loadKernelRebooter($container); |
|
154
|
|
|
$this->declareSymfonyContainers($container); |
|
155
|
|
|
} |
|
156
|
|
|
|
|
157
|
|
|
/** |
|
158
|
|
|
* {@inheritdoc} |
|
159
|
|
|
*/ |
|
160
|
|
|
public function process(ContainerBuilder $container): void |
|
161
|
|
|
{ |
|
162
|
|
|
} |
|
163
|
|
|
|
|
164
|
|
|
/** |
|
165
|
|
|
* @param ContainerBuilder $container |
|
166
|
|
|
* @param array $userConfig |
|
167
|
|
|
* @return array |
|
168
|
|
|
*/ |
|
169
|
|
|
private function autoconfigure(ContainerBuilder $container, array $userConfig): array |
|
170
|
|
|
{ |
|
171
|
|
|
$defaults = self::SYMFONY_DEFAULTS; |
|
172
|
|
|
|
|
173
|
|
|
$symfonyFourKernelPath = sprintf('%s/%s', $container->getParameter('paths.base'), self::SYMFONY_4_DEFAULTS['kernel']['path']); |
|
174
|
|
|
if ($userConfig['kernel']['bootstrap'] === null || file_exists($symfonyFourKernelPath)) { |
|
175
|
|
|
$defaults = self::SYMFONY_4_DEFAULTS; |
|
176
|
|
|
} |
|
177
|
|
|
|
|
178
|
|
|
$userConfig['kernel']['bootstrap'] = $userConfig['kernel']['bootstrap'] === false ? null : $userConfig['kernel']['bootstrap']; |
|
179
|
|
|
|
|
180
|
|
|
$config = array_replace_recursive($defaults, $userConfig); |
|
181
|
|
|
|
|
182
|
|
|
if (null !== $config['env_file']) { |
|
183
|
|
|
$this->loadEnvVars($container, $config['env_file']); |
|
184
|
|
|
|
|
185
|
|
|
if (!isset($userConfig['kernel']['env']) && false !== getenv('APP_ENV')) { |
|
186
|
|
|
$config['kernel']['env'] = getenv('APP_ENV'); |
|
187
|
|
|
} |
|
188
|
|
|
|
|
189
|
|
|
if (!isset($userConfig['kernel']['debug']) && false !== getenv('APP_DEBUG')) { |
|
190
|
|
|
$config['kernel']['debug'] = getenv('APP_DEBUG'); |
|
191
|
|
|
} |
|
192
|
|
|
} |
|
193
|
|
|
|
|
194
|
|
|
return $config; |
|
195
|
|
|
} |
|
196
|
|
|
|
|
197
|
|
|
/** |
|
198
|
|
|
* @param ContainerBuilder $container |
|
199
|
|
|
* @param string $fileName |
|
200
|
|
|
*/ |
|
201
|
|
|
private function loadEnvVars(ContainerBuilder $container, string $fileName): void |
|
202
|
|
|
{ |
|
203
|
|
|
$envFilePath = sprintf('%s/%s', $container->getParameter('paths.base'), $fileName); |
|
204
|
|
|
(new Dotenv())->load($envFilePath); |
|
205
|
|
|
} |
|
206
|
|
|
|
|
207
|
|
|
/** |
|
208
|
|
|
* @param ContainerBuilder $container |
|
209
|
|
|
*/ |
|
210
|
|
|
private function loadKernel(ContainerBuilder $container, array $config): void |
|
211
|
|
|
{ |
|
212
|
|
|
$definition = new Definition($config['class'], [ |
|
213
|
|
|
$config['env'], |
|
214
|
|
|
$config['debug'], |
|
215
|
|
|
]); |
|
216
|
|
|
$definition->addMethodCall('boot'); |
|
217
|
|
|
$definition->setPublic(true); |
|
218
|
|
|
|
|
219
|
|
|
$file = $this->getKernelFile($container->getParameter('paths.base'), $config['path']); |
|
220
|
|
|
if (null !== $file) { |
|
221
|
|
|
$definition->setFile($file); |
|
222
|
|
|
} |
|
223
|
|
|
|
|
224
|
|
|
$container->setDefinition(self::KERNEL_ID, $definition); |
|
225
|
|
|
|
|
226
|
|
|
$this->requireKernelBootstrapFile($container->getParameter('paths.base'), $config['bootstrap']); |
|
227
|
|
|
} |
|
228
|
|
|
|
|
229
|
|
|
/** |
|
230
|
|
|
* @param ContainerBuilder $container |
|
231
|
|
|
*/ |
|
232
|
|
|
private function loadKernelContainer(ContainerBuilder $container): void |
|
233
|
|
|
{ |
|
234
|
|
|
$containerDefinition = new Definition(Container::class); |
|
235
|
|
|
$containerDefinition->setFactory([new Reference(self::KERNEL_ID), 'getContainer']); |
|
236
|
|
|
|
|
237
|
|
|
$container->setDefinition(self::KERNEL_CONTAINER_ID, $containerDefinition); |
|
238
|
|
|
} |
|
239
|
|
|
|
|
240
|
|
|
/** |
|
241
|
|
|
* @param ContainerBuilder $container |
|
242
|
|
|
*/ |
|
243
|
|
|
private function loadDriverKernel(ContainerBuilder $container): void |
|
244
|
|
|
{ |
|
245
|
|
|
$container->setDefinition(self::DRIVER_KERNEL_ID, $container->findDefinition(self::KERNEL_ID)); |
|
246
|
|
|
} |
|
247
|
|
|
|
|
248
|
|
|
/** |
|
249
|
|
|
* @param ContainerBuilder $container |
|
250
|
|
|
*/ |
|
251
|
|
|
private function loadSharedKernel(ContainerBuilder $container): void |
|
252
|
|
|
{ |
|
253
|
|
|
$container->setDefinition(self::SHARED_KERNEL_ID, $container->findDefinition(self::KERNEL_ID)); |
|
254
|
|
|
} |
|
255
|
|
|
|
|
256
|
|
|
/** |
|
257
|
|
|
* @param ContainerBuilder $container |
|
258
|
|
|
*/ |
|
259
|
|
|
private function loadSharedKernelContainer(ContainerBuilder $container): void |
|
260
|
|
|
{ |
|
261
|
|
|
$containerDefinition = new Definition(Container::class); |
|
262
|
|
|
$containerDefinition->setFactory([ |
|
263
|
|
|
new Reference(self::SHARED_KERNEL_ID), |
|
264
|
|
|
'getContainer', |
|
265
|
|
|
]); |
|
266
|
|
|
|
|
267
|
|
|
$container->setDefinition(self::SHARED_KERNEL_CONTAINER_ID, $containerDefinition); |
|
268
|
|
|
} |
|
269
|
|
|
|
|
270
|
|
|
/** |
|
271
|
|
|
* @param ContainerBuilder $container |
|
272
|
|
|
* |
|
273
|
|
|
* @throws \Exception |
|
274
|
|
|
*/ |
|
275
|
|
|
private function loadKernelRebooter(ContainerBuilder $container): void |
|
276
|
|
|
{ |
|
277
|
|
|
$definition = new Definition(KernelRebooter::class, [new Reference(self::KERNEL_ID)]); |
|
278
|
|
|
$definition->addTag(EventDispatcherExtension::SUBSCRIBER_TAG); |
|
279
|
|
|
|
|
280
|
|
|
$container->setDefinition(self::KERNEL_ID . '.rebooter', $definition); |
|
281
|
|
|
} |
|
282
|
|
|
|
|
283
|
|
|
/** |
|
284
|
|
|
* @param ContainerBuilder $container |
|
285
|
|
|
* |
|
286
|
|
|
* @throws \Exception |
|
287
|
|
|
*/ |
|
288
|
|
|
private function declareSymfonyContainers(ContainerBuilder $container): void |
|
289
|
|
|
{ |
|
290
|
|
|
if (null === $this->crossContainerProcessor) { |
|
291
|
|
|
return; |
|
292
|
|
|
} |
|
293
|
|
|
|
|
294
|
|
|
$containerAccessors = [ |
|
295
|
|
|
'symfony' => self::KERNEL_ID, |
|
296
|
|
|
'symfony_driver' => self::DRIVER_KERNEL_ID, |
|
297
|
|
|
'symfony_shared' => self::SHARED_KERNEL_ID, |
|
298
|
|
|
]; |
|
299
|
|
|
|
|
300
|
|
|
foreach ($containerAccessors as $containerName => $kernelIdentifier) { |
|
301
|
|
|
$kernel = $container->get($kernelIdentifier); |
|
302
|
|
|
|
|
303
|
|
|
if (!$kernel instanceof KernelInterface) { |
|
|
|
|
|
|
304
|
|
|
throw new \RuntimeException(sprintf( |
|
305
|
|
|
'Expected service "%s" to be an instance of "%s", got "%s" instead.', |
|
306
|
|
|
$kernelIdentifier, |
|
307
|
|
|
KernelInterface::class, |
|
308
|
|
|
\is_object($kernel) ? \get_class($kernel) : \gettype($kernel) |
|
309
|
|
|
)); |
|
310
|
|
|
} |
|
311
|
|
|
|
|
312
|
|
|
$this->crossContainerProcessor->addContainerAccessor($containerName, new KernelBasedContainerAccessor($kernel)); |
|
313
|
|
|
} |
|
314
|
|
|
} |
|
315
|
|
|
|
|
316
|
|
|
/** |
|
317
|
|
|
* @param ExtensionManager $extensionManager |
|
318
|
|
|
*/ |
|
319
|
|
|
private function initializeCrossContainerProcessor(ExtensionManager $extensionManager): void |
|
320
|
|
|
{ |
|
321
|
|
|
/** @var CrossContainerExtension $extension */ |
|
322
|
|
|
$extension = $extensionManager->getExtension('fob_cross_container'); |
|
323
|
|
|
if (null !== $extension) { |
|
324
|
|
|
$this->crossContainerProcessor = $extension->getCrossContainerProcessor(); |
|
325
|
|
|
} |
|
326
|
|
|
} |
|
327
|
|
|
|
|
328
|
|
|
/** |
|
329
|
|
|
* @param ExtensionManager $extensionManager |
|
330
|
|
|
*/ |
|
331
|
|
|
private function registerSymfonyDriverFactory(ExtensionManager $extensionManager): void |
|
332
|
|
|
{ |
|
333
|
|
|
/** @var MinkExtension|null $minkExtension */ |
|
334
|
|
|
$minkExtension = $extensionManager->getExtension('mink'); |
|
335
|
|
|
if (null === $minkExtension) { |
|
336
|
|
|
return; |
|
337
|
|
|
} |
|
338
|
|
|
|
|
339
|
|
|
$minkExtension->registerDriverFactory(new SymfonyDriverFactory( |
|
340
|
|
|
'symfony', |
|
341
|
|
|
new Reference(self::DRIVER_KERNEL_ID) |
|
342
|
|
|
)); |
|
343
|
|
|
} |
|
344
|
|
|
|
|
345
|
|
|
/** |
|
346
|
|
|
* @param string $basePath |
|
347
|
|
|
* @param string $kernelPath |
|
348
|
|
|
* |
|
349
|
|
|
* @return string|null |
|
350
|
|
|
*/ |
|
351
|
|
|
private function getKernelFile(string $basePath, string $kernelPath): ?string |
|
352
|
|
|
{ |
|
353
|
|
|
$possibleFiles = [ |
|
354
|
|
|
sprintf('%s/%s', $basePath, $kernelPath), |
|
355
|
|
|
$kernelPath, |
|
356
|
|
|
]; |
|
357
|
|
|
|
|
358
|
|
|
foreach ($possibleFiles as $possibleFile) { |
|
359
|
|
|
if (file_exists($possibleFile)) { |
|
360
|
|
|
return $possibleFile; |
|
361
|
|
|
} |
|
362
|
|
|
} |
|
363
|
|
|
|
|
364
|
|
|
return null; |
|
365
|
|
|
} |
|
366
|
|
|
|
|
367
|
|
|
/** |
|
368
|
|
|
* @param string $basePath |
|
369
|
|
|
* @param string|null $bootstrapPath |
|
370
|
|
|
* |
|
371
|
|
|
* @throws \DomainException |
|
372
|
|
|
*/ |
|
373
|
|
|
private function requireKernelBootstrapFile(string $basePath, ?string $bootstrapPath): void |
|
374
|
|
|
{ |
|
375
|
|
|
if (null === $bootstrapPath) { |
|
376
|
|
|
return; |
|
377
|
|
|
} |
|
378
|
|
|
|
|
379
|
|
|
$possiblePaths = [ |
|
380
|
|
|
sprintf('%s/%s', $basePath, $bootstrapPath), |
|
381
|
|
|
$bootstrapPath, |
|
382
|
|
|
]; |
|
383
|
|
|
|
|
384
|
|
|
foreach ($possiblePaths as $possiblePath) { |
|
385
|
|
|
if (file_exists($possiblePath)) { |
|
386
|
|
|
require_once $possiblePath; |
|
387
|
|
|
|
|
388
|
|
|
return; |
|
389
|
|
|
} |
|
390
|
|
|
} |
|
391
|
|
|
|
|
392
|
|
|
throw new \DomainException('Could not load bootstrap file.'); |
|
393
|
|
|
} |
|
394
|
|
|
} |
|
395
|
|
|
|
This error could be the result of:
1. Missing dependencies
PHP Analyzer uses your
composer.jsonfile (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects thecomposer.jsonto be in the root folder of your repository.Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the
requireorrequire-devsection?2. Missing use statement
PHP does not complain about undefined classes in
ìnstanceofchecks. For example, the following PHP code will work perfectly fine:If you have not tested against this specific condition, such errors might go unnoticed.