Completed
Pull Request — master (#2233)
by Revin
64:12
created

ModulesUninstallCommand::execute()   C

Complexity

Conditions 8
Paths 4

Size

Total Lines 71
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 71
c 0
b 0
f 0
rs 6.4391
cc 8
eloc 38
nc 4
nop 2

How to fix   Long Method   

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 Console\Modules;
4
5
/*
6
 * This file is part of Fork CMS.
7
 *
8
 * For the full copyright and license information, please view the license
9
 * file that was distributed with this source code.
10
 */
11
12
use Backend\Core\Engine\Model as BackendModel;
13
use Backend\Core\Installer\ModuleUninstaller;
14
use Backend\Core\Installer\UninstallerInterface;
15
use Backend\Core\Language\Language as BL;
16
use Console\Exceptions\ModuleNotExistsException;
17
use Console\Exceptions\ModuleNotInstalledException;
18
use Console\Exceptions\UninstallerClassNotFoundException;
19
use Console\Exceptions\UninstallerInterfaceException;
20
use Symfony\Component\Console\Input\InputArgument;
21
use Symfony\Component\Console\Input\InputInterface;
22
use Symfony\Component\Console\Input\InputOption;
23
use Symfony\Component\Console\Output\OutputInterface;
24
use Symfony\Component\Console\Question\Question;
25
use Symfony\Component\Filesystem\Filesystem;
26
27
/**
28
 * Console command for uninstall forkcms modules
29
 */
30
class ModulesUninstallCommand extends ModulesBaseInstallCommand
31
{
32
33
    protected function configure(): void
34
    {
35
        $this->setName('forkcms:modules:uninstall')
36
             ->setDescription('Uninstall module')
37
             ->addUsage('ModuleOne ModuleTwo')
38
             ->addUsage('--show-all')
39
             ->addArgument('modules-id', InputArgument::OPTIONAL | InputArgument::IS_ARRAY)
40
             ->addOption('show-all', 'a', InputOption::VALUE_OPTIONAL, 'Show all modules (default only installed)', false);
41
    }
42
43
    /**
44
     * @param \Symfony\Component\Console\Input\InputInterface $input
45
     * @param \Symfony\Component\Console\Output\OutputInterface $output
46
     * @return int
47
     * @throws \LogicException
48
     * @throws \Console\Exceptions\ModuleNotExistsException
49
     * @throws \Console\Exceptions\ModuleNotInstalledException
50
     * @throws \Console\Exceptions\UninstallerClassNotFoundException
51
     * @throws \Console\Exceptions\UninstallerInterfaceException
52
     * @throws \Symfony\Component\Console\Exception\LogicException
53
     * @throws \Symfony\Component\Console\Exception\RuntimeException
54
     * @throws \Symfony\Component\Console\Exception\InvalidArgumentException
55
     * @throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException
56
     * @throws \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException
57
     * @throws \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
58
     * @throws \Symfony\Component\Filesystem\Exception\IOException
59
     */
60
    public function execute(InputInterface $input, OutputInterface $output)
61
    {
62
        parent::execute($input, $output);
63
64
        // get parameters
65
        $modules = $input->getArgument('modules-id');
66
67
        if (empty($modules)) {
68
            $modules = $this->promptModule(static::PROMPT_INSTALLED, [$this, 'uninstallModulePromptFormat']);
69
        }
70
71
        if (empty($modules)) {
72
            $output->writeln('  Module not selected.');
73
        } else {
74
            foreach ($modules as $module) {
75
                // make sure this module can be installed
76
                $this->validateIfModuleCanBeUninstalled($module);
77
78
                $toRemove = [];
79
80
                $this->findInstalledDependsModules($toRemove, $module);
81
82
                if (empty($toRemove)) {
83
                    $quest = "\n  Are you sure you want to <error>remove</error> module <info>$module</info>?"
84
                        . "\n  <comment>This action can not be undone!</comment> (y/N) ";
85
86
                    if ($this->confirmAction($quest)) {
87
                        // do the actual install
88
                        $this->uninstallModule($module);
89
90
                        $output->writeln("\n    * <info>Module <comment>$module</comment> successful uninstalled</info>.\n");
91
                    } else {
92
                        $output->writeln('  Canceled.');
93
                    }
94
                } else {
95
                    arsort($toRemove);
96
97
                    $toRemoveModules = implode(', ', array_keys($toRemove));
98
99
                    $quest = "\n  Module <info>$module</info> depends on modules <comment>$toRemoveModules</comment>."
100
                        . "\n\n  To <error>remove</error> module <info>$module</info>, you will have to <error>remove</error> modules <comment>$toRemoveModules</comment>."
101
                        . "\n  Are you sure you want to do this?"
102
                        . "\n  <comment>This action can not be undone!</comment> (y/N) ";
103
104
                    if ($this->confirmAction($quest)) {
105
106
                        $output->writeln('');
107
108
                        // Adding main module to remove list
109
                        $toRemove[$module] = 0;
110
111
                        foreach ($toRemove as $toRemoveModule => $priority) {
112
                            // do the actual install
113
                            $this->uninstallModule($toRemoveModule);
114
115
                            $output->writeln("    * <info>Module <comment>$toRemoveModule</comment> successful uninstalled</info>.");
116
                        }
117
118
                        $output->writeln('');
119
                    } else {
120
                        $output->writeln('  Canceled.');
121
                    }
122
                }
123
            }
124
125
            // remove our container cache after this request
126
            (new Filesystem)->remove($this->getContainer()->getParameter('kernel.cache_dir'));
127
        }
128
129
        return 0;
130
    }
131
132
    /**
133
     * @param string $module
134
     * @return string
135
     * @throws \LogicException
136
     * @throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException
137
     * @throws \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException
138
     */
139
    public function uninstallModulePromptFormat(string $module): string
140
    {
141
        $module_pad = str_pad($module, 18, ' ', STR_PAD_RIGHT);
142
143
        $isInstalled = $this->isModuleInstalled($module);
144
145
        $installed = $isInstalled
146
            ? '<info>  ✔ installed</info>'
147
            : '<fg=magenta>not installed</>';
148
149
        try {
150
            $this->validateIfModuleCanBeUninstalled($module);
151
152
            $isCanBeUninstalled = true;
153
        } catch (\Exception $e) {
154
            $isCanBeUninstalled = false;
155
        }
156
157
        $canBeUninstalled = $isCanBeUninstalled
158
            ? ''
159
            : '<error>can not be uninstalled</error>';
160
161
        return "$module_pad $installed    $canBeUninstalled";
162
    }
163
164
    /**
165
     * @param string $module
166
     * @throws \Console\Exceptions\ModuleNotExistsException
167
     * @throws \Console\Exceptions\ModuleNotInstalledException
168
     * @throws \Console\Exceptions\UninstallerClassNotFoundException
169
     * @throws \Console\Exceptions\UninstallerInterfaceException
170
     * @throws \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException
171
     * @throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException
172
     */
173
    private function validateIfModuleCanBeUninstalled(string $module): void
174
    {
175
        // does the item exist
176
        if (!$this->existsModule($module)) {
177
            throw new ModuleNotExistsException($module);
178
        }
179
180
        // already installed
181
        if (!BackendModel::isModuleInstalled($module)) {
182
            throw new ModuleNotInstalledException($module);
183
        }
184
185
        $uninstallerFile = BACKEND_MODULES_PATH . '/' . $module . '/Installer/Uninstaller.php';
186
187
        // no installer class present
188
        if (!is_file($uninstallerFile)) {
189
            $installerFile = BACKEND_MODULES_PATH . '/' . $module . '/Installer/Installer.php';
190
191
            if (!is_file($installerFile)) {
192
                throw new UninstallerClassNotFoundException($module, $uninstallerFile);
193
            }
194
        }
195
196
        $installer = $this->createUninstaller($module);
197
198
        if (!($installer instanceof UninstallerInterface) && !($installer instanceof ModuleUninstaller)) {
199
            throw new UninstallerInterfaceException($module, $uninstallerFile);
200
        }
201
    }
202
203
    /**
204
     * @param string $module
205
     * @throws \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException
206
     * @throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException
207
     * @throws \Symfony\Component\Console\Exception\InvalidArgumentException
208
     * @throws \Symfony\Component\Console\Exception\LogicException
209
     * @throws \Symfony\Component\Console\Exception\RuntimeException
210
     */
211
    private function uninstallModule(string $module): void
212
    {
213
        $uninstaller = $this->createUninstaller($module);
214
        $uninstaller->setInput($this->input);
215
        $uninstaller->setOutput($this->output);
216
217
        $variables = $uninstaller->getPromptVariables();
218
219
        if (!empty($variables)) {
220
221
            /** @var \Symfony\Component\Console\Helper\SymfonyQuestionHelper $helper */
222
            $helper = $this->getHelper('question');
223
224
            foreach ($variables as $variable => $desc) {
225
226
                $question = new Question($desc . ':');
227
                $question->setMaxAttempts(1);
228
229
                $variables[$variable] = $helper->ask($this->input, $this->output, $question);
230
            }
231
        }
232
233
        $uninstaller->setVariables($variables);
234
235
        $uninstaller->uninstall();
236
237
        // clear the cache so locale (and so much more) gets rebuilt
238
        $this->clearCache();
239
    }
240
241
    /**
242
     * @param string $module
243
     * @param array $variables
244
     * @return \Backend\Core\Installer\UninstallerInterface
245
     * @throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException
246
     * @throws \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException
247
     */
248
    private function createUninstaller(string $module, array $variables = []): UninstallerInterface
249
    {
250
        $searchClass = 'Backend\\Modules\\' . $module . '\\Installer\\Uninstaller';
251
252
        if (class_exists($searchClass)) {
253
            $class = $searchClass;
254
        } else {
255
            $class = 'Backend\\Modules\\' . $module . '\\Installer\\Installer';
256
        }
257
258
        return new $class(
259
            BackendModel::getContainer()->get('database'),
260
            BL::getActiveLanguages(),
261
            array_keys(BL::getInterfaceLanguages()),
262
            false,
263
            $variables
264
        );
265
    }
266
}
267