Completed
Pull Request — master (#219)
by Kevin
01:39
created

LiipMonitorExtension::configureMailer()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

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