composer = $composer;
$this->deploymentConfig = $deploymentConfig;
$this->fullModuleList = $fullModuleList;
$this->packageInfo = $this->objectManager->get(\Magento\Framework\Module\PackageInfoFactory::class)->create();
$this->collector = $collector;
$this->dependencyChecker = $this->objectManager->get(\Magento\Framework\Module\DependencyChecker::class);
$this->backupRollbackFactory = $this->objectManager->get(\Magento\Framework\Setup\BackupRollbackFactory::class);
$this->moduleUninstaller = $moduleUninstaller;
$this->moduleRegistryUninstaller = $moduleRegistryUninstaller;
$this->maintenanceModeEnabler =
$maintenanceModeEnabler ?: $this->objectManager->get(MaintenanceModeEnabler::class);
}
/**
* @return PatchApplier
*/
private function getPatchApplier()
{
if (!$this->patchApplier) {
$this->patchApplier = $this
->objectManager->get(PatchApplier::class);
}
return $this->patchApplier;
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$options = [
new InputOption(
self::INPUT_KEY_REMOVE_DATA,
'r',
InputOption::VALUE_NONE,
'Remove data installed by module(s)'
),
new InputOption(
self::INPUT_KEY_BACKUP_CODE,
null,
InputOption::VALUE_NONE,
'Take code and configuration files backup (excluding temporary files)'
),
new InputOption(
self::INPUT_KEY_BACKUP_MEDIA,
null,
InputOption::VALUE_NONE,
'Take media backup'
),
new InputOption(
self::INPUT_KEY_BACKUP_DB,
null,
InputOption::VALUE_NONE,
'Take complete database backup'
),
new InputOption(
self::INPUT_KEY_NON_COMPOSER_MODULE,
null,
InputOption::VALUE_NONE,
'All modules, that will be past here will be non composer based'
)
];
$this->setName('module:uninstall')
->setDescription('Uninstalls modules installed by composer')
->setDefinition($options);
parent::configure();
}
/**
* {@inheritdoc}
*/
protected function isModuleRequired()
{
return true;
}
/**
* {@inheritdoc}
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
if (!$this->deploymentConfig->isAvailable()) {
$output->writeln(
'You cannot run this command because the Magento application is not installed.'
);
// we must have an exit code higher than zero to indicate something was wrong
return Cli::RETURN_FAILURE;
}
$modules = $input->getArgument(self::INPUT_KEY_MODULES);
if ($input->getOption(self::INPUT_KEY_NON_COMPOSER_MODULE)) {
foreach ($modules as $moduleName) {
$this->getPatchApplier()->revertDataPatches($moduleName);
}
return Cli::RETURN_SUCCESS;
}
// validate modules input
$messages = $this->validate($modules);
if (!empty($messages)) {
$output->writeln($messages);
// we must have an exit code higher than zero to indicate something was wrong
return Cli::RETURN_FAILURE;
}
// check dependencies
$dependencyMessages = $this->checkDependencies($modules);
if (!empty($dependencyMessages)) {
$output->writeln($dependencyMessages);
// we must have an exit code higher than zero to indicate something was wrong
return Cli::RETURN_FAILURE;
}
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion(
'You are about to remove code and/or database tables. Are you sure?[y/N]',
false
);
if (!$helper->ask($input, $output, $question) && $input->isInteractive()) {
return Cli::RETURN_FAILURE;
}
$result = $this->maintenanceModeEnabler->executeInMaintenanceMode(
function () use ($input, $output, $modules, $helper) {
try {
$this->takeBackup($input, $output);
$dbBackupOption = $input->getOption(self::INPUT_KEY_BACKUP_DB);
if ($input->getOption(self::INPUT_KEY_REMOVE_DATA)) {
$this->removeData($modules, $output, $dbBackupOption);
} else {
if (!empty($this->collector->collectUninstall())) {
$question = new ConfirmationQuestion(
'You are about to remove a module(s) that might have database data. '
. 'Do you want to remove the data from database?[y/N]',
false
);
if ($helper->ask($input, $output, $question) || !$input->isInteractive()) {
$this->removeData($modules, $output, $dbBackupOption);
}
} else {
$output->writeln(
'You are about to remove a module(s) that might have database data. '
. 'Remove the database data manually after uninstalling, if desired.'
);
}
}
$this->moduleRegistryUninstaller->removeModulesFromDb($output, $modules);
$this->moduleRegistryUninstaller->removeModulesFromDeploymentConfig($output, $modules);
$this->moduleUninstaller->uninstallCode($output, $modules);
$this->cleanup($input, $output);
return Cli::RETURN_SUCCESS;
} catch (\Exception $e) {
$output->writeln('' . $e->getMessage() . '');
$output->writeln('Please disable maintenance mode after you resolved above issues');
return Cli::RETURN_FAILURE;
}
},
$output,
true
);
return $result;
}
/**
* Check backup options and take backup appropriately
*
* @param InputInterface $input
* @param OutputInterface $output
* @return void
*/
private function takeBackup(InputInterface $input, OutputInterface $output)
{
$time = time();
if ($input->getOption(self::INPUT_KEY_BACKUP_CODE)) {
$codeBackup = $this->backupRollbackFactory->create($output);
$codeBackup->codeBackup($time);
}
if ($input->getOption(self::INPUT_KEY_BACKUP_MEDIA)) {
$mediaBackup = $this->backupRollbackFactory->create($output);
$mediaBackup->codeBackup($time, Factory::TYPE_MEDIA);
}
if ($input->getOption(self::INPUT_KEY_BACKUP_DB)) {
$dbBackup = $this->backupRollbackFactory->create($output);
$this->setAreaCode();
$dbBackup->dbBackup($time);
}
}
/**
* Invoke remove data routine in each specified module
*
* @param string[] $modules
* @param OutputInterface $output
* @param bool $dbBackupOption
* @return void
*/
private function removeData(array $modules, OutputInterface $output, $dbBackupOption)
{
if (!$dbBackupOption) {
$output->writeln('You are removing data without a database backup.');
} else {
$output->writeln('Removing data');
}
$this->moduleUninstaller->uninstallData($output, $modules);
}
/**
* Validate list of modules against installed composer packages and return error messages
*
* @param string[] $modules
* @return string[]
*/
protected function validate(array $modules)
{
$messages = [];
$unknownPackages = [];
$unknownModules = [];
$installedPackages = $this->composer->getRootRequiredPackages();
foreach ($modules as $module) {
if (array_search($this->packageInfo->getPackageName($module), $installedPackages) === false) {
$unknownPackages[] = $module;
}
if (!$this->fullModuleList->has($module)) {
$unknownModules[] = $module;
}
}
$unknownPackages = array_diff($unknownPackages, $unknownModules);
if (!empty($unknownPackages)) {
$text = count($unknownPackages) > 1 ?
' are not installed composer packages' : ' is not an installed composer package';
$messages[] = '' . implode(', ', $unknownPackages) . $text . '';
}
if (!empty($unknownModules)) {
$messages[] = 'Unknown module(s): ' . implode(', ', $unknownModules) . '';
}
return $messages;
}
/**
* Check for dependencies to modules, return error messages
*
* @param string[] $modules
* @return string[]
*/
private function checkDependencies(array $modules)
{
$messages = [];
$dependencies = $this->dependencyChecker->checkDependenciesWhenDisableModules(
$modules,
$this->fullModuleList->getNames()
);
foreach ($dependencies as $module => $dependingModules) {
if (!empty($dependingModules)) {
$messages[] =
"Cannot uninstall module '$module' because the following module(s) depend on it:" .
PHP_EOL . "\t" . implode('' . PHP_EOL . "\t", array_keys($dependingModules)) .
"";
}
}
return $messages;
}
/**
* Sets area code to start a session for database backup and rollback
*
* @return void
*/
private function setAreaCode()
{
$areaCode = 'adminhtml';
/** @var \Magento\Framework\App\State $appState */
$appState = $this->objectManager->get(\Magento\Framework\App\State::class);
$appState->setAreaCode($areaCode);
/** @var \Magento\Framework\ObjectManager\ConfigLoaderInterface $configLoader */
$configLoader = $this->objectManager->get(\Magento\Framework\ObjectManager\ConfigLoaderInterface::class);
$this->objectManager->configure($configLoader->load($areaCode));
}
}