Completed
Pull Request — master (#29)
by Adrian
01:34
created

SymfonyExtension::autoconfigure()   B

Complexity

Conditions 9
Paths 20

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 27
rs 8.0555
c 0
b 0
f 0
cc 9
nc 20
nop 2
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) {
0 ignored issues
show
Bug introduced by
The class Symfony\Component\HttpKernel\KernelInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to 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 require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
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