Completed
Pull Request — master (#227)
by Carlos
05:59
created

createTemporaryConfiguration()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 39

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 39
ccs 0
cts 14
cp 0
rs 9.296
c 0
b 0
f 0
cc 3
nc 3
nop 3
crap 12
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
            $config['mailer'] = [
243
                'enabled' => false,
244
            ];
245
        }
246
247 44
        foreach ($config['mailer'] as $key => $value) {
248 44
            $container->setParameter(sprintf('%s.mailer.%s', $this->getAlias(), $key), $value);
249
        }
250 44
    }
251
252
    /**
253
     * Return key-value array with migration version as key and class as a value defined in config file.
254
     *
255
     * @param ContainerBuilder               $container  The container
256
     * @param DoctrineMigrationConfiguration $config     Current configuration
257
     * @param Connection                     $connection Fake connections
258
     *
259
     * @return array[]
260
     */
261
    private function getPredefinedMigrations(ContainerBuilder $container, DoctrineMigrationConfiguration $config, Connection $connection)
262
    {
263
        $result = [];
264
265
        $diff = new LiipMigrationConfiguration($connection);
266
267
        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...
268
            $diff->setMigrationsNamespace($config->getMigrationsNamespace());
269
        }
270
271
        if ($dir = $config->getMigrationsDirectory()) {
272
            $diff->setMigrationsDirectory($dir);
273
        }
274
275
        $diff->setContainer($container);
276
        $diff->configure();
277
278
        foreach ($config->getMigrations() as $version) {
279
            $result[$version->getVersion()] = get_class($version->getMigration());
280
        }
281
282
        foreach ($diff->getAvailableVersions() as $version) {
283
            unset($result[$version]);
284
        }
285
286
        return $result;
287
    }
288
289
    /**
290
     * Creates migration configuration service definition.
291
     *
292
     * @param ContainerBuilder $container      DI Container
293
     * @param string           $connectionName Connection name for container service
294
     * @param string           $filename       File name with migration configuration
295
     *
296
     * @return DefinitionDecorator|ChildDefinition
297
     */
298
    private function createMigrationConfigurationService(ContainerBuilder $container, string $connectionName, string $filename = null)
299
    {
300
        $configuration = $this->createTemporaryConfiguration($container, $this->fakeConnection, $filename);
301
302
        $configurationServiceName = 'liip_monitor.check.doctrine_migrations.abstract_configuration';
303
        $serviceConfiguration = class_exists('Symfony\Component\DependencyInjection\ChildDefinition')
304
            ? new ChildDefinition($configurationServiceName)
305
            : new DefinitionDecorator($configurationServiceName)
306
        ;
307
308
        $this->migrationConfigurationsServices[] = [$serviceConfiguration, $configuration];
309
310
        $serviceConfiguration->replaceArgument(
311
            0,
312
            new Reference(sprintf('doctrine.dbal.%s_connection', $connectionName))
313
        );
314
315
        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...
316
            $serviceConfiguration->addMethodCall(
317
                'setMigrationsNamespace',
318
                [$configuration->getMigrationsNamespace()]
319
            );
320
        }
321
322
        if ($configuration->getMigrationsTableName()) {
323
            $serviceConfiguration->addMethodCall(
324
                'setMigrationsTableName',
325
                [$configuration->getMigrationsTableName()]
326
            );
327
        }
328
329
        if ($configuration->getMigrationsColumnName()) {
330
            $serviceConfiguration->addMethodCall(
331
                'setMigrationsColumnName',
332
                [$configuration->getMigrationsColumnName()]
333
            );
334
        }
335
336
        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...
337
            $serviceConfiguration->addMethodCall('setName', [$configuration->getName()]);
338
        }
339
340
        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...
341
            $directory = $configuration->getMigrationsDirectory();
342
            $pathPlaceholders = ['kernel.root_dir', 'kernel.cache_dir', 'kernel.logs_dir'];
343
            foreach ($pathPlaceholders as $parameter) {
344
                $kernelDir = realpath($container->getParameter($parameter));
345
                if (0 === strpos(realpath($directory), $kernelDir)) {
346
                    $directory = str_replace($kernelDir, "%{$parameter}%", $directory);
347
                    break;
348
                }
349
            }
350
351
            $serviceConfiguration->addMethodCall(
352
                'setMigrationsDirectory',
353
                [$directory]
354
            );
355
        }
356
357
        $serviceConfiguration->addMethodCall('configure', []);
358
359
        if ($configuration->areMigrationsOrganizedByYear()) {
360
            $serviceConfiguration->addMethodCall('setMigrationsAreOrganizedByYear', [true]);
361
362
            return $serviceConfiguration;
363
        } elseif ($configuration->areMigrationsOrganizedByYearAndMonth()) {
364
            $serviceConfiguration->addMethodCall('setMigrationsAreOrganizedByYearAndMonth', [true]);
365
366
            return $serviceConfiguration;
367
        }
368
369
        return $serviceConfiguration;
370
    }
371
372
    /**
373
     * Creates in-memory migration configuration for setting up container service.
374
     *
375
     * @param ContainerBuilder $container  The container
376
     * @param Connection       $connection Fake connection
377
     * @param string           $filename   Migrations configuration file
378
     */
379
    private function createTemporaryConfiguration(
380
        ContainerBuilder $container,
381
        Connection $connection,
382
        string $filename = null
383
    ): DoctrineMigrationConfiguration {
384
        if (null === $filename) {
385
            // this is configured from migrations bundle
386
            return new DoctrineMigrationConfiguration($connection);
387
        }
388
389
        // -------
390
        // This part must be in sync with Doctrine\Migrations\Tools\Console\Helper\ConfigurationHelper::loadConfig
391
        $map = [
392
            'xml' => '\XmlConfiguration',
393
            'yaml' => '\YamlConfiguration',
394
            'yml' => '\YamlConfiguration',
395
            'php' => '\ArrayConfiguration',
396
            'json' => '\JsonConfiguration',
397
        ];
398
        // --------
399
400
        $filename = $container->getParameterBag()->resolveValue($filename);
401
        $info = pathinfo($filename);
402
        // check we can support this file type
403
        if (empty($map[$info['extension']])) {
404
            throw new \InvalidArgumentException('Given config file type is not supported');
405
        }
406
407
        $class = 'Doctrine\Migrations\Configuration';
408
        $class .= $map[$info['extension']];
409
        // -------
410
411
        /** @var AbstractFileConfiguration $configuration */
412
        $configuration = new $class($connection);
413
        $configuration->load($filename);
414
        $configuration->validate();
415
416
        return $configuration;
417
    }
418
}
419