Passed
Push — master ( 9d6058...4098f7 )
by Gaetano
06:09
created

GenerateCommand::generateMigrationFile()   B

Complexity

Conditions 11
Paths 19

Size

Total Lines 54
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 11.7686

Importance

Changes 0
Metric Value
cc 11
eloc 34
nc 19
nop 4
dl 0
loc 54
ccs 22
cts 27
cp 0.8148
crap 11.7686
rs 7.3166
c 0
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
15
class GenerateCommand extends AbstractCommand
16
{
17
    const DIR_CREATE_PERMISSIONS = 0755;
18
19
    private $availableMigrationFormats = array('yml', 'php', 'sql', 'json');
20
    private $availableModes = array('create', 'update', 'delete');
21
    private $availableTypes = array('content', 'content_type', 'content_type_group', 'language', 'object_state', 'object_state_group', 'role', 'section', 'generic', 'db', 'php', '...');
22
    private $thisBundle = 'EzMigrationBundle';
23
24
    /**
25
     * Configure the console command
26 78
     */
27
    protected function configure()
28 78
    {
29 78
        $this->setName('kaliop:migration:generate')
30 78
            ->setDescription('Generate a blank migration definition file.')
31 78
            ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The format of migration file to generate (' . implode(', ', $this->availableMigrationFormats) . ')', 'yml')
32 78
            ->addOption('type', null, InputOption::VALUE_REQUIRED, 'The type of migration to generate (' . implode(', ', $this->availableTypes) . ')', '')
33 78
            ->addOption('mode', null, InputOption::VALUE_REQUIRED, 'The mode of the migration (' . implode(', ', $this->availableModes) . ')', 'create')
34 78
            ->addOption('match-type', null, InputOption::VALUE_REQUIRED, 'The type of identifier used to find the entity to generate the migration for', null)
35 78
            ->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)
36 78
            ->addOption('match-except', null, InputOption::VALUE_NONE, 'Used to match all entities except the ones satisfying the match-value condition', null)
37 78
            ->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')
38 78
            ->addOption('dbserver', null, InputOption::VALUE_REQUIRED, 'The type of the database server the sql migration is for, when type=db (mysql, postgresql, ...)', 'mysql')
39 78
            ->addOption('admin-login', 'a', InputOption::VALUE_REQUIRED, "Login of admin account used whenever elevated privileges are needed (user id 14 used by default)")
40 78
            ->addOption('list-types', null, InputOption::VALUE_NONE, 'Use this option to list all available migration types and their match conditions')
41 78
            ->addArgument('bundle', InputArgument::REQUIRED, 'The bundle to generate the migration definition file in. eg.: AcmeMigrationBundle')
42 78
            ->addArgument('name', InputArgument::OPTIONAL, 'The migration name (will be prefixed with current date)', null)
43
            ->setHelp(<<<EOT
44
The <info>kaliop:migration:generate</info> command generates a skeleton migration definition file:
45
46
    <info>php ezpublish/console kaliop:migration:generate bundleName</info>
47
48
You can optionally specify the file type to generate with <info>--format</info>, as well a name for the migration:
49
50
    <info>php ezpublish/console kaliop:migration:generate --format=json bundleName migrationName</info>
51
52
For SQL type migration you can optionally specify the database server type the migration is for with <info>--dbserver</info>:
53
54
    <info>php ezpublish/console kaliop:migration:generate --format=sql bundleName</info>
55
56
For content/content_type/language/object_state/role/section migrations you need to specify the entity that you want to generate the migration for:
57
58
    <info>php ezpublish/console kaliop:migration:generate --type=content --match-type=content_id --match-value=10,14 bundleName</info>
59
60
For role type migration you will receive a yaml file with the current role definition. You must define ALL the policies
61
you wish for the role. Any not defined will be removed. Example for updating an existing role:
62
63
    <info>php ezpublish/console kaliop:migration:generate --type=role --mode=update --match-type=identifier --match-value=Anonymous bundleName</info>
64
65
For freeform php migrations, you will receive a php class definition
66
67
    <info>php ezpublish/console kaliop:migration:generate --format=php bundlename classname</info>
68
69
To list all available migration types for generation, as well as the corresponding match-types, run:
70
71 78
    <info> php ezpublish/console ka:mig:gen --list-types whatever</info>
72
73
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>
74
option when you run the <info>migrate</info> command.
75
EOT
76
            );
77
    }
78
79
    /**
80
     * Run the command and display the results.
81 31
     *
82
     * @param InputInterface $input
83 31
     * @param OutputInterface $output
84 31
     * @return null|int null or 0 if everything went fine, or an error code
85 31
     * @throws \InvalidArgumentException When an unsupported file type is selected
86 31
     */
87 31
    public function execute(InputInterface $input, OutputInterface $output)
88 31
    {
89 31
        if ($input->getOption('list-types')) {
90 31
            $this->listAvailableTypes($output);
91 31
            return;
92
        }
93 31
94
        $bundleName = $input->getArgument('bundle');
95
        $name = $input->getArgument('name');
96
        $fileType = $input->getOption('format');
97
        $migrationType = $input->getOption('type');
98 31
        $matchType = $input->getOption('match-type');
99 6
        $matchValue = $input->getOption('match-value');
100 2
        $matchExcept = $input->getOption('match-except');
101 4
        $mode = $input->getOption('mode');
102 2
        $dbServer = $input->getOption('dbserver');
103
104 2
        if ($bundleName == $this->thisBundle) {
105
            throw new \InvalidArgumentException("It is not allowed to create migrations in bundle '$bundleName'");
106
        }
107
108 31
        // be kind to lazy users
109
        if ($migrationType == '') {
110
            if ($fileType == 'sql') {
111
                $migrationType = 'db';
112 31
            } elseif ($fileType == 'php') {
113
                $migrationType = 'php';
114
            } else {
115
                $migrationType = 'generic';
116 31
            }
117
        }
118 31
119 1
        if (!in_array($fileType, $this->availableMigrationFormats)) {
120 1
            throw new \InvalidArgumentException('Unsupported migration file format ' . $fileType);
121 1
        }
122
123
        if (!in_array($mode, $this->availableModes)) {
124 1
            throw new \InvalidArgumentException('Unsupported migration mode ' . $mode);
125 1
        }
126 1
127 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

127
        $migrationDirectory = $this->getMigrationDirectory(/** @scrutinizer ignore-type */ $bundleName);
Loading history...
128
129
        if (!is_dir($migrationDirectory)) {
130
            $output->writeln(sprintf(
131
                "Migrations directory <info>%s</info> does not exist. I will create it now....",
132
                $migrationDirectory
133
            ));
134
135
            if (mkdir($migrationDirectory, self::DIR_CREATE_PERMISSIONS, true)) {
136
                $output->writeln(sprintf(
137
                    "Migrations directory <info>%s</info> has been created",
138 31
                    $migrationDirectory
139 2
                ));
140
            } else {
141
                throw new FileException(sprintf(
142
                    "Failed to create migrations directory %s.",
143 31
                    $migrationDirectory
144 31
                ));
145 31
            }
146 31
        }
147 31
148 31
        // allow to generate migrations for many entities
149 31
        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

149
        if (strpos(/** @scrutinizer ignore-type */ $matchValue, ',') !== false ) {
Loading history...
150
            $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

150
            $matchValue = explode(',', /** @scrutinizer ignore-type */ $matchValue);
Loading history...
151
        }
152 31
153
        $parameters = array(
154
            'dbserver' => $dbServer,
155 31
            'matchType' => $matchType,
156
            'matchValue' => $matchValue,
157 2
            'matchExcept' => $matchExcept,
158 1
            'mode' => $mode,
159
            'lang' => $input->getOption('lang'),
160 2
            'adminLogin' => $input->getOption('admin-login')
161 2
        );
162
163 29
        $date = date('YmdHis');
164
165 2
        switch ($fileType) {
166 2
            case 'sql':
167 1
                /// @todo this logic should come from the DefinitionParser, really
168
                if ($name != '') {
169
                    $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

169
                    $name = '_' . ltrim(/** @scrutinizer ignore-type */ $name, '_');
Loading history...
170 2
                }
171 2
                $fileName = $date . '_' . $dbServer . $name . '.sql';
172
                break;
173
174 2
            case 'php':
175 2
                /// @todo this logic should come from the DefinitionParser, really
176
                $className = ltrim($name, '_');
177 2
                if ($className == '') {
178 2
                    $className = 'Migration';
179
                }
180
                // Make sure that php class names are unique, not only migration definition file names
181 27
                $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

181
                $existingMigrations = count(/** @scrutinizer ignore-type */ glob($migrationDirectory . '/*_' . $className . '*.php'));
Loading history...
182 1
                if ($existingMigrations) {
183
                    $className = $className . sprintf('%03d', $existingMigrations + 1);
184 27
                }
185
                $parameters = array_merge($parameters, array(
186
                    'class_name' => $className
187 31
                ));
188
                $fileName = $date . '_' . $className . '.php';
189 31
                break;
190
191 31
            default:
192
                if ($name == '') {
193 31
                    $name = 'placeholder';
194
                }
195
                $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

195
                $fileName = $date . '_' . /** @scrutinizer ignore-type */ $name . '.' . $fileType;
Loading history...
196 31
        }
197
198
        $path = $migrationDirectory . '/' . $fileName;
199
200
        $warning = $this->generateMigrationFile($path, $fileType, $migrationType, $parameters);
201
202
        $output->writeln(sprintf("Generated new migration file: <info>%s</info>", $path));
203
204
        if ($warning != '') {
205
            $output->writeln("<comment>$warning</comment>");
206
        }
207
    }
208
209 31
    /**
210
     * Generates a migration definition file.
211 31
     * @todo allow non-filesystem storage
212
     *
213
     * @param string $path filename to file to generate (full path)
214 31
     * @param string $fileType The type of migration file to generate
215 29
     * @param string $migrationType The type of migration to generate
216 27
     * @param array $parameters passed on to twig
217
     * @return string A warning message in case file generation was OK but there was something weird
218 6
     * @throws \Exception
219 6
     */
220 6
    protected function generateMigrationFile($path, $fileType, $migrationType, array $parameters = array())
221
    {
222
        $warning = '';
223
224 6
        switch ($migrationType) {
225 6
            case 'db':
226
            case 'generic':
227
            case 'php':
228
                // Generate migration file by template
229 25
                $template = $migrationType . 'Migration.' . $fileType . '.twig';
230 25
                $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

230
                $templatePath = $this->getApplication()->/** @scrutinizer ignore-call */ getKernel()->getBundle($this->thisBundle)->getPath() . '/Resources/views/MigrationTemplate/';
Loading history...
231
                if (!is_file($templatePath . $template)) {
232
                    throw new \Exception("The combination of migration type '$migrationType' is not supported with format '$fileType'");
233 25
                }
234
235 25
                $code = $this->getContainer()->get('twig')->render($this->thisBundle . ':MigrationTemplate:' . $template, $parameters);
236
                break;
237 25
238 25
            default:
239 2
                // Generate migration file by executor
240
                $executors = $this->getGeneratingExecutors();
241 25
                if (!in_array($migrationType, $executors)) {
242
                    throw new \Exception("It is not possible to generate a migration of type '$migrationType': executor not found or not a generator");
243 25
                }
244
                /** @var MigrationGeneratorInterface $executor */
245
                $executor = $this->getMigrationService()->getExecutor($migrationType);
246
247
                $context = $this->migrationContextFromParameters($parameters);
248 25
249 16
                $matchCondition = array($parameters['matchType'] => $parameters['matchValue']);
250 16
                if ($parameters['matchExcept']) {
251 9
                    $matchCondition = array(MatcherInterface::MATCH_NOT => $matchCondition);
252 9
                }
253 9
                $data = $executor->generateMigration($matchCondition, $parameters['mode'], $context);
254
255
                if (!is_array($data) || !count($data)) {
0 ignored issues
show
introduced by
The condition is_array($data) is always true.
Loading history...
256
                    $warning = 'Note: the generated migration is empty';
257
                }
258
259 31
                switch ($fileType) {
260
                    case 'yml':
261 31
                        $code = Yaml::dump($data, 5);
262
                        break;
263
                    case 'json':
264
                        $code = json_encode($data, JSON_PRETTY_PRINT);
265
                        break;
266
                    default:
267
                        throw new \Exception("The combination of migration type '$migrationType' is not supported with format '$fileType'");
268 31
                }
269
        }
270
271 31
        file_put_contents($path, $code);
272
273
        return $warning;
274
    }
275 31
276 31
    protected function listAvailableTypes(OutputInterface $output)
277 31
    {
278
        $output->writeln('Specific migration types available for generation (besides sql,php, generic):');
279 31
        foreach ($this->getGeneratingExecutors() as $executorType) {
280 31
            $output->writeln($executorType);
281
            /** @var MigrationGeneratorInterface $executor */
282
            $executor = $this->getMigrationService()->getExecutor($executorType);
283
            if ($executor instanceof EnumerableMatcherInterface) {
284 31
                $conditions = $executor->listAllowedConditions();
285 31
                $conditions = array_diff($conditions, array('and', 'or', 'not'));
286
                $output->writeln("  corresponding match types:\n    - " . implode("\n    - ", $conditions));
287 31
            }
288
        }
289
    }
290
291 25
    /**
292
     * @param string $bundleName a bundle name or filesystem path to a directory
293 25
     * @return string
294 25
     */
295 25
    protected function getMigrationDirectory($bundleName)
296 25
    {
297 25
        // Allow direct usage of a directory path instead of a bundle name
298 25
        if (strpos($bundleName, '/') !== false && is_dir($bundleName)) {
299
            return rtrim($bundleName, '/');
300
        }
301 25
302
        $activeBundles = array();
303
        foreach ($this->getApplication()->getKernel()->getBundles() as $bundle) {
304
            $activeBundles[] = $bundle->getName();
305
        }
306
        asort($activeBundles);
307
        if (!in_array($bundleName, $activeBundles)) {
308
            throw new \InvalidArgumentException("Bundle '$bundleName' does not exist or it is not enabled. Try with one of:\n" . implode(', ', $activeBundles));
309 25
        }
310
311 25
        $bundle = $this->getApplication()->getKernel()->getBundle($bundleName);
312
        $migrationDirectory = $bundle->getPath() . '/' . $this->getContainer()->get('ez_migration_bundle.helper.config.resolver')->getParameter('kaliop_bundle_migration.version_directory');
313 25
314
        return $migrationDirectory;
315
    }
316 25
317
    /**
318
     * @todo move somewhere else. Maybe to the MigrationService itself ?
319
     * @return string[]
320 25
     */
321
    protected function getGeneratingExecutors()
322
    {
323
        $migrationService = $this->getMigrationService();
324
        $executors = $migrationService->listExecutors();
325
        foreach($executors as $key => $name) {
326
            $executor = $migrationService->getExecutor($name);
327
            if (!$executor instanceof MigrationGeneratorInterface) {
328
                unset($executors[$key]);
329
            }
330
        }
331
        return $executors;
332
    }
333
334
    /**
335
     * @see MigrationService::migrationContextFromParameters
336
     * @param array $parameters
337
     * @return array
338
     */
339
    protected function migrationContextFromParameters(array $parameters)
340
    {
341
        $context = array();
342
343
        if (isset($parameters['lang']) && $parameters['lang'] != '') {
344
            $context['defaultLanguageCode'] = $parameters['lang'];
345
        }
346
        if (isset($parameters['adminLogin']) && $parameters['adminLogin'] != '') {
347
            $context['adminUserLogin'] = $parameters['adminLogin'];
348
        }
349
350
        return $context;
351
    }
352
}
353