Completed
Push — master ( 6c6728...1d94a7 )
by Lukas Kahwe
11s
created

LiipMonitorExtension::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
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
    public function __construct()
38
    {
39
        if (class_exists(Connection::class)) {
40
            $this->fakeConnection = new Connection([], new Driver());
41
        }
42
    }
43
44
    /**
45
     * Loads the services based on your application configuration.
46
     *
47
     * @param array            $configs
48
     * @param ContainerBuilder $container
49
     */
50
    public function load(array $configs, ContainerBuilder $container)
51
    {
52
        $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
53
        $loader->load('runner.xml');
54
        $loader->load('helper.xml');
55
        $loader->load('commands.xml');
56
57
        $configuration = new Configuration();
58
        $config = $this->processConfiguration($configuration, $configs);
59
60
        if (null === $config['view_template']) {
61
            $config['view_template'] = __DIR__.'/../Resources/views/health/index.html.php';
62
        }
63
64
        if ($config['enable_controller']) {
65
            $container->setParameter(sprintf('%s.view_template', $this->getAlias()), $config['view_template']);
66
            $loader->load('controller.xml');
67
        }
68
69
        if ($config['mailer']['enabled']) {
70
            $loader->load('swift_mailer.xml');
71
72
            foreach ($config['mailer'] as $key => $value) {
73
                $container->setParameter(sprintf('%s.mailer.%s', $this->getAlias(), $key), $value);
74
            }
75
        }
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
                continue;
171
172
            case 'symfony_version':
173
                continue;
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
                continue;
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\DBAL\Migrations\Configuration\Configuration')) {
187
                    throw new \InvalidArgumentException('Please require at least "v1.1.0" of "Doctrine Migrations Library"');
188
                }
189
190
                $container->setParameter($prefix.'.'.$group, $values);
191
                continue;
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
                continue;
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) {
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
    /**
258
     * Return key-value array with migration version as key and class as a value defined in config file
259
     *
260
     * @param ContainerBuilder               $container  The container
261
     * @param DoctrineMigrationConfiguration $config     Current configuration
262
     * @param Connection                     $connection Fake connections
263
     *
264
     * @return array[]
265
     */
266
    private function getPredefinedMigrations(ContainerBuilder $container, DoctrineMigrationConfiguration $config, Connection $connection)
267
    {
268
        $result = array();
269
270
        $diff = new LiipMigrationConfiguration($connection);
271
        $diff->setMigrationsNamespace($config->getMigrationsNamespace());
272
        $diff->setMigrationsDirectory($config->getMigrationsDirectory());
273
        $diff->setContainer($container);
274
        $diff->configure();
275
276
        foreach ($config->getMigrations() as $version) {
277
            $result[$version->getVersion()] = get_class($version->getMigration());
278
        }
279
280
        foreach ($diff->getAvailableVersions() as $version) {
281
            unset($result[$version]);
282
        }
283
284
        return $result;
285
    }
286
287
    /**
288
     * Creates migration configuration service definition
289
     *
290
     * @param ContainerBuilder $container      DI Container
291
     * @param string           $connectionName Connection name for container service
292
     * @param string           $filename       File name with migration configuration
293
     *
294
     * @return DefinitionDecorator|ChildDefinition
295
     */
296
    private function createMigrationConfigurationService(ContainerBuilder $container, string $connectionName, string $filename = null)
297
    {
298
        $configuration = $this->createTemporaryConfiguration($container, $this->fakeConnection, $filename);
299
300
        $configurationServiceName = 'liip_monitor.check.doctrine_migrations.abstract_configuration';
301
        $serviceConfiguration = class_exists('Symfony\Component\DependencyInjection\ChildDefinition')
302
            ? new ChildDefinition($configurationServiceName)
303
            : new DefinitionDecorator($configurationServiceName)
304
        ;
305
306
        $this->migrationConfigurationsServices[] = [$serviceConfiguration, $configuration];
307
308
        $serviceConfiguration->replaceArgument(
309
            0,
310
            new Reference(sprintf('doctrine.dbal.%s_connection', $connectionName))
311
        );
312
313
        if ($configuration->getMigrationsNamespace()) {
314
            $serviceConfiguration->addMethodCall(
315
                'setMigrationsNamespace',
316
                [ $configuration->getMigrationsNamespace() ]
317
            );
318
        }
319
320
        if ($configuration->getMigrationsTableName()) {
321
            $serviceConfiguration->addMethodCall(
322
                'setMigrationsTableName',
323
                [ $configuration->getMigrationsTableName() ]
324
            );
325
        }
326
327
        if ($configuration->getMigrationsColumnName()) {
328
            $serviceConfiguration->addMethodCall(
329
                'setMigrationsColumnName',
330
                [ $configuration->getMigrationsColumnName() ]
331
            );
332
        }
333
334
        if ($configuration->getName()) {
335
            $serviceConfiguration->addMethodCall('setName', [ $configuration->getName() ]);
336
        }
337
338
        if ($configuration->getMigrationsDirectory()) {
339
            $directory        = $configuration->getMigrationsDirectory();
340
            $pathPlaceholders = array('kernel.root_dir', 'kernel.cache_dir', 'kernel.logs_dir');
341
            foreach ($pathPlaceholders as $parameter) {
342
                $kernelDir = realpath($container->getParameter($parameter));
343
                if (strpos(realpath($directory), $kernelDir) === 0) {
344
                    $directory = str_replace($kernelDir, "%{$parameter}%", $directory);
345
                    break;
346
                }
347
            }
348
349
            $serviceConfiguration->addMethodCall(
350
                'setMigrationsDirectory',
351
                [ $directory ]
352
            );
353
        }
354
355
        $serviceConfiguration->addMethodCall('configure', []);
356
357
        if ($configuration->areMigrationsOrganizedByYear()) {
358
            $serviceConfiguration->addMethodCall('setMigrationsAreOrganizedByYear', [ true ]);
359
360
            return $serviceConfiguration;
361
        } elseif ($configuration->areMigrationsOrganizedByYearAndMonth()) {
362
            $serviceConfiguration->addMethodCall('setMigrationsAreOrganizedByYearAndMonth', [ true ]);
363
364
            return $serviceConfiguration;
365
        }
366
367
        return $serviceConfiguration;
368
    }
369
370
    /**
371
     * Creates in-memory migration configuration for setting up container service
372
     *
373
     * @param ContainerBuilder $container  The container
374
     * @param Connection       $connection Fake connection
375
     * @param string           $filename   Migrations configuration file
376
     *
377
     * @return DoctrineMigrationConfiguration
378
     */
379
    private function createTemporaryConfiguration(
380
        ContainerBuilder $container,
381
        Connection $connection,
382
        string $filename = null
383
    ): DoctrineMigrationConfiguration {
384
        if ($filename === null) {
385
            // this is configured from migrations bundle
386
            return new DoctrineMigrationConfiguration($connection);
387
        }
388
389
        // -------
390
        // This part must be in sync with Doctrine\DBAL\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\DBAL\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