Passed
Push — master ( f8e7b1...27dd84 )
by Gaetano
08:24
created

GenerateCommand::generateMigrationFile()   C

Complexity

Conditions 12
Paths 23

Size

Total Lines 69
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 19.8021

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 12
eloc 43
nc 23
nop 5
dl 0
loc 69
ccs 23
cts 37
cp 0.6216
crap 19.8021
rs 6.9666
c 2
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Kaliop\eZMigrationBundle\Command;
4
5
use Symfony\Component\Console\Input\InputArgument;
6
use Symfony\Component\Console\Input\InputInterface;
7
use Symfony\Component\Console\Input\InputOption;
8
use Symfony\Component\Console\Output\OutputInterface;
9
use Symfony\Component\HttpFoundation\File\Exception\FileException;
10
use Symfony\Component\Yaml\Yaml;
11
use Kaliop\eZMigrationBundle\API\MigrationGeneratorInterface;
12
use Kaliop\eZMigrationBundle\API\MatcherInterface;
13
use Kaliop\eZMigrationBundle\API\EnumerableMatcherInterface;
14
use Kaliop\eZMigrationBundle\API\Event\MigrationGeneratedEvent;
15
16
class GenerateCommand extends AbstractCommand
17
{
18
    const DIR_CREATE_PERMISSIONS = 0755;
19
20
    private $availableMigrationFormats = array('yml', 'php', 'sql', 'json');
21
    private $availableModes = array('create', 'update', 'delete');
22
    private $availableTypes = array('content', 'content_type', 'content_type_group', 'language', 'object_state', 'object_state_group', 'role', 'section', 'generic', 'db', 'php', '...');
23
    private $thisBundle = 'EzMigrationBundle';
24
25
    protected $eventName = 'ez_migration.migration_generated';
26
27 94
    /**
28
     * Configure the console command
29 94
     */
30 94
    protected function configure()
31 94
    {
32 94
        $this->setName('kaliop:migration:generate')
33 94
            ->setDescription('Generate a blank migration definition file.')
34 94
            ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The format of migration file to generate (' . implode(', ', $this->availableMigrationFormats) . ')', 'yml')
35 94
            ->addOption('type', null, InputOption::VALUE_REQUIRED, 'The type of migration to generate (' . implode(', ', $this->availableTypes) . ')', '')
36 94
            ->addOption('mode', null, InputOption::VALUE_REQUIRED, 'The mode of the migration (' . implode(', ', $this->availableModes) . ')', 'create')
37 94
            ->addOption('match-type', null, InputOption::VALUE_REQUIRED, 'The type of identifier used to find the entity to generate the migration for', null)
38 94
            ->addOption('match-value', null, InputOption::VALUE_REQUIRED, 'The identifier value used to find the entity to generate the migration for. Can have many values separated by commas', null)
39 94
            ->addOption('match-except', null, InputOption::VALUE_NONE, 'Used to match all entities except the ones satisfying the match-value condition', null)
40 94
            ->addOption('lang', 'l', InputOption::VALUE_REQUIRED, 'The language of the migration (eng-GB, ger-DE, ...). If null, the default language of the current siteaccess is used')
41 94
            ->addOption('dbserver', null, InputOption::VALUE_REQUIRED, 'The type of the database server the sql migration is for, when type=db (mysql, postgresql, ...)', 'mysql')
42 94
            ->addOption('admin-login', 'a', InputOption::VALUE_REQUIRED, "Login of admin account used whenever elevated privileges are needed (user id 14 used by default)")
43 94
            ->addOption('list-types', null, InputOption::VALUE_NONE, 'Use this option to list all available migration types and their match conditions')
44 94
            ->addArgument('bundle', InputArgument::OPTIONAL, 'The bundle to generate the migration definition file in. eg.: AcmeMigrationBundle')
45
            ->addArgument('name', InputArgument::OPTIONAL, 'The migration name (will be prefixed with current date)', null)
46
            ->setHelp(<<<EOT
47
The <info>kaliop:migration:generate</info> command generates a skeleton migration definition file:
48
49
    <info>php ezpublish/console kaliop:migration:generate bundleName</info>
50
51
You can optionally specify the file type to generate with <info>--format</info>, as well a name for the migration:
52
53
    <info>php ezpublish/console kaliop:migration:generate --format=json bundleName migrationName</info>
54
55
For SQL type migration you can optionally specify the database server type the migration is for with <info>--dbserver</info>:
56
57
    <info>php ezpublish/console kaliop:migration:generate --format=sql bundleName</info>
58
59
For content/content_type/language/object_state/role/section migrations you need to specify the entity that you want to generate the migration for:
60
61
    <info>php ezpublish/console kaliop:migration:generate --type=content --match-type=content_id --match-value=10,14 bundleName</info>
62
63
For role type migration you will receive a yaml file with the current role definition. You must define ALL the policies
64
you wish for the role. Any not defined will be removed. Example for updating an existing role:
65
66
    <info>php ezpublish/console kaliop:migration:generate --type=role --mode=update --match-type=identifier --match-value=Anonymous bundleName</info>
67
68
For freeform php migrations, you will receive a php class definition
69
70
    <info>php ezpublish/console kaliop:migration:generate --format=php bundlename classname</info>
71
72
To list all available migration types for generation, as well as the corresponding match-types, run:
73
74
    <info>php ezpublish/console ka:mig:gen --list-types</info>
75
76
Note that you can pass in a custom directory path instead of a bundle name, but, if you do, you will have to use the <info>--path</info>
77 94
option when you run the <info>migrate</info> command.
78
EOT
79
            );
80
    }
81
82
    /**
83
     * Run the command and display the results.
84
     *
85
     * @param InputInterface $input
86
     * @param OutputInterface $output
87 34
     * @return null|int null or 0 if everything went fine, or an error code
88
     * @throws \InvalidArgumentException When an unsupported file type is selected
89 34
     *
90 34
     * @todo for type=db, we could fold 'dbserver' option into 'mode'
91
     */
92 34
    public function execute(InputInterface $input, OutputInterface $output)
93
    {
94
        $this->setOutput($output);
95
        $this->setVerbosity($output->getVerbosity());
96
97 34
        if ($input->getOption('list-types')) {
98 34
            $this->listAvailableTypes($output);
99
            return 0;
100
        }
101
102 34
        $bundleName = $input->getArgument('bundle');
103 34
        if ($bundleName === null) {
104 34
            // throw same exception as SF would when declaring 'bundle' as mandatory arg
105 34
            throw new \RuntimeException('Not enough arguments (missing: "bundle").');
106 34
        }
107 34
        $name = $input->getArgument('name');
108 34
        $fileType = $input->getOption('format');
109 34
        $migrationType = $input->getOption('type');
110
        $matchType = $input->getOption('match-type');
111 34
        $matchValue = $input->getOption('match-value');
112
        $matchExcept = $input->getOption('match-except');
113
        $mode = $input->getOption('mode');
114
        $dbServer = $input->getOption('dbserver');
115
116 34
        if ($bundleName == $this->thisBundle) {
117 6
            throw new \InvalidArgumentException("It is not allowed to create migrations in bundle '$bundleName'");
118 2
        }
119 4
120 2
        // be kind to lazy users
121
        if ($migrationType == '') {
122 2
            if ($fileType == 'sql') {
123
                $migrationType = 'db';
124
            } elseif ($fileType == 'php') {
125
                $migrationType = 'php';
126 34
            } else {
127
                $migrationType = 'generic';
128
            }
129
        }
130 34
131
        if (!in_array($fileType, $this->availableMigrationFormats)) {
132
            throw new \InvalidArgumentException('Unsupported migration file format ' . $fileType);
133
        }
134 34
135
        if (!in_array($mode, $this->availableModes)) {
136 34
            throw new \InvalidArgumentException('Unsupported migration mode ' . $mode);
137 1
        }
138 1
139 1
        $migrationDirectory = $this->getMigrationDirectory($bundleName);
0 ignored issues
show
Bug introduced by
It seems like $bundleName can also be of type string[]; however, parameter $bundleName of Kaliop\eZMigrationBundle...getMigrationDirectory() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

139
        $migrationDirectory = $this->getMigrationDirectory(/** @scrutinizer ignore-type */ $bundleName);
Loading history...
140
141
        if (!is_dir($migrationDirectory)) {
142 1
            $output->writeln(sprintf(
143 1
                "Migrations directory <info>%s</info> does not exist. I will create it now....",
144 1
                $migrationDirectory
145 1
            ));
146
147
            if (mkdir($migrationDirectory, self::DIR_CREATE_PERMISSIONS, true)) {
148
                $output->writeln(sprintf(
149
                    "Migrations directory <info>%s</info> has been created",
150
                    $migrationDirectory
151
                ));
152
            } else {
153
                throw new FileException(sprintf(
154
                    "Failed to create migrations directory %s.",
155
                    $migrationDirectory
156 34
                ));
157 2
            }
158
        }
159
160
        // allow to generate migrations for many entities
161 34
        if (strpos($matchValue, ',') !== false ) {
0 ignored issues
show
Bug introduced by
It seems like $matchValue can also be of type string[]; however, parameter $haystack of strpos() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

161
        if (strpos(/** @scrutinizer ignore-type */ $matchValue, ',') !== false ) {
Loading history...
162 34
            $matchValue = explode(',', $matchValue);
0 ignored issues
show
Bug introduced by
It seems like $matchValue can also be of type string[]; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

162
            $matchValue = explode(',', /** @scrutinizer ignore-type */ $matchValue);
Loading history...
163 34
        }
164 34
165 34
        $parameters = array(
166 34
            'type' => $migrationType,
167 34
            'mode' => $mode,
168
            'matchType' => $matchType,
169
            'matchValue' => $matchValue,
170
            'matchExcept' => $matchExcept,
171
            'dbserver' => $dbServer,
172 34
            /// @todo move these 2 params out of here, pass the context as template parameter instead
173
            'lang' => $input->getOption('lang'),
174
            'adminLogin' => $input->getOption('admin-login')
175 34
            /// @todo should we allow users to specify this ?
176
            //'forceSigchildEnabled' => null
177 2
        );
178 1
179
        $date = date('YmdHis');
180 2
181 2
        switch ($fileType) {
182
            case 'sql':
183 32
                /// @todo this logic should come from the DefinitionParser, really
184
                if ($name != '') {
185 2
                    $name = '_' . ltrim($name, '_');
0 ignored issues
show
Bug introduced by
It seems like $name can also be of type string[]; however, parameter $str of ltrim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

185
                    $name = '_' . ltrim(/** @scrutinizer ignore-type */ $name, '_');
Loading history...
186 2
                }
187 1
                $fileName = $date . '_' . $dbServer . $name . '.sql';
188
                break;
189
190 2
            case 'php':
191 2
                /// @todo this logic should come from the DefinitionParser, really
192
                $className = ltrim($name, '_');
193
                if ($className == '') {
194 2
                    $className = 'Migration';
195 2
                }
196
                // Make sure that php class names are unique, not only migration definition file names
197 2
                $existingMigrations = count(glob($migrationDirectory . '/*_' . $className . '*.php'));
0 ignored issues
show
Bug introduced by
It seems like glob($migrationDirectory.... $className . '*.php') can also be of type false; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

197
                $existingMigrations = count(/** @scrutinizer ignore-type */ glob($migrationDirectory . '/*_' . $className . '*.php'));
Loading history...
198 2
                if ($existingMigrations) {
199
                    $className = $className . sprintf('%03d', $existingMigrations + 1);
200
                }
201 30
                $parameters = array_merge($parameters, array(
202 1
                    'class_name' => $className
203
                ));
204 30
                $fileName = $date . '_' . $className . '.php';
205
                break;
206
207 34
            default:
208
                if ($name == '') {
209 34
                    $name = 'placeholder';
210
                }
211 34
                $fileName = $date . '_' . $name . '.' . $fileType;
0 ignored issues
show
Bug introduced by
Are you sure $name of type null|string|string[] can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

211
                $fileName = $date . '_' . /** @scrutinizer ignore-type */ $name . '.' . $fileType;
Loading history...
212
        }
213 34
214
        $filePath = $migrationDirectory . '/' . $fileName;
215
216
        $warning = $this->generateMigrationFile($migrationType, $mode, $fileType, $filePath, $parameters);
0 ignored issues
show
Bug introduced by
It seems like $migrationType can also be of type string[]; however, parameter $migrationType of Kaliop\eZMigrationBundle...generateMigrationFile() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

216
        $warning = $this->generateMigrationFile(/** @scrutinizer ignore-type */ $migrationType, $mode, $fileType, $filePath, $parameters);
Loading history...
Bug introduced by
It seems like $mode can also be of type string[]; however, parameter $migrationMode of Kaliop\eZMigrationBundle...generateMigrationFile() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

216
        $warning = $this->generateMigrationFile($migrationType, /** @scrutinizer ignore-type */ $mode, $fileType, $filePath, $parameters);
Loading history...
217 34
218
        $output->writeln(sprintf("Generated new migration file: <info>%s</info>", $filePath));
219
220
        if ($warning != '') {
221
            $output->writeln("<comment>$warning</comment>");
222
        }
223
224
        return 0;
225
    }
226
227
    /**
228
     * Generates a migration definition file.
229
     * @todo allow non-filesystem storage (delegate saving to a service, just as we do for loading)
230
     *
231 34
     * @param string $migrationType The type of migration to generate
232
     * @param string $migrationMode
233 34
     * @param string $fileType The type of migration file to generate
234
     * @param string $filePath filename to file to generate (full path)
235
     * @param array $parameters passed on to twig
236 34
     * @return string A warning message in case file generation was OK but there was something weird
237 32
     * @throws \Exception
238 30
     */
239
    protected function generateMigrationFile($migrationType, $migrationMode, $fileType, $filePath, array $parameters = array())
240 6
    {
241 6
        $warning = '';
242 6
243
        switch ($migrationType) {
244
            case 'db':
245
            case 'generic':
246 6
            case 'php':
247 6
                // Generate migration file by template
248
                $template = $migrationType . 'Migration.' . $fileType . '.twig';
249
                $templatePath = $this->getApplication()->getKernel()->getBundle($this->thisBundle)->getPath() . '/Resources/views/MigrationTemplate/';
0 ignored issues
show
Bug introduced by
The method getKernel() does not exist on Symfony\Component\Console\Application. It seems like you code against a sub-type of Symfony\Component\Console\Application such as Symfony\Bundle\FrameworkBundle\Console\Application. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

249
                $templatePath = $this->getApplication()->/** @scrutinizer ignore-call */ getKernel()->getBundle($this->thisBundle)->getPath() . '/Resources/views/MigrationTemplate/';
Loading history...
250
                if (!is_file($templatePath . $template)) {
251 28
                    throw new \Exception("The combination of migration type '$migrationType' is not supported with format '$fileType'");
252 28
                }
253
254
                $code = $this->getContainer()->get('twig')->render($this->thisBundle . ':MigrationTemplate:' . $template, $parameters);
255
256 28
                // allow event handlers to replace data
257
                $event = new MigrationGeneratedEvent($migrationType, $migrationMode, $fileType, $code, $filePath);
258 28
                $this->getContainer()->get('event_dispatcher')->dispatch($this->eventName, $event);
259
                $code = $event->getData();
260 28
                $filePath = $event->getFile();
261 28
262 2
                break;
263
264 28
            default:
265
                // Generate migration file by executor
266 28
                $executors = $this->getGeneratingExecutors();
267
                if (!in_array($migrationType, $executors)) {
268
                    throw new \Exception("It is not possible to generate a migration of type '$migrationType': executor not found or not a generator");
269
                }
270
                /** @var MigrationGeneratorInterface $executor */
271 28
                $executor = $this->getMigrationService()->getExecutor($migrationType);
272 18
273 18
                $context = $this->migrationContextFromParameters($parameters);
274 10
275 10
                $matchCondition = array($parameters['matchType'] => $parameters['matchValue']);
276 10
                if ($parameters['matchExcept']) {
277
                    $matchCondition = array(MatcherInterface::MATCH_NOT => $matchCondition);
278
                }
279
                $data = $executor->generateMigration($matchCondition, $migrationMode, $context);
280
281
                // allow event handlers to replace data
282 34
                $event = new MigrationGeneratedEvent($migrationType, $migrationMode, $fileType, $data, $filePath, $matchCondition, $context);
283
                $this->getContainer()->get('event_dispatcher')->dispatch($this->eventName, $event);
284 34
                $data = $event->getData();
285
                $filePath = $event->getFile();
286
287
                if (!is_array($data) || !count($data)) {
288
                    $warning = 'Note: the generated migration is empty';
289
                }
290
291
                switch ($fileType) {
292
                    case 'yml':
293
                    case 'yaml':
294
                        /// @todo use Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE option if it is supported
295
                        $code = Yaml::dump($data, 5);
296
                        break;
297
                    case 'json':
298
                        $code = json_encode($data, JSON_PRETTY_PRINT);
299
                        break;
300
                    default:
301
                        throw new \Exception("The combination of migration type '$migrationType' is not supported with format '$fileType'");
302
                }
303
        }
304
305
        file_put_contents($filePath, $code);
306 34
307
        return $warning;
308
    }
309 34
310
    protected function listAvailableTypes(OutputInterface $output)
311
    {
312
        $output->writeln('Specific migration types available for generation (besides sql,php, generic):');
313 34
        foreach ($this->getGeneratingExecutors() as $executorType) {
314 34
            $output->writeln($executorType);
315 34
            /** @var MigrationGeneratorInterface $executor */
316
            $executor = $this->getMigrationService()->getExecutor($executorType);
317 34
            if ($executor instanceof EnumerableMatcherInterface) {
318 34
                $conditions = $executor->listAllowedConditions();
319
                $conditions = array_diff($conditions, array('and', 'or', 'not'));
320
                $output->writeln("  corresponding match types:\n    - " . implode("\n    - ", $conditions));
321
            }
322 34
        }
323 34
    }
324
325 34
    /**
326
     * @param string $bundleName a bundle name or filesystem path to a directory
327
     * @return string
328
     */
329
    protected function getMigrationDirectory($bundleName)
330
    {
331
        // Allow direct usage of a directory path instead of a bundle name
332 28
        if (strpos($bundleName, '/') !== false && is_dir($bundleName)) {
333
            return rtrim($bundleName, '/');
334 28
        }
335 28
336 28
        $activeBundles = array();
337 28
        foreach ($this->getApplication()->getKernel()->getBundles() as $bundle) {
338 28
            $activeBundles[] = $bundle->getName();
339 28
        }
340
        asort($activeBundles);
341
        if (!in_array($bundleName, $activeBundles)) {
342 28
            throw new \InvalidArgumentException("Bundle '$bundleName' does not exist or it is not enabled. Try with one of:\n" . implode(', ', $activeBundles));
343
        }
344
345
        $bundle = $this->getApplication()->getKernel()->getBundle($bundleName);
346
        $migrationDirectory = $bundle->getPath() . '/' . $this->getContainer()->get('ez_migration_bundle.helper.config.resolver')->getParameter('kaliop_bundle_migration.version_directory');
347
348
        return $migrationDirectory;
349
    }
350 28
351
    /**
352 28
     * @todo move somewhere else. Maybe to the MigrationService itself ?
353
     * @return string[]
354 28
     */
355
    protected function getGeneratingExecutors()
356
    {
357 28
        $migrationService = $this->getMigrationService();
358
        $executors = $migrationService->listExecutors();
359
        foreach($executors as $key => $name) {
360 28
            $executor = $migrationService->getExecutor($name);
361
            if (!$executor instanceof MigrationGeneratorInterface) {
362
                unset($executors[$key]);
363
            }
364
        }
365 28
        return $executors;
366
    }
367
368
    /**
369
     * @see MigrationService::migrationContextFromParameters
370
     * @param array $parameters these come directly from cli options
371
     * @return array
372
     */
373
    protected function migrationContextFromParameters(array $parameters)
374
    {
375
        $context = array();
376
377
        if (isset($parameters['lang']) && $parameters['lang'] != '') {
378
            $context['defaultLanguageCode'] = $parameters['lang'];
379
        }
380
        if (isset($parameters['adminLogin']) && $parameters['adminLogin'] != '') {
381
            $context['adminUserLogin'] = $parameters['adminLogin'];
382
        }
383
        if (isset($parameters['forceSigchildEnabled']) && $parameters['forceSigchildEnabled'] !== null)
384
        {
385
            $context['forceSigchildEnabled'] = $parameters['forceSigchildEnabled'];
386
        }
387
388
        return $context;
389
    }
390
}
391