Completed
Pull Request — master (#219)
by Markus
11:05
created

LiipMonitorExtension   F

Complexity

Total Complexity 80

Size/Duplication

Total Lines 420
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 17

Importance

Changes 0
Metric Value
wmc 80
lcom 1
cbo 17
dl 0
loc 420
rs 2
c 0
b 0
f 0

9 Methods

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