Descriptor   F
last analyzed

Complexity

Total Complexity 60

Size/Duplication

Total Lines 300
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 138
c 1
b 0
f 0
dl 0
loc 300
ccs 0
cts 149
cp 0
rs 3.6
wmc 60

11 Methods

Rating   Name   Duplication   Size   Complexity  
A describeCommand() 0 26 5
B describeInputOption() 0 27 9
A formatDefaultValue() 0 3 1
A writeText() 0 4 4
A describeInputArgument() 0 16 4
A write() 0 3 2
B describeInputDefinition() 0 35 10
A describe() 0 22 6
B describeConsole() 0 49 11
A calculateTotalWidthForOptions() 0 16 4
A getColumnWidth() 0 12 4

How to fix   Complexity   

Complex Class

Complex classes like Descriptor 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 Descriptor, and based on these observations, apply Extract Interface, too.

1
<?php
2
// +----------------------------------------------------------------------
3
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
4
// +----------------------------------------------------------------------
5
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
6
// +----------------------------------------------------------------------
7
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
// +----------------------------------------------------------------------
9
// | Author: yunwuxin <[email protected]>
10
// +----------------------------------------------------------------------
11
12
namespace think\console\output;
13
14
use think\Console;
15
use think\console\Command;
16
use think\console\input\Argument as InputArgument;
17
use think\console\input\Definition as InputDefinition;
18
use think\console\input\Option as InputOption;
19
use think\console\Output;
20
use think\console\output\descriptor\Console as ConsoleDescription;
21
22
class Descriptor
23
{
24
25
    /**
26
     * @var Output
27
     */
28
    protected $output;
29
30
    /**
31
     * {@inheritdoc}
32
     */
33
    public function describe(Output $output, $object, array $options = [])
34
    {
35
        $this->output = $output;
36
37
        switch (true) {
38
            case $object instanceof InputArgument:
39
                $this->describeInputArgument($object, $options);
40
                break;
41
            case $object instanceof InputOption:
42
                $this->describeInputOption($object, $options);
43
                break;
44
            case $object instanceof InputDefinition:
45
                $this->describeInputDefinition($object, $options);
46
                break;
47
            case $object instanceof Command:
48
                $this->describeCommand($object, $options);
49
                break;
50
            case $object instanceof Console:
51
                $this->describeConsole($object, $options);
52
                break;
53
            default:
54
                throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_class($object)));
55
        }
56
    }
57
58
    /**
59
     * 输出内容
60
     * @param string $content
61
     * @param bool   $decorated
62
     */
63
    protected function write($content, $decorated = false)
64
    {
65
        $this->output->write($content, false, $decorated ? Output::OUTPUT_NORMAL : Output::OUTPUT_RAW);
66
    }
67
68
    /**
69
     * 描述参数
70
     * @param InputArgument $argument
71
     * @param array         $options
72
     * @return string|mixed
73
     */
74
    protected function describeInputArgument(InputArgument $argument, array $options = [])
75
    {
76
        if (null !== $argument->getDefault()
77
            && (!is_array($argument->getDefault())
78
                || count($argument->getDefault()))
79
        ) {
80
            $default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($argument->getDefault()));
81
        } else {
82
            $default = '';
83
        }
84
85
        $totalWidth   = $options['total_width'] ?? strlen($argument->getName());
86
        $spacingWidth = $totalWidth - strlen($argument->getName()) + 2;
87
88
        $this->writeText(sprintf("  <info>%s</info>%s%s%s", $argument->getName(), str_repeat(' ', $spacingWidth), // + 17 = 2 spaces + <info> + </info> + 2 spaces
89
            preg_replace('/\s*\R\s*/', PHP_EOL . str_repeat(' ', $totalWidth + 17), $argument->getDescription()), $default), $options);
90
    }
91
92
    /**
93
     * 描述选项
94
     * @param InputOption $option
95
     * @param array       $options
96
     * @return string|mixed
97
     */
98
    protected function describeInputOption(InputOption $option, array $options = [])
99
    {
100
        if ($option->acceptValue() && null !== $option->getDefault()
101
            && (!is_array($option->getDefault())
102
                || count($option->getDefault()))
103
        ) {
104
            $default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($option->getDefault()));
105
        } else {
106
            $default = '';
107
        }
108
109
        $value = '';
110
        if ($option->acceptValue()) {
111
            $value = '=' . strtoupper($option->getName());
112
113
            if ($option->isValueOptional()) {
114
                $value = '[' . $value . ']';
115
            }
116
        }
117
118
        $totalWidth = $options['total_width'] ?? $this->calculateTotalWidthForOptions([$option]);
119
        $synopsis   = sprintf('%s%s', $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : '    ', sprintf('--%s%s', $option->getName(), $value));
120
121
        $spacingWidth = $totalWidth - strlen($synopsis) + 2;
122
123
        $this->writeText(sprintf("  <info>%s</info>%s%s%s%s", $synopsis, str_repeat(' ', $spacingWidth), // + 17 = 2 spaces + <info> + </info> + 2 spaces
124
            preg_replace('/\s*\R\s*/', "\n" . str_repeat(' ', $totalWidth + 17), $option->getDescription()), $default, $option->isArray() ? '<comment> (multiple values allowed)</comment>' : ''), $options);
125
    }
126
127
    /**
128
     * 描述输入
129
     * @param InputDefinition $definition
130
     * @param array           $options
131
     * @return string|mixed
132
     */
133
    protected function describeInputDefinition(InputDefinition $definition, array $options = [])
134
    {
135
        $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions());
136
        foreach ($definition->getArguments() as $argument) {
137
            $totalWidth = max($totalWidth, strlen($argument->getName()));
138
        }
139
140
        if ($definition->getArguments()) {
141
            $this->writeText('<comment>Arguments:</comment>', $options);
142
            $this->writeText("\n");
143
            foreach ($definition->getArguments() as $argument) {
144
                $this->describeInputArgument($argument, array_merge($options, ['total_width' => $totalWidth]));
145
                $this->writeText("\n");
146
            }
147
        }
148
149
        if ($definition->getArguments() && $definition->getOptions()) {
150
            $this->writeText("\n");
151
        }
152
153
        if ($definition->getOptions()) {
154
            $laterOptions = [];
155
156
            $this->writeText('<comment>Options:</comment>', $options);
157
            foreach ($definition->getOptions() as $option) {
158
                if (strlen($option->getShortcut()) > 1) {
159
                    $laterOptions[] = $option;
160
                    continue;
161
                }
162
                $this->writeText("\n");
163
                $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
164
            }
165
            foreach ($laterOptions as $option) {
166
                $this->writeText("\n");
167
                $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
168
            }
169
        }
170
    }
171
172
    /**
173
     * 描述指令
174
     * @param Command $command
175
     * @param array   $options
176
     * @return string|mixed
177
     */
178
    protected function describeCommand(Command $command, array $options = [])
179
    {
180
        $command->getSynopsis(true);
181
        $command->getSynopsis(false);
182
        $command->mergeConsoleDefinition(false);
183
184
        $this->writeText('<comment>Usage:</comment>', $options);
185
        foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) {
186
            $this->writeText("\n");
187
            $this->writeText('  ' . $usage, $options);
188
        }
189
        $this->writeText("\n");
190
191
        $definition = $command->getNativeDefinition();
192
        if ($definition->getOptions() || $definition->getArguments()) {
193
            $this->writeText("\n");
194
            $this->describeInputDefinition($definition, $options);
195
            $this->writeText("\n");
196
        }
197
198
        if ($help = $command->getProcessedHelp()) {
199
            $this->writeText("\n");
200
            $this->writeText('<comment>Help:</comment>', $options);
201
            $this->writeText("\n");
202
            $this->writeText(' ' . str_replace("\n", "\n ", $help), $options);
203
            $this->writeText("\n");
204
        }
205
    }
206
207
    /**
208
     * 描述控制台
209
     * @param Console $console
210
     * @param array   $options
211
     * @return string|mixed
212
     */
213
    protected function describeConsole(Console $console, array $options = [])
214
    {
215
        $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
216
        $description        = new ConsoleDescription($console, $describedNamespace);
217
218
        if (isset($options['raw_text']) && $options['raw_text']) {
219
            $width = $this->getColumnWidth($description->getNamespaces());
220
221
            foreach ($description->getCommands() as $command) {
222
                $this->writeText(sprintf("%-{$width}s %s", $command->getName(), $command->getDescription()), $options);
223
                $this->writeText("\n");
224
            }
225
        } else {
226
            if ('' != $help = $console->getHelp()) {
227
                $this->writeText("$help\n\n", $options);
228
            }
229
230
            $this->writeText("<comment>Usage:</comment>\n", $options);
231
            $this->writeText("  command [options] [arguments]\n\n", $options);
232
233
            $this->describeInputDefinition(new InputDefinition($console->getDefinition()->getOptions()), $options);
234
235
            $this->writeText("\n");
236
            $this->writeText("\n");
237
238
            $width = $this->getColumnWidth($description->getNamespaces());
239
240
            if ($describedNamespace) {
241
                $this->writeText(sprintf('<comment>Available commands for the "%s" namespace:</comment>', $describedNamespace), $options);
242
            } else {
243
                $this->writeText('<comment>Available commands:</comment>', $options);
244
            }
245
246
            // add commands by namespace
247
            foreach ($description->getNamespaces() as $namespace) {
248
                if (!$describedNamespace && ConsoleDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
249
                    $this->writeText("\n");
250
                    $this->writeText(' <comment>' . $namespace['id'] . '</comment>', $options);
251
                }
252
253
                foreach ($namespace['commands'] as $name) {
254
                    $this->writeText("\n");
255
                    $spacingWidth = $width - strlen($name);
256
                    $this->writeText(sprintf("  <info>%s</info>%s%s", $name, str_repeat(' ', $spacingWidth), $description->getCommand($name)
257
                            ->getDescription()), $options);
258
                }
259
            }
260
261
            $this->writeText("\n");
262
        }
263
    }
264
265
    /**
266
     * {@inheritdoc}
267
     */
268
    private function writeText($content, array $options = [])
269
    {
270
        $this->write(isset($options['raw_text'])
271
            && $options['raw_text'] ? strip_tags($content) : $content, isset($options['raw_output']) ? !$options['raw_output'] : true);
272
    }
273
274
    /**
275
     * 格式化
276
     * @param mixed $default
277
     * @return string
278
     */
279
    private function formatDefaultValue($default)
280
    {
281
        return json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
282
    }
283
284
    /**
285
     * @param Namespaces[] $namespaces
286
     * @return int
287
     */
288
    private function getColumnWidth(array $namespaces)
289
    {
290
        $width = 0;
291
        foreach ($namespaces as $namespace) {
292
            foreach ($namespace['commands'] as $name) {
293
                if (strlen($name) > $width) {
294
                    $width = strlen($name);
295
                }
296
            }
297
        }
298
299
        return $width + 2;
300
    }
301
302
    /**
303
     * @param InputOption[] $options
304
     * @return int
305
     */
306
    private function calculateTotalWidthForOptions($options)
307
    {
308
        $totalWidth = 0;
309
        foreach ($options as $option) {
310
            $nameLength = 4 + strlen($option->getName()) + 2; // - + shortcut + , + whitespace + name + --
311
312
            if ($option->acceptValue()) {
313
                $valueLength = 1 + strlen($option->getName()); // = + value
314
                $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ]
315
316
                $nameLength += $valueLength;
317
            }
318
            $totalWidth = max($totalWidth, $nameLength);
319
        }
320
321
        return $totalWidth;
322
    }
323
}
324