Completed
Pull Request — master (#24)
by Arnaud
11:04
created

SymfonyExtension::load()   B

Complexity

Conditions 4
Paths 5

Size

Total Lines 23
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 23
rs 8.7972
c 0
b 0
f 0
cc 4
eloc 14
nc 5
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 DEBUG_MODE = false;
73
74
    /**
75
     * @var CrossContainerProcessor|null
76
     */
77
    private $crossContainerProcessor;
78
79
    /**
80
     * {@inheritdoc}
81
     */
82
    public function getConfigKey(): string
83
    {
84
        return 'fob_symfony';
85
    }
86
87
    /**
88
     * {@inheritdoc}
89
     */
90
    public function initialize(ExtensionManager $extensionManager): void
91
    {
92
        $this->registerSymfonyDriverFactory($extensionManager);
93
        $this->initializeCrossContainerProcessor($extensionManager);
94
    }
95
96
    /**
97
     * {@inheritdoc}
98
     */
99
    public function configure(ArrayNodeDefinition $builder): void
100
    {
101
        $builder
102
            ->addDefaultsIfNotSet()
103
                ->children()
104
                    ->scalarNode('env_file')->defaultNull()->end()
105
                    ->arrayNode('kernel')
106
                        ->addDefaultsIfNotSet()
107
                        ->children()
108
                            ->scalarNode('bootstrap')->defaultValue('app/autoload.php')->end()
109
                            ->scalarNode('path')->defaultValue('app/AppKernel.php')->end()
110
                            ->scalarNode('class')->defaultValue('AppKernel')->end()
111
                            ->scalarNode('env')->defaultValue('test')->end()
112
                            ->booleanNode('debug')->defaultTrue()->end()
113
                        ->end()
114
                    ->end()
115
                ->end()
116
            ->end()
117
        ;
118
    }
119
120
    /**
121
     * {@inheritdoc}
122
     */
123
    public function load(ContainerBuilder $container, array $config): void
124
    {
125
        if (null !== $config['env_file']) {
126
            $this->loadEnvVars($container, $config['env_file']);
127
128
            $environment = false !== getenv('APP_ENV') ? getenv('APP_ENV') : self::DEBUG_MODE;
129
            $debugMode = false !== getenv('APP_DEBUG') ? getenv('APP_DEBUG') : self::DEBUG_MODE;
130
131
            $config['kernel']['env'] = $environment;
132
            $config['kernel']['kernel'] = $debugMode;
133
        }
134
135
        $this->loadKernel($container, $config['kernel']);
136
        $this->loadKernelContainer($container);
137
138
        $this->loadDriverKernel($container);
139
140
        $this->loadSharedKernel($container);
141
        $this->loadSharedKernelContainer($container);
142
143
        $this->loadKernelRebooter($container);
144
        $this->declareSymfonyContainers($container);
145
    }
146
147
    /**
148
     * {@inheritdoc}
149
     */
150
    public function process(ContainerBuilder $container): void
151
    {
152
    }
153
154
    /**
155
     * @param ContainerBuilder $container
156
     * @param string $fileName
157
     */
158
    private function loadEnvVars(ContainerBuilder $container, string $fileName): void
159
    {
160
        $envFilePath = sprintf('%s/%s', $container->getParameter('paths.base'), $fileName);
161
        (new Dotenv())->load($envFilePath);
162
    }
163
164
    /**
165
     * @param ContainerBuilder $container
166
     */
167
    private function loadKernel(ContainerBuilder $container, array $config): void
168
    {
169
        $definition = new Definition($config['class'], array(
170
            $config['env'],
171
            $config['debug'],
172
        ));
173
        $definition->addMethodCall('boot');
174
        $definition->setFile($this->getKernelFile($container->getParameter('paths.base'), $config['path']));
175
176
        $container->setDefinition(self::KERNEL_ID, $definition);
177
178
        $this->requireKernelBootstrapFile($container->getParameter('paths.base'), $config['bootstrap']);
179
    }
180
181
    /**
182
     * @param ContainerBuilder $container
183
     */
184
    private function loadKernelContainer(ContainerBuilder $container): void
185
    {
186
        $containerDefinition = new Definition(Container::class);
187
        $containerDefinition->setFactory([
188
            new Reference(self::KERNEL_ID),
189
            'getContainer',
190
        ]);
191
192
        $container->setDefinition(self::KERNEL_CONTAINER_ID, $containerDefinition);
193
    }
194
195
    /**
196
     * @param ContainerBuilder $container
197
     */
198
    private function loadDriverKernel(ContainerBuilder $container): void
199
    {
200
        $container->setDefinition(self::DRIVER_KERNEL_ID, $container->findDefinition(self::KERNEL_ID));
201
    }
202
203
    /**
204
     * @param ContainerBuilder $container
205
     */
206
    private function loadSharedKernel(ContainerBuilder $container): void
207
    {
208
        $container->setDefinition(self::SHARED_KERNEL_ID, $container->findDefinition(self::KERNEL_ID));
209
    }
210
211
    /**
212
     * @param ContainerBuilder $container
213
     */
214
    private function loadSharedKernelContainer(ContainerBuilder $container): void
215
    {
216
        $containerDefinition = new Definition(Container::class);
217
        $containerDefinition->setFactory([
218
            new Reference(self::SHARED_KERNEL_ID),
219
            'getContainer',
220
        ]);
221
222
        $container->setDefinition(self::SHARED_KERNEL_CONTAINER_ID, $containerDefinition);
223
    }
224
225
    /**
226
     * @param ContainerBuilder $container
227
     */
228
    private function loadKernelRebooter(ContainerBuilder $container): void
229
    {
230
        $definition = new Definition(KernelRebooter::class, [new Reference(self::KERNEL_ID)]);
231
        $definition->addTag(EventDispatcherExtension::SUBSCRIBER_TAG);
232
233
        $container->setDefinition(self::KERNEL_ID . '.rebooter', $definition);
234
    }
235
236
    /**
237
     * @param ContainerBuilder $container
238
     *
239
     * @throws \Exception
240
     */
241
    private function declareSymfonyContainers(ContainerBuilder $container): void
242
    {
243
        if (null !== $this->crossContainerProcessor) {
244
            $this->crossContainerProcessor->addContainerAccessor(
245
                'symfony',
246
                new KernelBasedContainerAccessor($container->get(self::KERNEL_ID))
247
            );
248
249
            $this->crossContainerProcessor->addContainerAccessor(
250
                'symfony_driver',
251
                new KernelBasedContainerAccessor($container->get(self::DRIVER_KERNEL_ID))
252
            );
253
254
            $this->crossContainerProcessor->addContainerAccessor(
255
                'symfony_shared',
256
                new KernelBasedContainerAccessor($container->get(self::SHARED_KERNEL_ID))
257
            );
258
        }
259
    }
260
261
    /**
262
     * @param ExtensionManager $extensionManager
263
     */
264
    private function initializeCrossContainerProcessor(ExtensionManager $extensionManager): void
265
    {
266
        /** @var CrossContainerExtension $extension */
267
        $extension = $extensionManager->getExtension('fob_cross_container');
268
        if (null !== $extension) {
269
            $this->crossContainerProcessor = $extension->getCrossContainerProcessor();
270
        }
271
    }
272
273
    /**
274
     * @param ExtensionManager $extensionManager
275
     */
276
    private function registerSymfonyDriverFactory(ExtensionManager $extensionManager): void
277
    {
278
        /** @var MinkExtension $minkExtension */
279
        $minkExtension = $extensionManager->getExtension('mink');
280
        if (null === $minkExtension) {
281
            return;
282
        }
283
284
        $minkExtension->registerDriverFactory(new SymfonyDriverFactory(
285
            'symfony',
286
            new Reference(self::DRIVER_KERNEL_ID)
287
        ));
288
    }
289
290
    /**
291
     * @param string $basePath
292
     * @param string $kernelPath
293
     *
294
     * @return string|null
295
     */
296
    private function getKernelFile(string $basePath, string $kernelPath): ?string
297
    {
298
        $possibleFiles = [
299
            sprintf('%s/%s', $basePath, $kernelPath),
300
            $kernelPath,
301
        ];
302
303
        foreach ($possibleFiles as $possibleFile) {
304
            if (file_exists($possibleFile)) {
305
                return $possibleFile;
306
            }
307
        }
308
309
        return null;
310
    }
311
312
    /**
313
     * @param string $basePath
314
     * @param string|null $bootstrapPath
315
     *
316
     * @throws \DomainException
317
     */
318
    private function requireKernelBootstrapFile(string $basePath, ?string $bootstrapPath): void
319
    {
320
        if (null === $bootstrapPath) {
321
            return;
322
        }
323
324
        $possiblePaths = [
325
            sprintf('%s/%s', $basePath, $bootstrapPath),
326
            $bootstrapPath,
327
        ];
328
329
        foreach ($possiblePaths as $possiblePath) {
330
            if (file_exists($possiblePath)) {
331
                require_once $possiblePath;
332
333
                return;
334
            }
335
        }
336
337
        throw new \DomainException('Could not load bootstrap file.');
338
    }
339
}
340