Completed
Push — master ( c37ad9...8221f3 )
by Marko
129:14 queued 114:10
created

CKEditorInstallerCommand   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 355
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
dl 0
loc 355
ccs 0
cts 220
cp 0
rs 8.3396
c 0
b 0
f 0
wmc 44

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 2
A createProgressBar() 0 3 2
A startProgressBar() 0 3 2
A finishProgressBar() 0 4 1
A info() 0 3 1
A block() 0 21 4
A choice() 0 17 2
A execute() 0 10 2
A advanceProgressBar() 0 3 2
B configure() 0 27 1
B createOptions() 0 25 6
A title() 0 8 1
D createNotifier() 0 82 16
A comment() 0 4 1
A success() 0 3 1

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.

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 FOS\CKEditorBundle\Command;
13
14
use FOS\CKEditorBundle\Installer\CKEditorInstaller;
15
use Symfony\Component\Console\Command\Command;
16
use Symfony\Component\Console\Helper\ProgressBar;
17
use Symfony\Component\Console\Helper\ProgressHelper;
0 ignored issues
show
Bug introduced by Eric GELOEN
The type Symfony\Component\Console\Helper\ProgressHelper was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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
    public function __construct(CKEditorInstaller $installer = null)
39
    {
40
        parent::__construct();
41
42
        $this->installer = $installer ?: new CKEditorInstaller();
43
    }
44
45
    /**
46
     * {@inheritdoc}
47
     */
48
    protected function configure()
49
    {
50
        $this
51
            ->setName('ckeditor:install')
52
            ->setDescription('Install CKEditor')
53
            ->addArgument('path', InputArgument::OPTIONAL, 'Where to install CKEditor')
54
            ->addOption(
55
                'release',
56
                null,
57
                InputOption::VALUE_OPTIONAL,
58
                'CKEditor release (basic, standard or full)'
59
            )
60
            ->addOption('tag', null, InputOption::VALUE_OPTIONAL, 'CKEditor tag (x.y.z or latest)')
61
            ->addOption(
62
                'clear',
63
                null,
64
                InputOption::VALUE_OPTIONAL,
65
                'How to clear previous CKEditor installation (drop, keep or skip)'
66
            )
67
            ->addOption(
68
                'exclude',
69
                null,
70
                InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL,
71
                'Path to exclude when extracting CKEditor'
72
            )
73
            ->setHelp(
74
                <<<'EOF'
75
The <info>%command.name%</info> command install CKEditor in your application:
76
77
  <info>php %command.full_name%</info>
78
  
79
You can install it at a specific path (absolute):
80
81
  <info>php %command.full_name% path</info>
82
  
83
You can install a specific release (basic, standard or full):
84
85
  <info>php %command.full_name% --release=full</info>
86
  
87
You can install a specific version:
88
89
  <info>php %command.full_name% --tag=4.7.0</info>
90
91
If there is a previous CKEditor installation detected, 
92
you can control how it should be handled in non-interactive mode:
93
94
  <info>php %command.full_name% --clear=drop</info>
95
  <info>php %command.full_name% --clear=keep</info>
96
  <info>php %command.full_name% --clear=skip</info>
97
  
98
You can exclude path(s) when extracting CKEditor:
99
100
  <info>php %command.full_name% --exclude=samples --exclude=adapters</info>
101
EOF
102
            );
103
    }
104
105
    /**
106
     * {@inheritdoc}
107
     */
108
    protected function execute(InputInterface $input, OutputInterface $output)
109
    {
110
        $this->title($output);
111
112
        $success = $this->installer->install($this->createOptions($input, $output));
113
114
        if ($success) {
0 ignored issues
show
introduced by Eric GELOEN
The condition $success is always true.
Loading history...
115
            $this->success('CKEditor has been successfully installed...', $output);
116
        } else {
117
            $this->info('CKEditor installation has been skipped...', $output);
118
        }
119
    }
120
121
    /**
122
     * @param InputInterface  $input
123
     * @param OutputInterface $output
124
     *
125
     * @return mixed[]
126
     */
127
    private function createOptions(InputInterface $input, OutputInterface $output)
128
    {
129
        $options = ['notifier' => $this->createNotifier($input, $output)];
130
131
        if ($input->hasArgument('path')) {
132
            $options['path'] = $input->getArgument('path');
133
        }
134
135
        if ($input->hasOption('release')) {
136
            $options['release'] = $input->getOption('release');
137
        }
138
139
        if ($input->hasOption('tag')) {
140
            $options['version'] = $input->getOption('tag');
141
        }
142
143
        if ($input->hasOption('exclude')) {
144
            $options['excludes'] = $input->getOption('exclude');
145
        }
146
147
        if ($input->hasOption('clear')) {
148
            $options['clear'] = $input->getOption('clear');
149
        }
150
151
        return array_filter($options);
152
    }
153
154
    /**
155
     * @param InputInterface  $input
156
     * @param OutputInterface $output
157
     *
158
     * @return \Closure
159
     */
160
    private function createNotifier(InputInterface $input, OutputInterface $output)
161
    {
162
        $clear = $this->createProgressBar($output);
163
        $download = $this->createProgressBar($output);
164
        $extract = $this->createProgressBar($output);
165
166
        return function ($type, $data) use ($input, $output, $clear, $download, $extract) {
167
            switch ($type) {
168
                case CKEditorInstaller::NOTIFY_CLEAR:
169
                    $result = $this->choice(
170
                        [
171
                            sprintf('CKEditor is already installed in "%s"...', $data),
172
                            '',
173
                            'What do you want to do?',
174
                        ],
175
                        $choices = [
176
                            CKEditorInstaller::CLEAR_DROP => 'Drop the directory & reinstall CKEditor',
177
                            CKEditorInstaller::CLEAR_KEEP => 'Keep the directory & reinstall CKEditor by overriding files',
178
                            CKEditorInstaller::CLEAR_SKIP => 'Skip installation',
179
                        ],
180
                        CKEditorInstaller::CLEAR_DROP,
181
                        $input,
182
                        $output
183
                    );
184
185
                    if (($key = array_search($result, $choices, true)) !== false) {
186
                        $result = $key;
187
                    }
188
189
                    if ($result === CKEditorInstaller::CLEAR_DROP) {
190
                        $this->comment(sprintf('Dropping CKEditor from "%s"', $data), $output);
191
                    }
192
193
                    return $result;
194
195
                case CKEditorInstaller::NOTIFY_CLEAR_ARCHIVE:
196
                    $this->comment(sprintf('Dropping CKEditor ZIP archive "%s"', $data), $output);
197
                    break;
198
199
                case CKEditorInstaller::NOTIFY_CLEAR_COMPLETE:
200
                    $this->finishProgressBar($clear, $output);
201
                    break;
202
203
                case CKEditorInstaller::NOTIFY_CLEAR_PROGRESS:
204
                    $clear->advance();
205
                    break;
206
207
                case CKEditorInstaller::NOTIFY_CLEAR_SIZE:
208
                    $this->startProgressBar($clear, $output, $data);
209
                    break;
210
211
                case CKEditorInstaller::NOTIFY_DOWNLOAD:
212
                    $this->comment(sprintf('Downloading CKEditor ZIP archive from "%s"', $data), $output);
213
                    break;
214
215
                case CKEditorInstaller::NOTIFY_DOWNLOAD_COMPLETE:
216
                    $this->finishProgressBar($download, $output);
217
                    break;
218
219
                case CKEditorInstaller::NOTIFY_DOWNLOAD_PROGRESS:
220
                    $this->advanceProgressBar($download, $data);
221
                    break;
222
223
                case CKEditorInstaller::NOTIFY_DOWNLOAD_SIZE:
224
                    $this->startProgressBar($download, $output, $data);
225
                    break;
226
227
                case CKEditorInstaller::NOTIFY_EXTRACT:
228
                    $this->comment(sprintf('Extracting CKEditor ZIP archive to "%s"', $data), $output);
229
                    break;
230
231
                case CKEditorInstaller::NOTIFY_EXTRACT_COMPLETE:
232
                    $this->finishProgressBar($extract, $output);
233
                    break;
234
235
                case CKEditorInstaller::NOTIFY_EXTRACT_PROGRESS:
236
                    $extract->advance();
237
                    break;
238
239
                case CKEditorInstaller::NOTIFY_EXTRACT_SIZE:
240
                    $this->startProgressBar($extract, $output, $data);
241
                    break;
242
            }
243
        };
244
    }
245
246
    /**
247
     * @param OutputInterface $output
248
     */
249
    private function title(OutputInterface $output)
250
    {
251
        $output->writeln(
252
            [
253
                '----------------------',
254
                '| CKEditor Installer |',
255
                '----------------------',
256
                '',
257
            ]
258
        );
259
    }
260
261
    /**
262
     * @param string|string[] $message
263
     * @param OutputInterface $output
264
     */
265
    private function comment($message, OutputInterface $output)
266
    {
267
        $output->writeln(' // '.$message);
0 ignored issues
show
Bug introduced by Eric GELOEN
Are you sure $message of type string|string[] can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

267
        $output->writeln(' // './** @scrutinizer ignore-type */ $message);
Loading history...
268
        $output->writeln('');
269
    }
270
271
    /**
272
     * @param string          $message
273
     * @param OutputInterface $output
274
     */
275
    private function success($message, OutputInterface $output)
276
    {
277
        $this->block('[OK] - '.$message, $output, 'green', 'black');
278
    }
279
280
    /**
281
     * @param string          $message
282
     * @param OutputInterface $output
283
     */
284
    private function info($message, OutputInterface $output)
285
    {
286
        $this->block('[INFO] - '.$message, $output, 'yellow', 'black');
287
    }
288
289
    /**
290
     * @param string          $message
291
     * @param OutputInterface $output
292
     * @param string          $background
293
     * @param string          $font
294
     */
295
    private function block($message, OutputInterface $output, $background = null, $font = null)
296
    {
297
        $options = [];
298
299
        if ($background !== null) {
300
            $options[] = 'bg='.$background;
301
        }
302
303
        if ($font !== null) {
304
            $options[] = 'fg='.$font;
305
        }
306
307
        $pattern = ' %s ';
308
309
        if (!empty($options)) {
310
            $pattern = '<'.implode(';', $options).'>'.$pattern.'</>';
311
        }
312
313
        $output->writeln($block = sprintf($pattern, str_repeat(' ', strlen($message))));
314
        $output->writeln(sprintf($pattern, $message));
315
        $output->writeln($block);
316
    }
317
318
    /**
319
     * @param string|string[] $question
320
     * @param string[]        $choices
321
     * @param string          $default
322
     * @param InputInterface  $input
323
     * @param OutputInterface $output
324
     *
325
     * @return string|null
326
     */
327
    private function choice($question, array $choices, $default, InputInterface $input, OutputInterface $output)
328
    {
329
        $helper = new QuestionHelper();
330
331
        if (is_array($question)) {
332
            $question = implode("\n", $question);
333
        }
334
335
        $result = $helper->ask(
336
            $input,
337
            $output,
338
            new ChoiceQuestion($question, $choices, $choices[$default])
339
        );
340
341
        $output->writeln('');
342
343
        return $result;
0 ignored issues
show
Bug Best Practice introduced by Eric GELOEN
The expression return $result also could return the type boolean which is incompatible with the documented return type null|string.
Loading history...
344
    }
345
346
    /**
347
     * @param OutputInterface $output
348
     *
349
     * @return ProgressBar|ProgressHelper
350
     */
351
    private function createProgressBar(OutputInterface $output)
352
    {
353
        return class_exists(ProgressBar::class) ? new ProgressBar($output) : new ProgressHelper();
354
    }
355
356
    /**
357
     * @param ProgressBar|ProgressHelper $progress
358
     * @param OutputInterface            $output
359
     * @param int|null                   $max
360
     */
361
    private function startProgressBar($progress, OutputInterface $output, $max = null)
362
    {
363
        class_exists(ProgressBar::class) ? $progress->start($max) : $progress->start($output, $max);
0 ignored issues
show
Unused Code introduced by Eric GELOEN
The call to Symfony\Component\Consol...er\ProgressBar::start() has too many arguments starting with $max. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

363
        class_exists(ProgressBar::class) ? $progress->start($max) : $progress->/** @scrutinizer ignore-call */ start($output, $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. Please note the @ignore annotation hint above.

Loading history...
Bug introduced by Eric GELOEN
$output of type Symfony\Component\Console\Output\OutputInterface is incompatible with the type null|integer expected by parameter $max of Symfony\Component\Consol...er\ProgressBar::start(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

363
        class_exists(ProgressBar::class) ? $progress->start($max) : $progress->start(/** @scrutinizer ignore-type */ $output, $max);
Loading history...
364
    }
365
366
    /**
367
     * @param ProgressBar|ProgressHelper $progress
368
     * @param int                        $current
369
     */
370
    private function advanceProgressBar($progress, $current)
371
    {
372
        class_exists(ProgressBar::class) ? $progress->setProgress($current) : $progress->setCurrent($current);
0 ignored issues
show
Bug introduced by Eric GELOEN
The method setCurrent() does not exist on Symfony\Component\Console\Helper\ProgressBar. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

372
        class_exists(ProgressBar::class) ? $progress->setProgress($current) : $progress->/** @scrutinizer ignore-call */ setCurrent($current);

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...
373
    }
374
375
    /**
376
     * @param ProgressBar|ProgressHelper $progress
377
     * @param OutputInterface            $output
378
     */
379
    private function finishProgressBar($progress, OutputInterface $output)
380
    {
381
        $progress->finish();
382
        $output->writeln(['', '']);
383
    }
384
}
385