1 | <?php |
||
2 | |||
3 | namespace Kaliop\eZMigrationBundle\Command; |
||
4 | |||
5 | use Symfony\Component\Console\Input\InputInterface; |
||
6 | use Symfony\Component\Console\Output\OutputInterface; |
||
7 | use Symfony\Component\Console\Input\InputOption; |
||
8 | use Symfony\Component\Console\Input\InputArgument; |
||
9 | use Kaliop\eZMigrationBundle\API\Value\Migration; |
||
10 | use Kaliop\eZMigrationBundle\API\Value\MigrationDefinition; |
||
11 | use Symfony\Component\Console\Question\ConfirmationQuestion; |
||
12 | |||
13 | /** |
||
14 | * Command to manipulate the available migrations / migration definitions. |
||
15 | * @todo should we split off the actions used to manipulate migration defs into a separate command `MigrationDefinition` ? |
||
16 | */ |
||
17 | class MigrationCommand extends AbstractCommand |
||
18 | { |
||
19 | /** |
||
20 | * Set up the command. |
||
21 | * |
||
22 | * Define the name, options and help text. |
||
23 | */ |
||
24 | 148 | protected function configure() |
|
25 | { |
||
26 | 148 | parent::configure(); |
|
27 | |||
28 | $this |
||
29 | 148 | ->setName('kaliop:migration:migration') |
|
30 | 148 | ->setDescription('Manually modify or get info about migrations in the database table.') |
|
31 | 148 | ->addOption('delete', null, InputOption::VALUE_NONE, "Delete the specified migration.") |
|
32 | 148 | ->addOption('info', null, InputOption::VALUE_NONE, "Get info about the specified migration.") |
|
33 | 148 | ->addOption('add', null, InputOption::VALUE_NONE, "Add the specified migration definition.") |
|
34 | 148 | ->addOption('skip', null, InputOption::VALUE_NONE, "Mark the specified migration as skipped.") |
|
35 | 148 | ->addOption('fail', null, InputOption::VALUE_NONE, "Mark the specified migration as failed.") |
|
36 | 148 | ->addOption('no-interaction', 'n', InputOption::VALUE_NONE, "Do not ask any interactive question.") |
|
37 | 148 | ->addArgument('migration', InputArgument::REQUIRED, 'The migration to add/skip (filename with full path) or detail/delete (plain migration name).', null) |
|
38 | 148 | ->setHelp(<<<EOT |
|
39 | 148 | The <info>kaliop:migration:migration</info> command allows you to manually manage migrations. |
|
40 | |||
41 | To see detailed information about a migration or migration definition: |
||
42 | |||
43 | <info>php bin/console kaliop:migration:migration --info migration_name</info> |
||
44 | |||
45 | <info>php bin/console kaliop:migration:migration --info /path/to/migration_definition.yml</info> |
||
46 | |||
47 | To remove a migration from the migration table, or mark it as failed: |
||
48 | |||
49 | <info>php bin/console kaliop:migration:migration --delete migration_name</info> |
||
50 | |||
51 | <info>php bin/console kaliop:migration:migration --fail migration_name</info> |
||
52 | |||
53 | To manually add migration definitions to the migration table, or marking them as skipped: |
||
54 | |||
55 | <info>php bin/console kaliop:migration:migration --add /path/to/migration_definition</info> |
||
56 | |||
57 | <info>php bin/console kaliop:migration:migration --skip /path/to/migration_definition</info> |
||
58 | EOT |
||
59 | ); |
||
60 | 148 | } |
|
61 | |||
62 | /** |
||
63 | * Execute the command. |
||
64 | * |
||
65 | * @param InputInterface $input |
||
66 | * @param OutputInterface $output |
||
67 | * @return null|int null or 0 if everything went fine, or an error code |
||
68 | */ |
||
69 | 107 | protected function execute(InputInterface $input, OutputInterface $output) |
|
70 | { |
||
71 | 107 | $this->setOutput($output); |
|
72 | 107 | $this->setVerbosity($output->getVerbosity()); |
|
73 | |||
74 | 107 | if (!$input->getOption('add') && !$input->getOption('delete') && !$input->getOption('skip') && |
|
75 | 107 | !$input->getOption('info') && !$input->getOption('fail')) { |
|
76 | throw new \InvalidArgumentException('You must specify whether you want to --add, --delete, --skip, --fail or --info the specified migration.'); |
||
77 | } |
||
78 | |||
79 | 107 | $migrationService = $this->getMigrationService(); |
|
80 | 107 | $migrationNameOrPath = $input->getArgument('migration'); |
|
81 | |||
82 | 107 | if ($input->getOption('info')) { |
|
83 | 20 | $output->writeln(''); |
|
84 | |||
85 | /// @todo if we are passed a path, we could give the user a more specific warning than what we get back from the storage layer |
||
86 | 20 | $migration = $migrationService->getMigration($migrationNameOrPath); |
|
87 | 20 | if ($migration == null) { |
|
88 | throw new \InvalidArgumentException(sprintf('The migration "%s" does not exist in the migrations table. You can use `kaliop:migration:status --show-path` to find out more', $migrationNameOrPath)); |
||
89 | } |
||
90 | |||
91 | 20 | switch ($migration->status) { |
|
92 | case Migration::STATUS_DONE: |
||
93 | 18 | $status = '<info>executed</info>'; |
|
94 | 18 | break; |
|
95 | case Migration::STATUS_STARTED: |
||
96 | $status = '<comment>execution started</comment>'; |
||
97 | break; |
||
98 | case Migration::STATUS_TODO: |
||
99 | // bold to-migrate! |
||
100 | 1 | $status = '<error>not executed</error>'; |
|
101 | 1 | break; |
|
102 | case Migration::STATUS_SKIPPED: |
||
103 | 1 | $status = '<comment>skipped</comment>'; |
|
104 | 1 | break; |
|
105 | case Migration::STATUS_PARTIALLY_DONE: |
||
106 | $status = '<comment>partially executed</comment>'; |
||
107 | break; |
||
108 | case Migration::STATUS_SUSPENDED: |
||
109 | $status = '<comment>suspended</comment>'; |
||
110 | break; |
||
111 | case Migration::STATUS_FAILED: |
||
112 | $status = '<error>failed</error>'; |
||
113 | break; |
||
114 | } |
||
115 | |||
116 | 20 | $output->writeln('<info>Migration: ' . $migration->name . '</info>'); |
|
117 | 20 | $output->writeln('Status: ' . $status); |
|
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
![]() |
|||
118 | 20 | $output->writeln('Executed on: <info>' . ($migration->executionDate != null ? date("Y-m-d H:i:s", $migration->executionDate) : '--'). '</info>'); |
|
119 | 20 | $output->writeln('Execution notes: <info>' . $migration->executionError . '</info>'); |
|
120 | |||
121 | 20 | if ($migration->status == Migration::STATUS_SUSPENDED) { |
|
122 | /// @todo decode the suspension context: date, step, ... |
||
123 | } |
||
124 | |||
125 | 20 | $output->writeln('Definition path: <info>' . $migration->path . '</info>'); |
|
126 | 20 | $output->writeln('Definition md5: <info>' . $migration->md5 . '</info>'); |
|
127 | |||
128 | 20 | if ($migration->path != '') { |
|
129 | // q: what if we have a loader which does not work with is_file? We could probably remove this check |
||
130 | // or, better, add a method `$migrationService->migrationDefinitionExists` / use a specific exception in getMigrationsDefinitions... |
||
131 | // Note also that $migration->path can not be used as is, as it is usually relative to the app's root dir |
||
132 | //if (is_file($migration->path)) { |
||
133 | try { |
||
134 | 20 | $migrationDefinitionCollection = $migrationService->getMigrationsDefinitions(array($migration->path)); |
|
135 | 20 | if (count($migrationDefinitionCollection)) { |
|
136 | 20 | $migrationDefinition = $migrationDefinitionCollection->reset(); |
|
137 | 20 | $migrationDefinition = $migrationService->parseMigrationDefinition($migrationDefinition); |
|
138 | |||
139 | 20 | if ($migrationDefinition->status != MigrationDefinition::STATUS_PARSED) { |
|
140 | $output->writeln('Definition error: <error>' . $migrationDefinition->parsingError . '</error>'); |
||
141 | } |
||
142 | |||
143 | 20 | if (md5($migrationDefinition->rawDefinition) != $migration->md5) { |
|
144 | 20 | $output->writeln('Notes: <comment>The migration definition file has now a different checksum</comment>'); |
|
145 | } |
||
146 | } else { |
||
147 | 20 | $output->writeln('Definition error: <error>The migration definition file can not be loaded</error>'); |
|
148 | } |
||
149 | } catch (\Exception $e) { |
||
150 | /// @todo one day we should be able to limit the kind of exceptions we have to catch here... |
||
151 | $output->writeln('Definition parsing error: <error>' . $e->getMessage() . '</error>'); |
||
152 | } |
||
153 | //} else { |
||
154 | // $output->writeln('Definition error: <error>The migration definition file can not be found any more</error>'); |
||
155 | //} |
||
156 | } |
||
157 | |||
158 | 20 | $output->writeln(''); |
|
159 | 20 | return 0; |
|
160 | } |
||
161 | |||
162 | // ask user for confirmation to make changes |
||
163 | 107 | if ($input->isInteractive() && !$input->getOption('no-interaction')) { |
|
164 | $dialog = $this->getHelperSet()->get('question'); |
||
165 | if (!$dialog->ask( |
||
166 | $input, |
||
167 | $output, |
||
168 | new ConfirmationQuestion('<question>Careful, the database will be modified. Do you want to continue Y/N ?</question>', false) |
||
169 | ) |
||
170 | ) { |
||
171 | $output->writeln('<error>Migration change cancelled!</error>'); |
||
172 | return 0; |
||
173 | } |
||
174 | } |
||
175 | |||
176 | 107 | if ($input->getOption('add')) { |
|
177 | // will throw if a file is passed and it is not found, but not if an empty dir is passed |
||
178 | 84 | $migrationDefinitionCollection = $migrationService->getMigrationsDefinitions(array($migrationNameOrPath)); |
|
179 | |||
180 | 84 | if (!count($migrationDefinitionCollection)) |
|
181 | { |
||
182 | throw new \InvalidArgumentException(sprintf('The path "%s" does not correspond to any migration definition.', $migrationNameOrPath)); |
||
183 | } |
||
184 | |||
185 | 84 | foreach ($migrationDefinitionCollection as $migrationDefinition) { |
|
186 | 84 | $migrationName = basename($migrationDefinition->path); |
|
187 | |||
188 | 84 | $migration = $migrationService->getMigration($migrationNameOrPath); |
|
189 | 84 | if ($migration != null) { |
|
190 | throw new \InvalidArgumentException(sprintf('The migration "%s" does already exist in the migrations table.', $migrationName)); |
||
191 | } |
||
192 | |||
193 | 84 | $migrationService->addMigration($migrationDefinition); |
|
194 | 84 | $output->writeln('<info>Added migration ' . $migrationDefinition->path . '</info>'); |
|
195 | } |
||
196 | |||
197 | 84 | return 0; |
|
198 | } |
||
199 | |||
200 | 107 | if ($input->getOption('delete') || $input->getOption('fail')) { |
|
201 | /// @todo if we are passed a path, we could give the user a more specific warning than what we get back from the storage layer |
||
202 | 107 | $migration = $migrationService->getMigration($migrationNameOrPath); |
|
203 | 107 | if ($migration == null) { |
|
204 | 104 | throw new \InvalidArgumentException(sprintf('The migration "%s" does not exist in the migrations table.', $migrationNameOrPath)); |
|
205 | } |
||
206 | |||
207 | 107 | if ($input->getOption('delete')) { |
|
208 | 107 | $migrationService->deleteMigration($migration); |
|
209 | } else { |
||
210 | $errorMessage = 'Manually failed on ' . date("Y-m-d H:i:s"); |
||
211 | if ($migration->executionError != '') { |
||
212 | $errorMessage .= ". Previous notes: " . $migration->executionError; |
||
213 | } |
||
214 | $migrationService->failMigration($migration, $errorMessage); |
||
215 | } |
||
216 | |||
217 | return 0; |
||
218 | 107 | } |
|
219 | |||
220 | if ($input->getOption('skip')) { |
||
221 | 1 | // will throw if a file is passed and it is not found, but not if an empty dir is passed |
|
222 | $migrationDefinitionCollection = $migrationService->getMigrationsDefinitions(array($migrationNameOrPath)); |
||
223 | 1 | ||
224 | if (!count($migrationDefinitionCollection)) |
||
225 | 1 | { |
|
226 | throw new \InvalidArgumentException(sprintf('The path "%s" does not correspond to any migration definition.', $migrationNameOrPath)); |
||
227 | } |
||
228 | |||
229 | foreach ($migrationDefinitionCollection as $migrationDefinition) { |
||
230 | 1 | $migrationService->skipMigration($migrationDefinition); |
|
231 | 1 | $output->writeln('<info>Migration ' . $migrationDefinition->path . ' marked as skipped</info>'); |
|
232 | 1 | } |
|
233 | |||
234 | return 0; |
||
235 | 1 | } |
|
236 | |||
237 | throw new \InvalidArgumentException("Please specify one action to be taken on the given migration"); |
||
238 | } |
||
239 | } |
||
240 |