CKEditorInstallerCommand   B
last analyzed

Complexity

Total Complexity 43

Size/Duplication

Total Lines 345
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 7

Test Coverage

Coverage 95.91%

Importance

Changes 0
Metric Value
wmc 43
lcom 2
cbo 7
dl 0
loc 345
ccs 164
cts 171
cp 0.9591
rs 8.3157
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 2
A configure() 0 50 1
A execute() 0 12 2
B createOptions() 0 26 6
A title() 0 9 1
A comment() 0 5 1
A success() 0 4 1
A info() 0 4 1
B block() 0 22 4
A choice() 0 14 1
A createProgressBar() 0 4 2
A startProgressBar() 0 4 2
A advanceProgressBar() 0 4 2
A finishProgressBar() 0 5 1
D createNotifier() 0 85 16

How to fix   Complexity   

Complex Class

Complex classes like CKEditorInstallerCommand often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CKEditorInstallerCommand, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of the Ivory CKEditor package.
5
 *
6
 * (c) Eric GELOEN <[email protected]>
7
 *
8
 * For the full copyright and license information, please read the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Ivory\CKEditorBundle\Command;
13
14
use Ivory\CKEditorBundle\Installer\CKEditorInstaller;
15
use Symfony\Component\Console\Command\Command;
16
use Symfony\Component\Console\Helper\ProgressBar;
17
use Symfony\Component\Console\Helper\ProgressHelper;
18
use Symfony\Component\Console\Helper\QuestionHelper;
19
use Symfony\Component\Console\Input\InputArgument;
20
use Symfony\Component\Console\Input\InputInterface;
21
use Symfony\Component\Console\Input\InputOption;
22
use Symfony\Component\Console\Output\OutputInterface;
23
use Symfony\Component\Console\Question\ChoiceQuestion;
24
25
/**
26
 * @author GeLo <[email protected]>
27
 */
28
class CKEditorInstallerCommand extends Command
29
{
30
    /**
31
     * @var CKEditorInstaller
32
     */
33
    private $installer;
34
35
    /**
36
     * @param CKEditorInstaller|null $installer
37
     */
38 12
    public function __construct(CKEditorInstaller $installer = null)
39
    {
40 12
        parent::__construct();
41
42 12
        $this->installer = $installer ?: new CKEditorInstaller();
43 12
    }
44
45
    /**
46
     * {@inheritdoc}
47
     */
48 12
    protected function configure()
49
    {
50 10
        $this
51 12
            ->setName('ckeditor:install')
52 12
            ->setDescription('Install CKEditor')
53 12
            ->addArgument('path', InputArgument::OPTIONAL, 'Where to install CKEditor')
54 12
            ->addOption('release', null, InputOption::VALUE_OPTIONAL, 'CKEditor release (basic, standard or full)')
55 12
            ->addOption('tag', null, InputOption::VALUE_OPTIONAL, 'CKEditor tag (x.y.z or latest)')
56 12
            ->addOption(
57 12
                'clear',
58 12
                null,
59 12
                InputOption::VALUE_OPTIONAL,
60 2
                'How to clear previous CKEditor installation (drop, keep or skip)'
61 10
            )
62 12
            ->addOption(
63 12
                'exclude',
64 12
                null,
65 12
                InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL,
66 2
                'Path to exclude when extracting CKEditor'
67 10
            )
68 12
            ->setHelp(<<<'EOF'
69 2
The <info>%command.name%</info> command install CKEditor in your application:
70
71
  <info>php %command.full_name%</info>
72
  
73
You can install it at a specific path (absolute):
74
75
  <info>php %command.full_name% path</info>
76
  
77
You can install a specific release (basic, standard or full):
78
79
  <info>php %command.full_name% --release=full</info>
80
  
81
You can install a specific version:
82
83
  <info>php %command.full_name% --tag=4.7.0</info>
84
85
If there is a previous CKEditor installation detected, 
86
you can control how it should be handled in non-interactive mode:
87
88
  <info>php %command.full_name% --clear=drop</info>
89
  <info>php %command.full_name% --clear=keep</info>
90
  <info>php %command.full_name% --clear=skip</info>
91
  
92
You can exclude path(s) when extracting CKEditor:
93
94
  <info>php %command.full_name% --exclude=samples --exclude=adapters</info>
95
EOF
96 10
            );
97 12
    }
98
99
    /**
100
     * {@inheritdoc}
101
     */
102 12
    protected function execute(InputInterface $input, OutputInterface $output)
103
    {
104 12
        $this->title($output);
105
106 12
        $success = $this->installer->install($this->createOptions($input, $output));
107
108 12
        if ($success) {
109 12
            $this->success('CKEditor has been successfully installed...', $output);
110 10
        } else {
111
            $this->info('CKEditor installation has been skipped...', $output);
112
        }
113 12
    }
114
115
    /**
116
     * @param InputInterface  $input
117
     * @param OutputInterface $output
118
     *
119
     * @return mixed[]
120
     */
121 12
    private function createOptions(InputInterface $input, OutputInterface $output)
122
    {
123 12
        $options = ['notifier' => $this->createNotifier($input, $output)];
124
125 12
        if ($input->hasArgument('path')) {
126 12
            $options['path'] = $input->getArgument('path');
127 10
        }
128
129 12
        if ($input->hasOption('release')) {
130 12
            $options['release'] = $input->getOption('release');
131 10
        }
132
133 12
        if ($input->hasOption('tag')) {
134 12
            $options['version'] = $input->getOption('tag');
135 10
        }
136
137 12
        if ($input->hasOption('exclude')) {
138 12
            $options['excludes'] = $input->getOption('exclude');
139 10
        }
140
141 12
        if ($input->hasOption('clear')) {
142 12
            $options['clear'] = $input->getOption('clear');
143 10
        }
144
145 12
        return array_filter($options);
146
    }
147
148
    /**
149
     * @param InputInterface  $input
150
     * @param OutputInterface $output
151
     *
152
     * @return \Closure
153
     */
154 12
    private function createNotifier(InputInterface $input, OutputInterface $output)
155
    {
156 12
        $clear = $this->createProgressBar($output);
157 12
        $download = $this->createProgressBar($output);
158 12
        $extract = $this->createProgressBar($output);
159
160 12
        return function ($type, $data) use ($input, $output, $clear, $download, $extract) {
161
            switch ($type) {
162 12
                case CKEditorInstaller::NOTIFY_CLEAR:
163 2
                    $result = $this->choice(
164
                        [
165 2
                            sprintf('CKEditor is already installed in "%s"...', $data),
166 2
                            '',
167 2
                            'What do you want to do?',
168 2
                        ],
169
                        $choices = [
170 2
                            CKEditorInstaller::CLEAR_DROP => 'Drop the directory & reinstall CKEditor',
171 2
                            CKEditorInstaller::CLEAR_KEEP => 'Keep the directory & reinstall CKEditor by overriding files',
172 2
                            CKEditorInstaller::CLEAR_SKIP => 'Skip installation',
173 2
                        ],
174 2
                        CKEditorInstaller::CLEAR_DROP,
175 2
                        $input,
176
                        $output
177 2
                    );
178
179 2
                    if (($key = array_search($result, $choices, true)) !== false) {
180
                        $result = $key;
181
                    }
182
183 2
                    if ($result === CKEditorInstaller::CLEAR_DROP) {
184 2
                        $this->comment(sprintf('Dropping CKEditor from "%s"', $data), $output);
185 2
                    }
186
187 2
                    return $result;
188
189 12
                case CKEditorInstaller::NOTIFY_CLEAR_ARCHIVE:
190 12
                    $this->comment(sprintf('Dropping CKEditor ZIP archive "%s"', $data), $output);
191 12
                    break;
192
193 12
                case CKEditorInstaller::NOTIFY_CLEAR_COMPLETE:
194 2
                    $this->finishProgressBar($clear, $output);
195 2
                    break;
196
197 12
                case CKEditorInstaller::NOTIFY_CLEAR_PROGRESS:
198 2
                    $clear->advance();
199 2
                    break;
200
201 12
                case CKEditorInstaller::NOTIFY_CLEAR_SIZE:
202 2
                    $this->startProgressBar($clear, $output, $data);
203 2
                    break;
204
205 12
                case CKEditorInstaller::NOTIFY_DOWNLOAD:
206 12
                    $this->comment(sprintf('Downloading CKEditor ZIP archive from "%s"', $data), $output);
207 12
                    break;
208
209 12
                case CKEditorInstaller::NOTIFY_DOWNLOAD_COMPLETE:
210 12
                    $this->finishProgressBar($download, $output);
211 12
                    break;
212
213 12
                case CKEditorInstaller::NOTIFY_DOWNLOAD_PROGRESS:
214 12
                    $this->advanceProgressBar($download, $data);
215 12
                    break;
216
217 12
                case CKEditorInstaller::NOTIFY_DOWNLOAD_SIZE:
218 12
                    $this->startProgressBar($download, $output, $data);
219 12
                    break;
220
221 12
                case CKEditorInstaller::NOTIFY_EXTRACT:
222 12
                    $this->comment(sprintf('Extracting CKEditor ZIP archive to "%s"', $data), $output);
223 12
                    break;
224
225 12
                case CKEditorInstaller::NOTIFY_EXTRACT_COMPLETE:
226 12
                    $this->finishProgressBar($extract, $output);
227 12
                    break;
228
229 12
                case CKEditorInstaller::NOTIFY_EXTRACT_PROGRESS:
230 12
                    $extract->advance();
231 12
                    break;
232
233 12
                case CKEditorInstaller::NOTIFY_EXTRACT_SIZE:
234 12
                    $this->startProgressBar($extract, $output, $data);
235 12
                    break;
236
            }
237 12
        };
238
    }
239
240
    /**
241
     * @param OutputInterface $output
242
     */
243 12
    private function title(OutputInterface $output)
244
    {
245 12
        $output->writeln([
246 12
            '----------------------',
247 10
            '| CKEditor Installer |',
248 10
            '----------------------',
249 10
            '',
250 10
        ]);
251 12
    }
252
253
    /**
254
     * @param string|string[] $message
255
     * @param OutputInterface $output
256
     */
257 12
    private function comment($message, OutputInterface $output)
258
    {
259 12
        $output->writeln(' // '.$message);
260 12
        $output->writeln('');
261 12
    }
262
263
    /**
264
     * @param string          $message
265
     * @param OutputInterface $output
266
     */
267 12
    private function success($message, OutputInterface $output)
268
    {
269 12
        $this->block('[OK] - '.$message, $output, 'green', 'black');
270 12
    }
271
272
    /**
273
     * @param string          $message
274
     * @param OutputInterface $output
275
     */
276
    private function info($message, OutputInterface $output)
277
    {
278
        $this->block('[INFO] - '.$message, $output, 'yellow', 'black');
279
    }
280
281
    /**
282
     * @param string          $message
283
     * @param OutputInterface $output
284
     * @param string          $background
285
     * @param string          $font
286
     */
287 12
    private function block($message, OutputInterface $output, $background = null, $font = null)
288
    {
289 12
        $options = [];
290
291 12
        if ($background !== null) {
292 12
            $options[] = 'bg='.$background;
293 10
        }
294
295 12
        if ($font !== null) {
296 12
            $options[] = 'fg='.$font;
297 10
        }
298
299 12
        $pattern = ' %s ';
300
301 12
        if (!empty($options)) {
302 12
            $pattern = '<'.implode(';', $options).'>'.$pattern.'</>';
303 10
        }
304
305 12
        $output->writeln($block = sprintf($pattern, str_repeat(' ', strlen($message))));
306 12
        $output->writeln(sprintf($pattern, $message));
307 12
        $output->writeln($block);
308 12
    }
309
310
    /**
311
     * @param string|string[] $question
312
     * @param string[]        $choices
313
     * @param string          $default
314
     * @param InputInterface  $input
315
     * @param OutputInterface $output
316
     *
317
     * @return string|null
318
     */
319 2
    private function choice($question, array $choices, $default, InputInterface $input, OutputInterface $output)
320
    {
321 2
        $helper = new QuestionHelper();
322
323 2
        $result = $helper->ask($input, $output, new ChoiceQuestion(
324 2
            $question,
0 ignored issues
show
Bug introduced by
It seems like $question defined by parameter $question on line 319 can also be of type array<integer,string>; however, Symfony\Component\Consol...Question::__construct() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
325 2
            $choices,
326 2
            $choices[$default]
327 2
        ));
328
329 2
        $output->writeln('');
330
331 2
        return $result;
332
    }
333
334
    /**
335
     * @param OutputInterface $output
336
     *
337
     * @return ProgressBar|ProgressHelper
338
     */
339 12
    private function createProgressBar(OutputInterface $output)
340
    {
341 12
        return class_exists(ProgressBar::class) ? new ProgressBar($output) : new ProgressHelper();
342
    }
343
344
    /**
345
     * @param ProgressBar|ProgressHelper $progress
346
     * @param OutputInterface            $output
347
     * @param int|null                   $max
348
     */
349 12
    private function startProgressBar($progress, OutputInterface $output, $max = null)
350
    {
351 12
        class_exists(ProgressBar::class) ? $progress->start($max) : $progress->start($output, $max);
0 ignored issues
show
Documentation introduced by
$output is of type object<Symfony\Component...Output\OutputInterface>, but the function expects a integer|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Unused Code introduced by
The call to ProgressBar::start() has too many arguments starting with $max.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
352 12
    }
353
354
    /**
355
     * @param ProgressBar|ProgressHelper $progress
356
     * @param int                        $current
357
     */
358 12
    private function advanceProgressBar($progress, $current)
359
    {
360 12
        class_exists(ProgressBar::class) ? $progress->setProgress($current) : $progress->setCurrent($current);
0 ignored issues
show
Bug introduced by
The method setCurrent() does not seem to exist on object<Symfony\Component...ole\Helper\ProgressBar>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
361 12
    }
362
363
    /**
364
     * @param ProgressBar|ProgressHelper $progress
365
     * @param OutputInterface            $output
366
     */
367 12
    private function finishProgressBar($progress, OutputInterface $output)
368
    {
369 12
        $progress->finish();
370 12
        $output->writeln(['', '']);
371 12
    }
372
}
373