Completed
Pull Request — master (#125)
by Guillaume
09:40
created

SymfonyExtension   A

Complexity

Total Complexity 40

Size/Duplication

Total Lines 272
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 0
Metric Value
wmc 40
lcom 1
cbo 11
dl 0
loc 272
rs 9.2
c 0
b 0
f 0

20 Methods

Rating   Name   Duplication   Size   Complexity  
A getConfigKey() 0 4 1
A initialize() 0 4 1
A configure() 0 20 1
A load() 0 28 4
A process() 0 8 2
A registerMinkDriver() 0 12 2
A loadKernel() 0 15 2
A loadDriverKernel() 0 4 1
A loadKernelRebooter() 0 7 1
A loadEnvironmentHandler() 0 10 1
A loadMink() 0 4 1
A loadMinkDefaultSession() 0 8 1
A loadMinkParameters() 0 7 1
A loadBootstrap() 0 8 2
A fallbackToTestEnvironment() 0 7 2
A processEnvironmentHandler() 0 7 2
A autodiscoverKernelConfiguration() 0 30 5
B autodiscoverBootstrap() 0 34 7
A handleAutoConfiguration() 0 4 1
A handleAutoWiring() 0 6 2

How to fix   Complexity   

Complex Class

Complex classes like SymfonyExtension often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SymfonyExtension, and based on these observations, apply Extract Interface, too.

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