Completed
Push — master ( 7e7d2b...6c6728 )
by Lukas Kahwe
9s
created

LiipMonitorExtension::process()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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