Completed
Push — master ( 3e4489...bae9a2 )
by Łukasz
02:05 queued 11s
created

SymfonyExtension::autodiscoverBootstrap()   B

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\MinkParameters;
18
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
19
use Symfony\Component\DependencyInjection\ContainerBuilder;
20
use Symfony\Component\DependencyInjection\Definition;
21
use Symfony\Component\DependencyInjection\Parameter;
22
use Symfony\Component\DependencyInjection\Reference;
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 rebuilt before every scenario.
29
     */
30
    public const KERNEL_ID = 'fob_symfony.kernel';
31
32
    /**
33
     * Kernel used by Symfony driver to isolate web container from contexts' container.
34
     * Container is rebuilt before every request.
35
     */
36
    private const DRIVER_KERNEL_ID = 'fob_symfony.driver_kernel';
37
38
    /** @var bool */
39
    private $minkExtensionFound = false;
40
41
    public function getConfigKey(): string
42
    {
43
        return 'fob_symfony';
44
    }
45
46
    public function initialize(ExtensionManager $extensionManager): void
47
    {
48
        $this->registerMinkDriver($extensionManager);
49
    }
50
51
    public function configure(ArrayNodeDefinition $builder): void
52
    {
53
        $builder
54
            ->addDefaultsIfNotSet()
55
            ->children()
56
                ->scalarNode('bootstrap')->defaultNull()->end()
57
                ->arrayNode('kernel')
58
                    ->addDefaultsIfNotSet()
59
                    ->children()
60
                        ->scalarNode('path')->defaultNull()->end()
61
                        ->scalarNode('class')->defaultNull()->end()
62
                        ->scalarNode('environment')->defaultValue('test')->end()
63
                        ->booleanNode('debug')->defaultTrue()->end()
64
                    ->end()
65
                ->end()
66
            ->end()
67
        ;
68
    }
69
70
    public function load(ContainerBuilder $container, array $config): void
71
    {
72
        $container->setParameter('fob_symfony.bootstrap', $config['bootstrap']);
73
74
        $this->loadKernel($container, $this->autodiscoverKernelConfiguration($config['kernel']));
75
        $this->loadDriverKernel($container);
76
77
        $this->loadKernelRebooter($container);
78
79
        $this->loadEnvironmentHandler($container);
80
81
        if ($this->minkExtensionFound) {
82
            $this->loadMinkDefaultSession($container);
83
            $this->loadMinkParameters($container);
84
        }
85
    }
86
87
    public function process(ContainerBuilder $container): void
88
    {
89
        $this->processBootstrap($this->autodiscoverBootstrap($container->getParameter('fob_symfony.bootstrap')));
90
    }
91
92
    private function registerMinkDriver(ExtensionManager $extensionManager): void
93
    {
94
        /** @var MinkExtension|null $minkExtension */
95
        $minkExtension = $extensionManager->getExtension('mink');
96
        if (null === $minkExtension) {
97
            return;
98
        }
99
100
        $minkExtension->registerDriverFactory(new SymfonyDriverFactory('symfony', new Reference(self::DRIVER_KERNEL_ID)));
101
102
        $this->minkExtensionFound = true;
103
    }
104
105
    private function loadKernel(ContainerBuilder $container, array $config): void
106
    {
107
        $definition = new Definition($config['class'], [
108
            $config['environment'],
109
            $config['debug'],
110
        ]);
111
        $definition->addMethodCall('boot');
112
        $definition->setPublic(true);
113
114
        if ($config['path'] !== null) {
115
            $definition->setFile($config['path']);
116
        }
117
118
        $container->setDefinition(self::KERNEL_ID, $definition);
119
    }
120
121
    private function loadDriverKernel(ContainerBuilder $container): void
122
    {
123
        $container->setDefinition(self::DRIVER_KERNEL_ID, $container->findDefinition(self::KERNEL_ID));
124
    }
125
126
    private function loadKernelRebooter(ContainerBuilder $container): void
127
    {
128
        $definition = new Definition(KernelOrchestrator::class, [new Reference(self::KERNEL_ID), $container]);
129
        $definition->addTag(EventDispatcherExtension::SUBSCRIBER_TAG);
130
131
        $container->setDefinition('fob_symfony.kernel_orchestrator', $definition);
132
    }
133
134
    private function loadEnvironmentHandler(ContainerBuilder $container): void
135
    {
136
        $definition = new Definition(ContextServiceEnvironmentHandler::class, [
137
            new Reference(self::KERNEL_ID),
138
        ]);
139
        $definition->addTag(EnvironmentExtension::HANDLER_TAG, ['priority' => 128]);
140
141
        foreach ($container->findTaggedServiceIds(ContextExtension::INITIALIZER_TAG) as $serviceId => $tags) {
142
            $definition->addMethodCall('registerContextInitializer', [$container->getDefinition($serviceId)]);
143
        }
144
145
        $container->setDefinition('fob_symfony.environment_handler.context_service', $definition);
146
    }
147
148
    private function loadMinkDefaultSession(ContainerBuilder $container): void
149
    {
150
        $minkDefaultSessionDefinition = new Definition(Session::class);
151
        $minkDefaultSessionDefinition->setPublic(true);
152
        $minkDefaultSessionDefinition->setFactory([new Reference('mink'), 'getSession']);
153
154
        $container->setDefinition('fob_symfony.mink.default_session', $minkDefaultSessionDefinition);
155
    }
156
157
    private function loadMinkParameters(ContainerBuilder $container): void
158
    {
159
        $minkParametersDefinition = new Definition(MinkParameters::class, [new Parameter('mink.parameters')]);
160
        $minkParametersDefinition->setPublic(true);
161
162
        $container->setDefinition('fob_symfony.mink.parameters', $minkParametersDefinition);
163
    }
164
165
    private function autodiscoverKernelConfiguration(array $config): array
166
    {
167
        if ($config['class'] !== null) {
168
            return $config;
169
        }
170
171
        $autodiscovered = 0;
172
173
        if (class_exists('\App\Kernel')) {
174
            $config['class'] = '\App\Kernel';
175
176
            ++$autodiscovered;
177
        }
178
179
        if (file_exists('app/AppKernel.php')) {
180
            $config['class'] = '\AppKernel';
181
            $config['path'] = 'app/AppKernel.php';
182
183
            ++$autodiscovered;
184
        }
185
186
        if ($autodiscovered !== 1) {
187
            throw new \RuntimeException(
188
                'Could not autodiscover the application kernel. ' .
189
                'Please define it manually with "FriendsOfBehat\SymfonyExtension.kernel" configuration option.'
190
            );
191
        }
192
193
        return $config;
194
    }
195
196
    /**
197
     * @param string|bool|null $bootstrap
198
     */
199
    private function autodiscoverBootstrap($bootstrap): ?string
200
    {
201
        if (is_string($bootstrap)) {
202
            return $bootstrap;
203
        }
204
205
        if ($bootstrap === false) {
206
            return null;
207
        }
208
209
        $autodiscovered = 0;
210
211
        if (file_exists('config/bootstrap.php')) {
212
            $bootstrap = 'config/bootstrap.php';
213
214
            ++$autodiscovered;
215
        }
216
217
        if (file_exists('app/autoload.php')) {
218
            $bootstrap = 'app/autoload.php';
219
220
            ++$autodiscovered;
221
        }
222
223
        if ($autodiscovered === 2) {
224
            throw new \RuntimeException(
225
                'Could not autodiscover the bootstrap file. ' .
226
                'Please define it manually with "FriendsOfBehat\SymfonyExtension.bootstrap" configuration option. ' .
227
                'Setting that option to "false" disables autodiscovering.'
228
            );
229
        }
230
231
        return is_string($bootstrap) ? $bootstrap : null;
232
    }
233
234
    private function processBootstrap(?string $bootstrap): void
235
    {
236
        if ($bootstrap === null) {
237
            return;
238
        }
239
240
        require_once $bootstrap;
241
    }
242
}
243