Completed
Push — master ( 033231...4a0dad )
by Kevin
12s queued 10s
created

configureDoctrineMigrationsCheck()   B

Complexity

Conditions 7
Paths 9

Size

Total Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 34.3644

Importance

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