SymfonyExtension::autodiscoverBootstrap()   B
last analyzed

Complexity

Conditions 7
Paths 14

Size

Total Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 34
rs 8.4426
c 0
b 0
f 0
cc 7
nc 14
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace FriendsOfBehat\SymfonyExtension\ServiceContainer;
6
7
use Behat\Behat\Context\ServiceContainer\ContextExtension;
8
use Behat\Mink\Session;
9
use Behat\MinkExtension\ServiceContainer\MinkExtension;
10
use Behat\Testwork\Environment\ServiceContainer\EnvironmentExtension;
11
use Behat\Testwork\EventDispatcher\ServiceContainer\EventDispatcherExtension;
12
use Behat\Testwork\ServiceContainer\Extension;
13
use Behat\Testwork\ServiceContainer\ExtensionManager;
14
use FriendsOfBehat\SymfonyExtension\Context\Environment\Handler\ContextServiceEnvironmentHandler;
15
use FriendsOfBehat\SymfonyExtension\Driver\Factory\SymfonyDriverFactory;
16
use FriendsOfBehat\SymfonyExtension\Listener\KernelOrchestrator;
17
use FriendsOfBehat\SymfonyExtension\Mink\Mink;
18
use FriendsOfBehat\SymfonyExtension\Mink\MinkParameters;
19
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
20
use Symfony\Component\DependencyInjection\Alias;
21
use Symfony\Component\DependencyInjection\ContainerBuilder;
22
use Symfony\Component\DependencyInjection\Definition;
23
use Symfony\Component\DependencyInjection\Parameter;
24
use Symfony\Component\DependencyInjection\Reference;
25
26
final class SymfonyExtension implements Extension
27
{
28
    /**
29
     * Kernel used inside Behat contexts or to create services injected to them.
30
     * Container is rebuilt before every scenario.
31
     */
32
    public const KERNEL_ID = 'fob_symfony.kernel';
33
34
    /**
35
     * Kernel used by Symfony driver to isolate web container from contexts' container.
36
     * Container is rebuilt before every request.
37
     */
38
    public const DRIVER_KERNEL_ID = 'fob_symfony.driver_kernel';
39
40
    /** @var bool */
41
    private $minkExtensionFound = false;
42
43
    public function getConfigKey(): string
44
    {
45
        return 'fob_symfony';
46
    }
47
48
    public function initialize(ExtensionManager $extensionManager): void
49
    {
50
        $this->registerMinkDriver($extensionManager);
51
    }
52
53
    public function configure(ArrayNodeDefinition $builder): void
54
    {
55
        $builder
56
            ->addDefaultsIfNotSet()
57
            ->children()
58
                ->scalarNode('bootstrap')->defaultNull()->end()
59
                ->arrayNode('kernel')
60
                    ->addDefaultsIfNotSet()
61
                    ->children()
62
                        ->scalarNode('path')->defaultNull()->end()
63
                        ->scalarNode('class')->defaultNull()->end()
64
                        ->scalarNode('environment')->defaultNull()->end()
65
                        ->booleanNode('debug')->defaultNull()->end()
66
                    ->end()
67
                ->end()
68
            ->end()
69
        ;
70
    }
71
72
    public function load(ContainerBuilder $container, array $config): void
73
    {
74
        $this->fallbackToTestEnvironment();
75
76
        $this->loadBootstrap($this->autodiscoverBootstrap($config['bootstrap']));
77
78
        $this->loadKernel($container, $this->autodiscoverKernelConfiguration($config['kernel']));
79
        $this->loadDriverKernel($container);
80
81
        $this->loadKernelRebooter($container);
82
83
        $this->loadEnvironmentHandler($container);
84
85
        if ($this->minkExtensionFound) {
86
            $this->loadMink($container);
87
            $this->loadMinkDefaultSession($container);
88
            $this->loadMinkParameters($container);
89
        }
90
    }
91
92
    public function process(ContainerBuilder $container): void
93
    {
94
        $this->processEnvironmentHandler($container);
95
96
        if ($this->minkExtensionFound) {
97
            $container->getDefinition(MinkExtension::MINK_ID)->setClass(Mink::class);
98
        }
99
    }
100
101
    private function registerMinkDriver(ExtensionManager $extensionManager): void
102
    {
103
        /** @var MinkExtension|null $minkExtension */
104
        $minkExtension = $extensionManager->getExtension('mink');
105
        if (null === $minkExtension) {
106
            return;
107
        }
108
109
        $minkExtension->registerDriverFactory(new SymfonyDriverFactory('symfony', new Reference(self::DRIVER_KERNEL_ID)));
110
111
        $this->minkExtensionFound = true;
112
    }
113
114
    private function loadKernel(ContainerBuilder $container, array $config): void
115
    {
116
        $definition = new Definition($config['class'], [
117
            $config['environment'] ?? $_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? 'test',
118
            (bool) ($config['debug'] ?? $_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? true),
119
        ]);
120
        $definition->addMethodCall('boot');
121
        $definition->setPublic(true);
122
123
        if ($config['path'] !== null) {
124
            $definition->setFile($config['path']);
125
        }
126
127
        $container->setDefinition(self::KERNEL_ID, $definition);
128
    }
129
130
    private function loadDriverKernel(ContainerBuilder $container): void
131
    {
132
        $container->setDefinition(self::DRIVER_KERNEL_ID, $container->findDefinition(self::KERNEL_ID));
133
    }
134
135
    private function loadKernelRebooter(ContainerBuilder $container): void
136
    {
137
        $definition = new Definition(KernelOrchestrator::class, [new Reference(self::KERNEL_ID), $container]);
138
        $definition->addTag(EventDispatcherExtension::SUBSCRIBER_TAG);
139
140
        $container->setDefinition('fob_symfony.kernel_orchestrator', $definition);
141
    }
142
143
    private function loadEnvironmentHandler(ContainerBuilder $container): void
144
    {
145
        $definition = new Definition(ContextServiceEnvironmentHandler::class, [
146
            new Reference(self::KERNEL_ID),
147
            new Reference('environment.handler.context'),
148
        ]);
149
        $definition->addTag(EnvironmentExtension::HANDLER_TAG, ['priority' => 128]);
150
151
        $container->setDefinition('fob_symfony.environment_handler.context_service', $definition);
152
    }
153
154
    private function loadMink(ContainerBuilder $container): void
155
    {
156
        $container->setAlias('fob_symfony.mink', (new Alias('mink'))->setPublic(true));
157
    }
158
159
    private function loadMinkDefaultSession(ContainerBuilder $container): void
160
    {
161
        $minkDefaultSessionDefinition = new Definition(Session::class);
162
        $minkDefaultSessionDefinition->setPublic(true);
163
        $minkDefaultSessionDefinition->setFactory([new Reference('mink'), 'getSession']);
164
165
        $container->setDefinition('fob_symfony.mink.default_session', $minkDefaultSessionDefinition);
166
    }
167
168
    private function loadMinkParameters(ContainerBuilder $container): void
169
    {
170
        $minkParametersDefinition = new Definition(MinkParameters::class, [new Parameter('mink.parameters')]);
171
        $minkParametersDefinition->setPublic(true);
172
173
        $container->setDefinition('fob_symfony.mink.parameters', $minkParametersDefinition);
174
    }
175
176
    private function loadBootstrap(?string $bootstrap): void
177
    {
178
        if ($bootstrap === null) {
179
            return;
180
        }
181
182
        require_once $bootstrap;
183
    }
184
185
    private function fallbackToTestEnvironment(): void
186
    {
187
        // If there's no defined server / environment variable with an environment, default to test
188
        if (($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? null) === null) {
189
            putenv('APP_ENV=' . $_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = 'test');
190
        }
191
    }
192
193
    private function processEnvironmentHandler(ContainerBuilder $container): void
194
    {
195
        $definition = $container->findDefinition('fob_symfony.environment_handler.context_service');
196
        foreach ($container->findTaggedServiceIds(ContextExtension::INITIALIZER_TAG) as $serviceId => $tags) {
197
            $definition->addMethodCall('registerContextInitializer', [new Reference($serviceId)]);
198
        }
199
    }
200
201
    private function autodiscoverKernelConfiguration(array $config): array
202
    {
203
        if ($config['class'] !== null) {
204
            return $config;
205
        }
206
207
        $autodiscovered = 0;
208
209
        if (class_exists('\App\Kernel')) {
210
            $config['class'] = '\App\Kernel';
211
212
            ++$autodiscovered;
213
        }
214
215
        if (file_exists('app/AppKernel.php')) {
216
            $config['class'] = '\AppKernel';
217
            $config['path'] = 'app/AppKernel.php';
218
219
            ++$autodiscovered;
220
        }
221
222
        if ($autodiscovered !== 1) {
223
            throw new \RuntimeException(
224
                'Could not autodiscover the application kernel. ' .
225
                'Please define it manually with "FriendsOfBehat\SymfonyExtension.kernel" configuration option.'
226
            );
227
        }
228
229
        return $config;
230
    }
231
232
    /**
233
     * @param string|bool|null $bootstrap
234
     */
235
    private function autodiscoverBootstrap($bootstrap): ?string
236
    {
237
        if (is_string($bootstrap)) {
238
            return $bootstrap;
239
        }
240
241
        if ($bootstrap === false) {
242
            return null;
243
        }
244
245
        $autodiscovered = 0;
246
247
        if (file_exists('config/bootstrap.php')) {
248
            $bootstrap = 'config/bootstrap.php';
249
250
            ++$autodiscovered;
251
        }
252
253
        if (file_exists('app/autoload.php')) {
254
            $bootstrap = 'app/autoload.php';
255
256
            ++$autodiscovered;
257
        }
258
259
        if ($autodiscovered === 2) {
260
            throw new \RuntimeException(
261
                'Could not autodiscover the bootstrap file. ' .
262
                'Please define it manually with "FriendsOfBehat\SymfonyExtension.bootstrap" configuration option. ' .
263
                'Setting that option to "false" disables autodiscovering.'
264
            );
265
        }
266
267
        return is_string($bootstrap) ? $bootstrap : null;
268
    }
269
}
270