Completed
Pull Request — master (#29)
by
unknown
01:27
created

SymfonyExtension::load()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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