Completed
Push — master ( 70065c...f2406b )
by Kevin
12:35 queued 10:57
created

LiipMonitorExtension   F

Complexity

Total Complexity 80

Size/Duplication

Total Lines 404
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 16

Test Coverage

Coverage 48.48%

Importance

Changes 0
Metric Value
wmc 80
lcom 1
cbo 16
dl 0
loc 404
ccs 96
cts 198
cp 0.4848
rs 2
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 19 5
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\Exception\ServiceNotFoundException;
19
use Symfony\Component\DependencyInjection\Loader;
20
use Symfony\Component\DependencyInjection\Reference;
21
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
22
23
class LiipMonitorExtension extends Extension implements CompilerPassInterface
24
{
25
    /**
26
     * Tuple (migrationsConfiguration, tempConfiguration) for doctrine migrations check.
27
     *
28
     * @var array
29
     */
30
    private $migrationConfigurationsServices = [];
31
32
    /**
33
     * Connection object needed for correct migration loading.
34
     *
35
     * @var Connection
36
     */
37
    private $fakeConnection;
38
39 49
    public function __construct()
40
    {
41 49
        if (class_exists(Connection::class)) {
42 49
            $this->fakeConnection = new Connection([], new Driver());
43
        }
44 49
    }
45
46
    /**
47
     * Loads the services based on your application configuration.
48
     */
49 49
    public function load(array $configs, ContainerBuilder $container)
50
    {
51 49
        $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
52 49
        $loader->load('runner.xml');
53 49
        $loader->load('helper.xml');
54 49
        $loader->load('commands.xml');
55
56 49
        $configuration = new Configuration();
57 49
        $config = $this->processConfiguration($configuration, $configs);
58
59 44
        if (null === $config['view_template']) {
60 44
            $config['view_template'] = __DIR__.'/../Resources/views/health/index.html.php';
61
        }
62
63 44
        if ($config['enable_controller']) {
64 2
            $container->setParameter(sprintf('%s.view_template', $this->getAlias()), $config['view_template']);
65 2
            $container->setParameter(sprintf('%s.failure_status_code', $this->getAlias()), $config['failure_status_code']);
66 2
            $loader->load('controller.xml');
67
        }
68
69 44
        $this->configureMailer($container, $loader, $config);
70
71 43
        $container->setParameter(sprintf('%s.default_group', $this->getAlias()), $config['default_group']);
72
73
        // symfony3 does not define templating.helper.assets unless php templating is included
74 43
        if ($container->has('templating.helper.assets')) {
75
            $pathHelper = $container->getDefinition('liip_monitor.helper');
76
            $pathHelper->replaceArgument(0, 'templating.helper.assets');
77
        }
78
79
        // symfony3 does not define templating.helper.router unless php templating is included
80 43
        if ($container->has('templating.helper.router')) {
81
            $pathHelper = $container->getDefinition('liip_monitor.helper');
82
            $pathHelper->replaceArgument(1, 'templating.helper.router');
83
        }
84
85 43
        if (empty($config['checks'])) {
86 6
            return;
87
        }
88
89 37
        $checksLoaded = [];
90 37
        $containerParams = [];
91 37
        foreach ($config['checks']['groups'] as $group => $checks) {
92 37
            if (empty($checks)) {
93
                continue;
94
            }
95
96 37
            foreach ($checks as $check => $values) {
97 37
                if (empty($values)) {
98 37
                    continue;
99
                }
100
101 37
                $containerParams['groups'][$group][$check] = $values;
102 37
                $this->setParameters($container, $check, $group, $values);
103
104 37
                if (!in_array($check, $checksLoaded)) {
105 37
                    $loader->load('checks/'.$check.'.xml');
106 37
                    $checksLoaded[] = $check;
107
                }
108
            }
109
        }
110
111 37
        $container->setParameter(sprintf('%s.checks', $this->getAlias()), $containerParams);
112 37
        $this->configureDoctrineMigrationsCheck($container, $containerParams);
113 37
    }
114
115
    /**
116
     * {@inheritdoc}
117
     */
118 40
    public function process(ContainerBuilder $container)
119
    {
120 40
        foreach ($this->migrationConfigurationsServices as $services) {
121
            list($configurationService, $configuration) = $services;
122
            /** @var Definition $configurationService */
123
            /** @var DoctrineMigrationConfiguration $configuration */
124
            $versions = $this->getPredefinedMigrations($container, $configuration, $this->fakeConnection);
125
            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...
126
                $configurationService->addMethodCall('registerMigrations', [$versions]);
127
            }
128
        }
129 40
    }
130
131
    /**
132
     * @param string $checkName
133
     * @param string $group
134
     * @param array  $values
135
     */
136 37
    private function setParameters(ContainerBuilder $container, $checkName, $group, $values)
137
    {
138 37
        $prefix = sprintf('%s.check.%s', $this->getAlias(), $checkName);
139 37
        switch ($checkName) {
140 37
            case 'class_exists':
141 36
            case 'cpu_performance':
142 35
            case 'php_extensions':
143 32
            case 'php_version':
144 31
            case 'php_flags':
145 30
            case 'readable_directory':
146 29
            case 'writable_directory':
147 28
            case 'process_running':
148 24
            case 'doctrine_dbal':
149 20
            case 'doctrine_mongodb':
150 20
            case 'http_service':
151 19
            case 'guzzle_http_service':
152 18
            case 'memcache':
153 17
            case 'memcached':
154 17
            case 'redis':
155 16
            case 'rabbit_mq':
156 15
            case 'stream_wrapper_exists':
157 14
            case 'file_ini':
158 13
            case 'file_json':
159 12
            case 'file_xml':
160 11
            case 'file_yaml':
161 10
            case 'expressions':
162 28
                $container->setParameter($prefix.'.'.$group, $values);
163 28
                break;
164
165 9
            case 'symfony_version':
166 1
                break;
167
168 8
            case 'opcache_memory':
169 1
                if (!class_exists('ZendDiagnostics\Check\OpCacheMemory')) {
170
                    throw new \InvalidArgumentException('Please require at least "v1.0.4" of "ZendDiagnostics"');
171
                }
172 1
                break;
173
174 7
            case 'doctrine_migrations':
175
                if (!class_exists('ZendDiagnostics\Check\DoctrineMigration')) {
176
                    throw new \InvalidArgumentException('Please require at least "v1.0.6" of "ZendDiagnostics"');
177
                }
178
179
                if (!class_exists('Doctrine\Migrations\Configuration\Configuration')) {
180
                    throw new \InvalidArgumentException('Please require at least "v2.0.0" of "Doctrine Migrations Library"');
181
                }
182
183
                $container->setParameter($prefix.'.'.$group, $values);
184
                break;
185
186 7
            case 'pdo_connections':
187 1
                if (!class_exists('ZendDiagnostics\Check\PDOCheck')) {
188
                    throw new \InvalidArgumentException('Please require at least "v1.0.5" of "ZendDiagnostics"');
189
                }
190 1
                $container->setParameter($prefix.'.'.$group, $values);
191 1
                break;
192
        }
193
194 37
        if (is_array($values)) {
195 33
            foreach ($values as $key => $value) {
196 33
                $container->setParameter($prefix.'.'.$key.'.'.$group, $value);
197
            }
198
        }
199 37
    }
200
201
    /**
202
     * Set up doctrine migration configuration services.
203
     *
204
     * @param ContainerBuilder $container The container
205
     * @param array            $params    Container params
206
     *
207
     * @return void
208
     */
209 37
    private function configureDoctrineMigrationsCheck(ContainerBuilder $container, array $params)
210
    {
211 37
        if (!$container->hasDefinition('liip_monitor.check.doctrine_migrations') || !isset($params['groups'])) {
212 37
            return;
213
        }
214
215
        foreach ($params['groups'] as $groupName => $groupChecks) {
216
            if (!isset($groupChecks['doctrine_migrations'])) {
217
                continue;
218
            }
219
220
            $services = [];
221
            foreach ($groupChecks['doctrine_migrations'] as $key => $config) {
222
                try {
223
                    $serviceConfiguration =
224
                        $this->createMigrationConfigurationService($container, $config['connection'], $config['configuration_file'] ?? null);
225
226
                    $serviceId = sprintf('liip_monitor.check.doctrine_migrations.configuration.%s.%s', $groupName, $key);
227
                    $container->setDefinition($serviceId, $serviceConfiguration);
228
229
                    $services[$key] = $serviceId;
230
                } 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...
231
                    throw new MigrationException(sprintf('Invalid doctrine migration check under "%s.%s": %s', $groupName, $key, $e->getMessage()), $e->getCode(), $e);
232
                }
233
            }
234
235
            $parameter = sprintf('%s.check.%s.%s', $this->getAlias(), 'doctrine_migrations', $groupName);
236
            $container->setParameter($parameter, $services);
237
        }
238
    }
239
240 44
    private function configureMailer(ContainerBuilder $container, LoaderInterface $loader, array $config)
241
    {
242 44
        if (false === $config['mailer']['enabled']) {
243 43
            return;
244
        }
245
246
        try {
247 3
            $mailerDefinition = $container->findDefinition('mailer');
248 1
        } catch (ServiceNotFoundException $e) {
249 1
            throw new \InvalidArgumentException('To enable mail reporting you have to install the "swiftmailer/swiftmailer" or "symfony/mailer".');
250
        }
251
252 2
        $filename = \Swift_Mailer::class !== $mailerDefinition->getClass() ? 'symfony_mailer.xml' : 'swift_mailer.xml';
253 2
        $loader->load($filename);
254
255 2
        foreach ($config['mailer'] as $key => $value) {
256 2
            $container->setParameter(sprintf('%s.mailer.%s', $this->getAlias(), $key), $value);
257
        }
258 2
    }
259
260
    /**
261
     * Return key-value array with migration version as key and class as a value defined in config file.
262
     *
263
     * @param ContainerBuilder               $container  The container
264
     * @param DoctrineMigrationConfiguration $config     Current configuration
265
     * @param Connection                     $connection Fake connections
266
     *
267
     * @return array[]
268
     */
269
    private function getPredefinedMigrations(ContainerBuilder $container, DoctrineMigrationConfiguration $config, Connection $connection)
270
    {
271
        $result = [];
272
273
        $diff = new LiipMigrationConfiguration($connection);
274
275
        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...
276
            $diff->setMigrationsNamespace($config->getMigrationsNamespace());
277
        }
278
279
        if ($dir = $config->getMigrationsDirectory()) {
280
            $diff->setMigrationsDirectory($dir);
281
        }
282
283
        $diff->setContainer($container);
284
        $diff->configure();
285
286
        foreach ($config->getMigrations() as $version) {
287
            $result[$version->getVersion()] = get_class($version->getMigration());
288
        }
289
290
        foreach ($diff->getAvailableVersions() as $version) {
291
            unset($result[$version]);
292
        }
293
294
        return $result;
295
    }
296
297
    /**
298
     * Creates migration configuration service definition.
299
     *
300
     * @param ContainerBuilder $container      DI Container
301
     * @param string           $connectionName Connection name for container service
302
     * @param string           $filename       File name with migration configuration
303
     *
304
     * @return DefinitionDecorator|ChildDefinition
305
     */
306
    private function createMigrationConfigurationService(ContainerBuilder $container, string $connectionName, string $filename = null)
307
    {
308
        $configuration = $this->createTemporaryConfiguration($container, $this->fakeConnection, $filename);
309
310
        $configurationServiceName = 'liip_monitor.check.doctrine_migrations.abstract_configuration';
311
        $serviceConfiguration = class_exists('Symfony\Component\DependencyInjection\ChildDefinition')
312
            ? new ChildDefinition($configurationServiceName)
313
            : new DefinitionDecorator($configurationServiceName)
314
        ;
315
316
        $this->migrationConfigurationsServices[] = [$serviceConfiguration, $configuration];
317
318
        $serviceConfiguration->replaceArgument(
319
            0,
320
            new Reference(sprintf('doctrine.dbal.%s_connection', $connectionName))
321
        );
322
323
        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...
324
            $serviceConfiguration->addMethodCall(
325
                'setMigrationsNamespace',
326
                [$configuration->getMigrationsNamespace()]
327
            );
328
        }
329
330
        if ($configuration->getMigrationsTableName()) {
331
            $serviceConfiguration->addMethodCall(
332
                'setMigrationsTableName',
333
                [$configuration->getMigrationsTableName()]
334
            );
335
        }
336
337
        if ($configuration->getMigrationsColumnName()) {
338
            $serviceConfiguration->addMethodCall(
339
                'setMigrationsColumnName',
340
                [$configuration->getMigrationsColumnName()]
341
            );
342
        }
343
344
        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...
345
            $serviceConfiguration->addMethodCall('setName', [$configuration->getName()]);
346
        }
347
348
        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...
349
            $directory = $configuration->getMigrationsDirectory();
350
            $pathPlaceholders = ['kernel.root_dir', 'kernel.cache_dir', 'kernel.logs_dir'];
351
            foreach ($pathPlaceholders as $parameter) {
352
                $kernelDir = realpath($container->getParameter($parameter));
353
                if (0 === strpos(realpath($directory), $kernelDir)) {
354
                    $directory = str_replace($kernelDir, "%{$parameter}%", $directory);
355
                    break;
356
                }
357
            }
358
359
            $serviceConfiguration->addMethodCall(
360
                'setMigrationsDirectory',
361
                [$directory]
362
            );
363
        }
364
365
        $serviceConfiguration->addMethodCall('configure', []);
366
367
        if ($configuration->areMigrationsOrganizedByYear()) {
368
            $serviceConfiguration->addMethodCall('setMigrationsAreOrganizedByYear', [true]);
369
370
            return $serviceConfiguration;
371
        } elseif ($configuration->areMigrationsOrganizedByYearAndMonth()) {
372
            $serviceConfiguration->addMethodCall('setMigrationsAreOrganizedByYearAndMonth', [true]);
373
374
            return $serviceConfiguration;
375
        }
376
377
        return $serviceConfiguration;
378
    }
379
380
    /**
381
     * Creates in-memory migration configuration for setting up container service.
382
     *
383
     * @param ContainerBuilder $container  The container
384
     * @param Connection       $connection Fake connection
385
     * @param string           $filename   Migrations configuration file
386
     */
387
    private function createTemporaryConfiguration(
388
        ContainerBuilder $container,
389
        Connection $connection,
390
        string $filename = null
391
    ): DoctrineMigrationConfiguration {
392
        if (null === $filename) {
393
            // this is configured from migrations bundle
394
            return new DoctrineMigrationConfiguration($connection);
395
        }
396
397
        // -------
398
        // This part must be in sync with Doctrine\Migrations\Tools\Console\Helper\ConfigurationHelper::loadConfig
399
        $map = [
400
            'xml' => '\XmlConfiguration',
401
            'yaml' => '\YamlConfiguration',
402
            'yml' => '\YamlConfiguration',
403
            'php' => '\ArrayConfiguration',
404
            'json' => '\JsonConfiguration',
405
        ];
406
        // --------
407
408
        $filename = $container->getParameterBag()->resolveValue($filename);
409
        $info = pathinfo($filename);
410
        // check we can support this file type
411
        if (empty($map[$info['extension']])) {
412
            throw new \InvalidArgumentException('Given config file type is not supported');
413
        }
414
415
        $class = 'Doctrine\Migrations\Configuration';
416
        $class .= $map[$info['extension']];
417
        // -------
418
419
        /** @var AbstractFileConfiguration $configuration */
420
        $configuration = new $class($connection);
421
        $configuration->load($filename);
422
        $configuration->validate();
423
424
        return $configuration;
425
    }
426
}
427