Completed
Push — master ( f2406b...f65e35 )
by Kevin
07:20 queued 06:04
created

LiipMonitorExtension   F

Complexity

Total Complexity 78

Size/Duplication

Total Lines 395
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 15

Test Coverage

Coverage 47.15%

Importance

Changes 0
Metric Value
wmc 78
lcom 1
cbo 15
dl 0
loc 395
ccs 91
cts 193
cp 0.4715
rs 2.16
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 2
C load() 0 65 11
A process() 0 12 3
D setParameters() 0 64 33
B configureDoctrineMigrationsCheck() 0 30 7
A configureMailer() 0 10 3
A getPredefinedMigrations() 0 27 5
F createMigrationConfigurationService() 0 73 11
A createTemporaryConfiguration() 0 39 3

How to fix   Complexity   

Complex Class

Complex classes like LiipMonitorExtension 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 LiipMonitorExtension, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Liip\MonitorBundle\DependencyInjection;
4
5
use Doctrine\DBAL\Connection;
6
use Doctrine\DBAL\Driver\PDOSqlite\Driver;
7
use Doctrine\Migrations\Configuration\AbstractFileConfiguration;
8
use Doctrine\Migrations\Configuration\Configuration as DoctrineMigrationConfiguration;
9
use Doctrine\Migrations\MigrationException;
10
use Liip\MonitorBundle\DoctrineMigrations\Configuration as LiipMigrationConfiguration;
11
use Symfony\Component\Config\FileLocator;
12
use Symfony\Component\Config\Loader\LoaderInterface;
13
use Symfony\Component\DependencyInjection\ChildDefinition;
14
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
15
use Symfony\Component\DependencyInjection\ContainerBuilder;
16
use Symfony\Component\DependencyInjection\Definition;
17
use Symfony\Component\DependencyInjection\DefinitionDecorator;
18
use Symfony\Component\DependencyInjection\Loader;
19
use Symfony\Component\DependencyInjection\Reference;
20
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
21
22
class LiipMonitorExtension extends Extension implements CompilerPassInterface
23
{
24
    /**
25
     * Tuple (migrationsConfiguration, tempConfiguration) for doctrine migrations check.
26
     *
27
     * @var array
28
     */
29
    private $migrationConfigurationsServices = [];
30
31
    /**
32
     * Connection object needed for correct migration loading.
33
     *
34
     * @var Connection
35
     */
36
    private $fakeConnection;
37
38 49
    public function __construct()
39
    {
40 49
        if (class_exists(Connection::class)) {
41 49
            $this->fakeConnection = new Connection([], new Driver());
42
        }
43 49
    }
44
45
    /**
46
     * Loads the services based on your application configuration.
47
     */
48 49
    public function load(array $configs, ContainerBuilder $container)
49
    {
50 49
        $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
51 49
        $loader->load('runner.xml');
52 49
        $loader->load('helper.xml');
53 49
        $loader->load('commands.xml');
54
55 49
        $configuration = new Configuration();
56 49
        $config = $this->processConfiguration($configuration, $configs);
57
58 44
        if (null === $config['view_template']) {
59 44
            $config['view_template'] = __DIR__.'/../Resources/views/health/index.html.php';
60
        }
61
62 44
        if ($config['enable_controller']) {
63 2
            $container->setParameter(sprintf('%s.view_template', $this->getAlias()), $config['view_template']);
64 2
            $container->setParameter(sprintf('%s.failure_status_code', $this->getAlias()), $config['failure_status_code']);
65 2
            $loader->load('controller.xml');
66
        }
67
68 44
        $this->configureMailer($container, $loader, $config);
69
70 44
        $container->setParameter(sprintf('%s.default_group', $this->getAlias()), $config['default_group']);
71
72
        // symfony3 does not define templating.helper.assets unless php templating is included
73 44
        if ($container->has('templating.helper.assets')) {
74
            $pathHelper = $container->getDefinition('liip_monitor.helper');
75
            $pathHelper->replaceArgument(0, 'templating.helper.assets');
76
        }
77
78
        // symfony3 does not define templating.helper.router unless php templating is included
79 44
        if ($container->has('templating.helper.router')) {
80
            $pathHelper = $container->getDefinition('liip_monitor.helper');
81
            $pathHelper->replaceArgument(1, 'templating.helper.router');
82
        }
83
84 44
        if (empty($config['checks'])) {
85 7
            return;
86
        }
87
88 37
        $checksLoaded = [];
89 37
        $containerParams = [];
90 37
        foreach ($config['checks']['groups'] as $group => $checks) {
91 37
            if (empty($checks)) {
92
                continue;
93
            }
94
95 37
            foreach ($checks as $check => $values) {
96 37
                if (empty($values)) {
97 37
                    continue;
98
                }
99
100 37
                $containerParams['groups'][$group][$check] = $values;
101 37
                $this->setParameters($container, $check, $group, $values);
102
103 37
                if (!in_array($check, $checksLoaded)) {
104 37
                    $loader->load('checks/'.$check.'.xml');
105 37
                    $checksLoaded[] = $check;
106
                }
107
            }
108
        }
109
110 37
        $container->setParameter(sprintf('%s.checks', $this->getAlias()), $containerParams);
111 37
        $this->configureDoctrineMigrationsCheck($container, $containerParams);
112 37
    }
113
114
    /**
115
     * {@inheritdoc}
116
     */
117 40
    public function process(ContainerBuilder $container)
118
    {
119 40
        foreach ($this->migrationConfigurationsServices as $services) {
120
            list($configurationService, $configuration) = $services;
121
            /** @var Definition $configurationService */
122
            /** @var DoctrineMigrationConfiguration $configuration */
123
            $versions = $this->getPredefinedMigrations($container, $configuration, $this->fakeConnection);
124
            if ($versions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $versions of type array[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
125
                $configurationService->addMethodCall('registerMigrations', [$versions]);
126
            }
127
        }
128 40
    }
129
130
    /**
131
     * @param string $checkName
132
     * @param string $group
133
     * @param array  $values
134
     */
135 37
    private function setParameters(ContainerBuilder $container, $checkName, $group, $values)
136
    {
137 37
        $prefix = sprintf('%s.check.%s', $this->getAlias(), $checkName);
138 37
        switch ($checkName) {
139 37
            case 'class_exists':
140 36
            case 'cpu_performance':
141 35
            case 'php_extensions':
142 32
            case 'php_version':
143 31
            case 'php_flags':
144 30
            case 'readable_directory':
145 29
            case 'writable_directory':
146 28
            case 'process_running':
147 24
            case 'doctrine_dbal':
148 20
            case 'doctrine_mongodb':
149 20
            case 'http_service':
150 19
            case 'guzzle_http_service':
151 18
            case 'memcache':
152 17
            case 'memcached':
153 17
            case 'redis':
154 16
            case 'rabbit_mq':
155 15
            case 'stream_wrapper_exists':
156 14
            case 'file_ini':
157 13
            case 'file_json':
158 12
            case 'file_xml':
159 11
            case 'file_yaml':
160 10
            case 'expressions':
161 28
                $container->setParameter($prefix.'.'.$group, $values);
162 28
                break;
163
164 9
            case 'symfony_version':
165 1
                break;
166
167 8
            case 'opcache_memory':
168 1
                if (!class_exists('ZendDiagnostics\Check\OpCacheMemory')) {
169
                    throw new \InvalidArgumentException('Please require at least "v1.0.4" of "ZendDiagnostics"');
170
                }
171 1
                break;
172
173 7
            case 'doctrine_migrations':
174
                if (!class_exists('ZendDiagnostics\Check\DoctrineMigration')) {
175
                    throw new \InvalidArgumentException('Please require at least "v1.0.6" of "ZendDiagnostics"');
176
                }
177
178
                if (!class_exists('Doctrine\Migrations\Configuration\Configuration')) {
179
                    throw new \InvalidArgumentException('Please require at least "v2.0.0" of "Doctrine Migrations Library"');
180
                }
181
182
                $container->setParameter($prefix.'.'.$group, $values);
183
                break;
184
185 7
            case 'pdo_connections':
186 1
                if (!class_exists('ZendDiagnostics\Check\PDOCheck')) {
187
                    throw new \InvalidArgumentException('Please require at least "v1.0.5" of "ZendDiagnostics"');
188
                }
189 1
                $container->setParameter($prefix.'.'.$group, $values);
190 1
                break;
191
        }
192
193 37
        if (is_array($values)) {
194 33
            foreach ($values as $key => $value) {
195 33
                $container->setParameter($prefix.'.'.$key.'.'.$group, $value);
196
            }
197
        }
198 37
    }
199
200
    /**
201
     * Set up doctrine migration configuration services.
202
     *
203
     * @param ContainerBuilder $container The container
204
     * @param array            $params    Container params
205
     *
206
     * @return void
207
     */
208 37
    private function configureDoctrineMigrationsCheck(ContainerBuilder $container, array $params)
209
    {
210 37
        if (!$container->hasDefinition('liip_monitor.check.doctrine_migrations') || !isset($params['groups'])) {
211 37
            return;
212
        }
213
214
        foreach ($params['groups'] as $groupName => $groupChecks) {
215
            if (!isset($groupChecks['doctrine_migrations'])) {
216
                continue;
217
            }
218
219
            $services = [];
220
            foreach ($groupChecks['doctrine_migrations'] as $key => $config) {
221
                try {
222
                    $serviceConfiguration =
223
                        $this->createMigrationConfigurationService($container, $config['connection'], $config['configuration_file'] ?? null);
224
225
                    $serviceId = sprintf('liip_monitor.check.doctrine_migrations.configuration.%s.%s', $groupName, $key);
226
                    $container->setDefinition($serviceId, $serviceConfiguration);
227
228
                    $services[$key] = $serviceId;
229
                } catch (MigrationException $e) {
0 ignored issues
show
Bug introduced by
The class Doctrine\Migrations\MigrationException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
230
                    throw new MigrationException(sprintf('Invalid doctrine migration check under "%s.%s": %s', $groupName, $key, $e->getMessage()), $e->getCode(), $e);
231
                }
232
            }
233
234
            $parameter = sprintf('%s.check.%s.%s', $this->getAlias(), 'doctrine_migrations', $groupName);
235
            $container->setParameter($parameter, $services);
236
        }
237
    }
238
239 44
    private function configureMailer(ContainerBuilder $container, LoaderInterface $loader, array $config)
0 ignored issues
show
Unused Code introduced by
The parameter $loader is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
240
    {
241 44
        if (false === $config['mailer']['enabled']) {
242 43
            return;
243
        }
244
245 1
        foreach ($config['mailer'] as $key => $value) {
246 1
            $container->setParameter(sprintf('%s.mailer.%s', $this->getAlias(), $key), $value);
247
        }
248 1
    }
249
250
    /**
251
     * Return key-value array with migration version as key and class as a value defined in config file.
252
     *
253
     * @param ContainerBuilder               $container  The container
254
     * @param DoctrineMigrationConfiguration $config     Current configuration
255
     * @param Connection                     $connection Fake connections
256
     *
257
     * @return array[]
258
     */
259
    private function getPredefinedMigrations(ContainerBuilder $container, DoctrineMigrationConfiguration $config, Connection $connection)
260
    {
261
        $result = [];
262
263
        $diff = new LiipMigrationConfiguration($connection);
264
265
        if ($namespace = $config->getMigrationsNamespace()) {
0 ignored issues
show
Unused Code introduced by
$namespace is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
266
            $diff->setMigrationsNamespace($config->getMigrationsNamespace());
267
        }
268
269
        if ($dir = $config->getMigrationsDirectory()) {
270
            $diff->setMigrationsDirectory($dir);
271
        }
272
273
        $diff->setContainer($container);
274
        $diff->configure();
275
276
        foreach ($config->getMigrations() as $version) {
277
            $result[$version->getVersion()] = get_class($version->getMigration());
278
        }
279
280
        foreach ($diff->getAvailableVersions() as $version) {
281
            unset($result[$version]);
282
        }
283
284
        return $result;
285
    }
286
287
    /**
288
     * Creates migration configuration service definition.
289
     *
290
     * @param ContainerBuilder $container      DI Container
291
     * @param string           $connectionName Connection name for container service
292
     * @param string           $filename       File name with migration configuration
293
     *
294
     * @return DefinitionDecorator|ChildDefinition
295
     */
296
    private function createMigrationConfigurationService(ContainerBuilder $container, string $connectionName, string $filename = null)
297
    {
298
        $configuration = $this->createTemporaryConfiguration($container, $this->fakeConnection, $filename);
299
300
        $configurationServiceName = 'liip_monitor.check.doctrine_migrations.abstract_configuration';
301
        $serviceConfiguration = class_exists('Symfony\Component\DependencyInjection\ChildDefinition')
302
            ? new ChildDefinition($configurationServiceName)
303
            : new DefinitionDecorator($configurationServiceName)
304
        ;
305
306
        $this->migrationConfigurationsServices[] = [$serviceConfiguration, $configuration];
307
308
        $serviceConfiguration->replaceArgument(
309
            0,
310
            new Reference(sprintf('doctrine.dbal.%s_connection', $connectionName))
311
        );
312
313
        if ($configuration->getMigrationsNamespace()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $configuration->getMigrationsNamespace() of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
314
            $serviceConfiguration->addMethodCall(
315
                'setMigrationsNamespace',
316
                [$configuration->getMigrationsNamespace()]
317
            );
318
        }
319
320
        if ($configuration->getMigrationsTableName()) {
321
            $serviceConfiguration->addMethodCall(
322
                'setMigrationsTableName',
323
                [$configuration->getMigrationsTableName()]
324
            );
325
        }
326
327
        if ($configuration->getMigrationsColumnName()) {
328
            $serviceConfiguration->addMethodCall(
329
                'setMigrationsColumnName',
330
                [$configuration->getMigrationsColumnName()]
331
            );
332
        }
333
334
        if ($configuration->getName()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $configuration->getName() of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
335
            $serviceConfiguration->addMethodCall('setName', [$configuration->getName()]);
336
        }
337
338
        if ($configuration->getMigrationsDirectory()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $configuration->getMigrationsDirectory() of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
339
            $directory = $configuration->getMigrationsDirectory();
340
            $pathPlaceholders = ['kernel.root_dir', 'kernel.cache_dir', 'kernel.logs_dir'];
341
            foreach ($pathPlaceholders as $parameter) {
342
                $kernelDir = realpath($container->getParameter($parameter));
343
                if (0 === strpos(realpath($directory), $kernelDir)) {
344
                    $directory = str_replace($kernelDir, "%{$parameter}%", $directory);
345
                    break;
346
                }
347
            }
348
349
            $serviceConfiguration->addMethodCall(
350
                'setMigrationsDirectory',
351
                [$directory]
352
            );
353
        }
354
355
        $serviceConfiguration->addMethodCall('configure', []);
356
357
        if ($configuration->areMigrationsOrganizedByYear()) {
358
            $serviceConfiguration->addMethodCall('setMigrationsAreOrganizedByYear', [true]);
359
360
            return $serviceConfiguration;
361
        } elseif ($configuration->areMigrationsOrganizedByYearAndMonth()) {
362
            $serviceConfiguration->addMethodCall('setMigrationsAreOrganizedByYearAndMonth', [true]);
363
364
            return $serviceConfiguration;
365
        }
366
367
        return $serviceConfiguration;
368
    }
369
370
    /**
371
     * Creates in-memory migration configuration for setting up container service.
372
     *
373
     * @param ContainerBuilder $container  The container
374
     * @param Connection       $connection Fake connection
375
     * @param string           $filename   Migrations configuration file
376
     */
377
    private function createTemporaryConfiguration(
378
        ContainerBuilder $container,
379
        Connection $connection,
380
        string $filename = null
381
    ): DoctrineMigrationConfiguration {
382
        if (null === $filename) {
383
            // this is configured from migrations bundle
384
            return new DoctrineMigrationConfiguration($connection);
385
        }
386
387
        // -------
388
        // This part must be in sync with Doctrine\Migrations\Tools\Console\Helper\ConfigurationHelper::loadConfig
389
        $map = [
390
            'xml' => '\XmlConfiguration',
391
            'yaml' => '\YamlConfiguration',
392
            'yml' => '\YamlConfiguration',
393
            'php' => '\ArrayConfiguration',
394
            'json' => '\JsonConfiguration',
395
        ];
396
        // --------
397
398
        $filename = $container->getParameterBag()->resolveValue($filename);
399
        $info = pathinfo($filename);
400
        // check we can support this file type
401
        if (empty($map[$info['extension']])) {
402
            throw new \InvalidArgumentException('Given config file type is not supported');
403
        }
404
405
        $class = 'Doctrine\Migrations\Configuration';
406
        $class .= $map[$info['extension']];
407
        // -------
408
409
        /** @var AbstractFileConfiguration $configuration */
410
        $configuration = new $class($connection);
411
        $configuration->load($filename);
412
        $configuration->validate();
413
414
        return $configuration;
415
    }
416
}
417