Completed
Branch master (e35419)
by Gaetano
06:40
created

GenerateCommand::configure()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 1

Importance

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

116
        $migrationDirectory = $this->getMigrationDirectory(/** @scrutinizer ignore-type */ $bundleName);
Loading history...
117
118 31
        if (!is_dir($migrationDirectory)) {
119 1
            $output->writeln(sprintf(
120 1
                "Migrations directory <info>%s</info> does not exist. I will create it now....",
121 1
                $migrationDirectory
122
            ));
123
124 1
            if (mkdir($migrationDirectory, self::DIR_CREATE_PERMISSIONS, true)) {
125 1
                $output->writeln(sprintf(
126 1
                    "Migrations directory <info>%s</info> has been created",
127 1
                    $migrationDirectory
128
                ));
129
            } else {
130
                throw new FileException(sprintf(
131
                    "Failed to create migrations directory %s.",
132
                    $migrationDirectory
133
                ));
134
            }
135
        }
136
137
        // allow to generate migrations for many entities
138 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

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

139
            $matchValue = explode(',', /** @scrutinizer ignore-type */ $matchValue);
Loading history...
140
        }
141
142
        $parameters = array(
143 31
            'dbserver' => $dbServer,
144 31
            'matchType' => $matchType,
145 31
            'matchValue' => $matchValue,
146 31
            'matchExcept' => $matchExcept,
147 31
            'mode' => $mode,
148 31
            'lang' => $input->getOption('lang'),
149 31
            'adminLogin' => $input->getOption('admin-login')
150
        );
151
152 31
        $date = date('YmdHis');
153
154
        switch ($fileType) {
155 31
            case 'sql':
156
                /// @todo this logic should come from the DefinitionParser, really
157 2
                if ($name != '') {
158 1
                    $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

158
                    $name = '_' . ltrim(/** @scrutinizer ignore-type */ $name, '_');
Loading history...
159
                }
160 2
                $fileName = $date . '_' . $dbServer . $name . '.sql';
161 2
                break;
162
163 29
            case 'php':
164
                /// @todo this logic should come from the DefinitionParser, really
165 2
                $className = ltrim($name, '_');
166 2
                if ($className == '') {
167 1
                    $className = 'Migration';
168
                }
169
                // Make sure that php class names are unique, not only migration definition file names
170 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

170
                $existingMigrations = count(/** @scrutinizer ignore-type */ glob($migrationDirectory . '/*_' . $className . '*.php'));
Loading history...
171 2
                if ($existingMigrations) {
172
                    $className = $className . sprintf('%03d', $existingMigrations + 1);
173
                }
174 2
                $parameters = array_merge($parameters, array(
175 2
                    'class_name' => $className
176
                ));
177 2
                $fileName = $date . '_' . $className . '.php';
178 2
                break;
179
180
            default:
181 27
                if ($name == '') {
182 1
                    $name = 'placeholder';
183
                }
184 27
                $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

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

219
                $templatePath = $this->getApplication()->/** @scrutinizer ignore-call */ getKernel()->getBundle($this->thisBundle)->getPath() . '/Resources/views/MigrationTemplate/';
Loading history...
220 6
                if (!is_file($templatePath . $template)) {
221
                    throw new \Exception("The combination of migration type '$migrationType' is not supported with format '$fileType'");
222
                }
223
224 6
                $code = $this->getContainer()->get('twig')->render($this->thisBundle . ':MigrationTemplate:' . $template, $parameters);
225 6
                break;
226
227
            default:
228
                // Generate migration file by executor
229 25
                $executors = $this->getGeneratingExecutors();
230 25
                if (!in_array($migrationType, $executors)) {
231
                    throw new \Exception("It is not possible to generate a migration of type '$migrationType': executor not found or not a generator");
232
                }
233 25
                $executor = $this->getMigrationService()->getExecutor($migrationType);
234
235 25
                $context = $this->migrationContextFromParameters($parameters);
236
237 25
                $matchCondition = array($parameters['matchType'] => $parameters['matchValue']);
238 25
                if ($parameters['matchExcept']) {
239 2
                    $matchCondition = array(MatcherInterface::MATCH_NOT => $matchCondition);
240
                }
241 25
                $data = $executor->generateMigration($matchCondition, $parameters['mode'], $context);
0 ignored issues
show
Bug introduced by
The method generateMigration() does not exist on Kaliop\eZMigrationBundle\API\ExecutorInterface. It seems like you code against a sub-type of Kaliop\eZMigrationBundle\API\ExecutorInterface such as Kaliop\eZMigrationBundle...xecutor\LanguageManager or Kaliop\eZMigrationBundle...Executor\ContentManager or Kaliop\eZMigrationBundle\Core\Executor\RoleManager or Kaliop\eZMigrationBundle...ObjectStateGroupManager or Kaliop\eZMigrationBundle...utor\ObjectStateManager or Kaliop\eZMigrationBundle...ContentTypeGroupManager or Kaliop\eZMigrationBundle...utor\ContentTypeManager or Kaliop\eZMigrationBundle...Executor\SectionManager. ( Ignorable by Annotation )

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

241
                /** @scrutinizer ignore-call */ 
242
                $data = $executor->generateMigration($matchCondition, $parameters['mode'], $context);
Loading history...
242
243 25
                if (!is_array($data) || !count($data)) {
244
                    $warning = 'Note: the generated migration is empty';
245
                }
246
247
                switch ($fileType) {
248 25
                    case 'yml':
249 16
                        $code = Yaml::dump($data, 5);
250 16
                        break;
251 9
                    case 'json':
252 9
                        $code = json_encode($data, JSON_PRETTY_PRINT);
253 9
                        break;
254
                    default:
255
                        throw new \Exception("The combination of migration type '$migrationType' is not supported with format '$fileType'");
256
                }
257
        }
258
259 31
        file_put_contents($path, $code);
260
261 31
        return $warning;
262
    }
263
264
    /**
265
     * @param string $bundleName a bundle name or filesystem path to a directory
266
     * @return string
267
     */
268 31
    protected function getMigrationDirectory($bundleName)
269
    {
270
        // Allow direct usage of a directory path instead of a bundle name
271 31
        if (strpos($bundleName, '/') !== false && is_dir($bundleName)) {
272
            return rtrim($bundleName, '/');
273
        }
274
275 31
        $activeBundles = array();
276 31
        foreach ($this->getApplication()->getKernel()->getBundles() as $bundle) {
277 31
            $activeBundles[] = $bundle->getName();
278
        }
279 31
        asort($activeBundles);
280 31
        if (!in_array($bundleName, $activeBundles)) {
281
            throw new \InvalidArgumentException("Bundle '$bundleName' does not exist or it is not enabled. Try with one of:\n" . implode(', ', $activeBundles));
282
        }
283
284 31
        $bundle = $this->getApplication()->getKernel()->getBundle($bundleName);
285 31
        $migrationDirectory = $bundle->getPath() . '/' . $this->getContainer()->get('ez_migration_bundle.helper.config.resolver')->getParameter('kaliop_bundle_migration.version_directory');
286
287 31
        return $migrationDirectory;
288
    }
289
290
    /// @todo move somewhere else. Maybe to the MigrationService itself ?
291 25
    protected function getGeneratingExecutors()
292
    {
293 25
        $migrationService = $this->getMigrationService();
294 25
        $executors = $migrationService->listExecutors();
295 25
        foreach($executors as $key => $name) {
296 25
            $executor = $migrationService->getExecutor($name);
297 25
            if (!$executor instanceof MigrationGeneratorInterface) {
298 25
                unset($executors[$key]);
299
            }
300
        }
301 25
        return $executors;
302
    }
303
304
    /**
305
     * @see MigrationService::migrationContextFromParameters
306
     * @param array $parameters
307
     * @return array
308
     */
309 25
    protected function migrationContextFromParameters(array $parameters)
310
    {
311 25
        $context = array();
312
313 25
        if (isset($parameters['lang']) && $parameters['lang'] != '') {
314
            $context['defaultLanguageCode'] = $parameters['lang'];
315
        }
316 25
        if (isset($parameters['adminLogin']) && $parameters['adminLogin'] != '') {
317
            $context['adminUserLogin'] = $parameters['adminLogin'];
318
        }
319
320 25
        return $context;
321
    }
322
}
323