Magento 2 Documentation  2.3
Documentation for Magento 2 CMS v2.3 (December 2018)
ModuleUninstallCommand.php
Go to the documentation of this file.
1 <?php
7 
23 use Symfony\Component\Console\Input\InputInterface;
24 use Symfony\Component\Console\Input\InputOption;
25 use Symfony\Component\Console\Output\OutputInterface;
26 use Symfony\Component\Console\Question\ConfirmationQuestion;
27 
35 {
39  const INPUT_KEY_REMOVE_DATA = 'remove-data';
40  const INPUT_KEY_BACKUP_CODE = 'backup-code';
41  const INPUT_KEY_BACKUP_MEDIA = 'backup-media';
42  const INPUT_KEY_BACKUP_DB = 'backup-db';
43  const INPUT_KEY_NON_COMPOSER_MODULE = 'non-composer';
44 
50  private $deploymentConfig;
51 
57  private $fullModuleList;
58 
64  private $packageInfo;
65 
71  private $collector;
72 
78  private $dependencyChecker;
79 
85  private $composer;
86 
92  private $backupRollbackFactory;
93 
99  private $moduleUninstaller;
100 
106  private $moduleRegistryUninstaller;
107 
111  private $maintenanceModeEnabler;
112 
116  private $patchApplier;
117 
133  public function __construct(
134  ComposerInformation $composer,
135  DeploymentConfig $deploymentConfig,
136  FullModuleList $fullModuleList,
137  MaintenanceMode $maintenanceMode,
138  ObjectManagerProvider $objectManagerProvider,
139  UninstallCollector $collector,
140  ModuleUninstaller $moduleUninstaller,
141  ModuleRegistryUninstaller $moduleRegistryUninstaller,
142  MaintenanceModeEnabler $maintenanceModeEnabler = null
143  ) {
144  parent::__construct($objectManagerProvider);
145  $this->composer = $composer;
146  $this->deploymentConfig = $deploymentConfig;
147  $this->fullModuleList = $fullModuleList;
148  $this->packageInfo = $this->objectManager->get(\Magento\Framework\Module\PackageInfoFactory::class)->create();
149  $this->collector = $collector;
150  $this->dependencyChecker = $this->objectManager->get(\Magento\Framework\Module\DependencyChecker::class);
151  $this->backupRollbackFactory = $this->objectManager->get(\Magento\Framework\Setup\BackupRollbackFactory::class);
152  $this->moduleUninstaller = $moduleUninstaller;
153  $this->moduleRegistryUninstaller = $moduleRegistryUninstaller;
154  $this->maintenanceModeEnabler =
155  $maintenanceModeEnabler ?: $this->objectManager->get(MaintenanceModeEnabler::class);
156  }
157 
161  private function getPatchApplier()
162  {
163  if (!$this->patchApplier) {
164  $this->patchApplier = $this
165  ->objectManager->get(PatchApplier::class);
166  }
167 
168  return $this->patchApplier;
169  }
170 
174  protected function configure()
175  {
176  $options = [
177  new InputOption(
178  self::INPUT_KEY_REMOVE_DATA,
179  'r',
180  InputOption::VALUE_NONE,
181  'Remove data installed by module(s)'
182  ),
183  new InputOption(
184  self::INPUT_KEY_BACKUP_CODE,
185  null,
186  InputOption::VALUE_NONE,
187  'Take code and configuration files backup (excluding temporary files)'
188  ),
189  new InputOption(
190  self::INPUT_KEY_BACKUP_MEDIA,
191  null,
192  InputOption::VALUE_NONE,
193  'Take media backup'
194  ),
195  new InputOption(
196  self::INPUT_KEY_BACKUP_DB,
197  null,
198  InputOption::VALUE_NONE,
199  'Take complete database backup'
200  ),
201  new InputOption(
202  self::INPUT_KEY_NON_COMPOSER_MODULE,
203  null,
204  InputOption::VALUE_NONE,
205  'All modules, that will be past here will be non composer based'
206  )
207  ];
208  $this->setName('module:uninstall')
209  ->setDescription('Uninstalls modules installed by composer')
210  ->setDefinition($options);
211  parent::configure();
212  }
213 
217  protected function isModuleRequired()
218  {
219  return true;
220  }
221 
227  protected function execute(InputInterface $input, OutputInterface $output)
228  {
229  if (!$this->deploymentConfig->isAvailable()) {
230  $output->writeln(
231  '<error>You cannot run this command because the Magento application is not installed.</error>'
232  );
233  // we must have an exit code higher than zero to indicate something was wrong
234  return Cli::RETURN_FAILURE;
235  }
236 
237  $modules = $input->getArgument(self::INPUT_KEY_MODULES);
238 
239  if ($input->getOption(self::INPUT_KEY_NON_COMPOSER_MODULE)) {
240  foreach ($modules as $moduleName) {
241  $this->getPatchApplier()->revertDataPatches($moduleName);
242  }
243 
244  return Cli::RETURN_SUCCESS;
245  }
246 
247  // validate modules input
248  $messages = $this->validate($modules);
249  if (!empty($messages)) {
250  $output->writeln($messages);
251  // we must have an exit code higher than zero to indicate something was wrong
252  return Cli::RETURN_FAILURE;
253  }
254 
255  // check dependencies
256  $dependencyMessages = $this->checkDependencies($modules);
257  if (!empty($dependencyMessages)) {
258  $output->writeln($dependencyMessages);
259  // we must have an exit code higher than zero to indicate something was wrong
260  return Cli::RETURN_FAILURE;
261  }
262 
263  $helper = $this->getHelper('question');
264  $question = new ConfirmationQuestion(
265  'You are about to remove code and/or database tables. Are you sure?[y/N]',
266  false
267  );
268  if (!$helper->ask($input, $output, $question) && $input->isInteractive()) {
269  return Cli::RETURN_FAILURE;
270  }
271 
272  $result = $this->maintenanceModeEnabler->executeInMaintenanceMode(
273  function () use ($input, $output, $modules, $helper) {
274  try {
275  $this->takeBackup($input, $output);
276  $dbBackupOption = $input->getOption(self::INPUT_KEY_BACKUP_DB);
277  if ($input->getOption(self::INPUT_KEY_REMOVE_DATA)) {
278  $this->removeData($modules, $output, $dbBackupOption);
279  } else {
280  if (!empty($this->collector->collectUninstall())) {
281  $question = new ConfirmationQuestion(
282  'You are about to remove a module(s) that might have database data. '
283  . 'Do you want to remove the data from database?[y/N]',
284  false
285  );
286  if ($helper->ask($input, $output, $question) || !$input->isInteractive()) {
287  $this->removeData($modules, $output, $dbBackupOption);
288  }
289  } else {
290  $output->writeln(
291  '<info>You are about to remove a module(s) that might have database data. '
292  . 'Remove the database data manually after uninstalling, if desired.</info>'
293  );
294  }
295  }
296  $this->moduleRegistryUninstaller->removeModulesFromDb($output, $modules);
297  $this->moduleRegistryUninstaller->removeModulesFromDeploymentConfig($output, $modules);
298  $this->moduleUninstaller->uninstallCode($output, $modules);
299  $this->cleanup($input, $output);
300 
301  return Cli::RETURN_SUCCESS;
302  } catch (\Exception $e) {
303  $output->writeln('<error>' . $e->getMessage() . '</error>');
304  $output->writeln('<error>Please disable maintenance mode after you resolved above issues</error>');
305  return Cli::RETURN_FAILURE;
306  }
307  },
308  $output,
309  true
310  );
311 
312  return $result;
313  }
314 
322  private function takeBackup(InputInterface $input, OutputInterface $output)
323  {
324  $time = time();
325  if ($input->getOption(self::INPUT_KEY_BACKUP_CODE)) {
326  $codeBackup = $this->backupRollbackFactory->create($output);
327  $codeBackup->codeBackup($time);
328  }
329  if ($input->getOption(self::INPUT_KEY_BACKUP_MEDIA)) {
330  $mediaBackup = $this->backupRollbackFactory->create($output);
331  $mediaBackup->codeBackup($time, Factory::TYPE_MEDIA);
332  }
333  if ($input->getOption(self::INPUT_KEY_BACKUP_DB)) {
334  $dbBackup = $this->backupRollbackFactory->create($output);
335  $this->setAreaCode();
336  $dbBackup->dbBackup($time);
337  }
338  }
339 
348  private function removeData(array $modules, OutputInterface $output, $dbBackupOption)
349  {
350  if (!$dbBackupOption) {
351  $output->writeln('<error>You are removing data without a database backup.</error>');
352  } else {
353  $output->writeln('<info>Removing data</info>');
354  }
355  $this->moduleUninstaller->uninstallData($output, $modules);
356  }
357 
364  protected function validate(array $modules)
365  {
366  $messages = [];
367  $unknownPackages = [];
368  $unknownModules = [];
369  $installedPackages = $this->composer->getRootRequiredPackages();
370  foreach ($modules as $module) {
371  if (array_search($this->packageInfo->getPackageName($module), $installedPackages) === false) {
372  $unknownPackages[] = $module;
373  }
374  if (!$this->fullModuleList->has($module)) {
375  $unknownModules[] = $module;
376  }
377  }
378  $unknownPackages = array_diff($unknownPackages, $unknownModules);
379  if (!empty($unknownPackages)) {
380  $text = count($unknownPackages) > 1 ?
381  ' are not installed composer packages' : ' is not an installed composer package';
382  $messages[] = '<error>' . implode(', ', $unknownPackages) . $text . '</error>';
383  }
384  if (!empty($unknownModules)) {
385  $messages[] = '<error>Unknown module(s): ' . implode(', ', $unknownModules) . '</error>';
386  }
387  return $messages;
388  }
389 
396  private function checkDependencies(array $modules)
397  {
398  $messages = [];
399  $dependencies = $this->dependencyChecker->checkDependenciesWhenDisableModules(
400  $modules,
401  $this->fullModuleList->getNames()
402  );
403  foreach ($dependencies as $module => $dependingModules) {
404  if (!empty($dependingModules)) {
405  $messages[] =
406  "<error>Cannot uninstall module '$module' because the following module(s) depend on it:</error>" .
407  PHP_EOL . "\t<error>" . implode('</error>' . PHP_EOL . "\t<error>", array_keys($dependingModules)) .
408  "</error>";
409  }
410  }
411  return $messages;
412  }
413 
419  private function setAreaCode()
420  {
421  $areaCode = 'adminhtml';
423  $appState = $this->objectManager->get(\Magento\Framework\App\State::class);
424  $appState->setAreaCode($areaCode);
426  $configLoader = $this->objectManager->get(\Magento\Framework\ObjectManager\ConfigLoaderInterface::class);
427  $this->objectManager->configure($configLoader->load($areaCode));
428  }
429 }
__construct(ComposerInformation $composer, DeploymentConfig $deploymentConfig, FullModuleList $fullModuleList, MaintenanceMode $maintenanceMode, ObjectManagerProvider $objectManagerProvider, UninstallCollector $collector, ModuleUninstaller $moduleUninstaller, ModuleRegistryUninstaller $moduleRegistryUninstaller, MaintenanceModeEnabler $maintenanceModeEnabler=null)
cleanup($repo, $mainline)
$helper
Definition: iframe.phtml:13
endifif( $block->getLastPageNum()>1)( 'Page') ?></strong >< ul class $text
Definition: pager.phtml:43
$deploymentConfig
execute(InputInterface $input, OutputInterface $output)