Input   F
last analyzed

Complexity

Total Complexity 87

Size/Duplication

Total Lines 445
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
eloc 141
dl 0
loc 445
ccs 0
cts 165
cp 0
rs 2
c 0
b 0
f 0
wmc 87

26 Methods

Rating   Name   Duplication   Size   Complexity  
A hasParameterOption() 0 13 5
A getOptions() 0 3 1
A parseShortOptionSet() 0 15 5
B parse() 0 15 11
A addShortOption() 0 7 2
A getArguments() 0 3 1
A bind() 0 7 1
A getFirstArgument() 0 10 4
A setOption() 0 7 2
A __toString() 0 15 4
A setArgument() 0 7 2
A __construct() 0 11 2
A escapeToken() 0 3 2
A hasOption() 0 3 2
A getParameterOption() 0 20 6
A parseArgument() 0 15 5
A validate() 0 4 2
A setTokens() 0 3 1
A getArgument() 0 8 2
A hasArgument() 0 3 1
A setInteractive() 0 3 1
A parseShortOption() 0 14 4
A getOption() 0 7 2
A parseLongOption() 0 8 2
A isInteractive() 0 3 1
C addLongOption() 0 41 16

How to fix   Complexity   

Complex Class

Complex classes like Input 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 Input, 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
declare (strict_types = 1);
12
13
namespace think\console;
14
15
use think\console\input\Argument;
16
use think\console\input\Definition;
17
use think\console\input\Option;
18
19
class Input
20
{
21
22
    /**
23
     * @var Definition
24
     */
25
    protected $definition;
26
27
    /**
28
     * @var Option[]
29
     */
30
    protected $options = [];
31
32
    /**
33
     * @var Argument[]
34
     */
35
    protected $arguments = [];
36
37
    protected $interactive = true;
38
39
    private $tokens;
40
    private $parsed;
41
42
    public function __construct($argv = null)
43
    {
44
        if (null === $argv) {
45
            $argv = $_SERVER['argv'];
46
            // 去除命令名
47
            array_shift($argv);
48
        }
49
50
        $this->tokens = $argv;
51
52
        $this->definition = new Definition();
53
    }
54
55
    protected function setTokens(array $tokens)
56
    {
57
        $this->tokens = $tokens;
58
    }
59
60
    /**
61
     * 绑定实例
62
     * @param Definition $definition A InputDefinition instance
63
     */
64
    public function bind(Definition $definition): void
65
    {
66
        $this->arguments  = [];
67
        $this->options    = [];
68
        $this->definition = $definition;
69
70
        $this->parse();
71
    }
72
73
    /**
74
     * 解析参数
75
     */
76
    protected function parse(): void
77
    {
78
        $parseOptions = true;
79
        $this->parsed = $this->tokens;
80
        while (null !== $token = array_shift($this->parsed)) {
81
            if ($parseOptions && '' == $token) {
82
                $this->parseArgument($token);
83
            } elseif ($parseOptions && '--' == $token) {
84
                $parseOptions = false;
85
            } elseif ($parseOptions && 0 === strpos($token, '--')) {
86
                $this->parseLongOption($token);
87
            } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) {
88
                $this->parseShortOption($token);
89
            } else {
90
                $this->parseArgument($token);
91
            }
92
        }
93
    }
94
95
    /**
96
     * 解析短选项
97
     * @param string $token 当前的指令.
98
     */
99
    private function parseShortOption(string $token): void
100
    {
101
        $name = substr($token, 1);
102
103
        if (strlen($name) > 1) {
104
            if ($this->definition->hasShortcut($name[0])
105
                && $this->definition->getOptionForShortcut($name[0])->acceptValue()
106
            ) {
107
                $this->addShortOption($name[0], substr($name, 1));
108
            } else {
109
                $this->parseShortOptionSet($name);
110
            }
111
        } else {
112
            $this->addShortOption($name, null);
113
        }
114
    }
115
116
    /**
117
     * 解析短选项
118
     * @param string $name 当前指令
119
     * @throws \RuntimeException
120
     */
121
    private function parseShortOptionSet(string $name): void
122
    {
123
        $len = strlen($name);
124
        for ($i = 0; $i < $len; ++$i) {
125
            if (!$this->definition->hasShortcut($name[$i])) {
126
                throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $name[$i]));
127
            }
128
129
            $option = $this->definition->getOptionForShortcut($name[$i]);
130
            if ($option->acceptValue()) {
131
                $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1));
132
133
                break;
134
            } else {
135
                $this->addLongOption($option->getName(), null);
136
            }
137
        }
138
    }
139
140
    /**
141
     * 解析完整选项
142
     * @param string $token 当前指令
143
     */
144
    private function parseLongOption(string $token): void
145
    {
146
        $name = substr($token, 2);
147
148
        if (false !== $pos = strpos($name, '=')) {
149
            $this->addLongOption(substr($name, 0, $pos), substr($name, $pos + 1));
150
        } else {
151
            $this->addLongOption($name, null);
152
        }
153
    }
154
155
    /**
156
     * 解析参数
157
     * @param string $token 当前指令
158
     * @throws \RuntimeException
159
     */
160
    private function parseArgument(string $token): void
161
    {
162
        $c = count($this->arguments);
163
164
        if ($this->definition->hasArgument($c)) {
165
            $arg = $this->definition->getArgument($c);
166
167
            $this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token;
168
169
        } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) {
170
            $arg = $this->definition->getArgument($c - 1);
171
172
            $this->arguments[$arg->getName()][] = $token;
173
        } else {
174
            throw new \RuntimeException('Too many arguments.');
175
        }
176
    }
177
178
    /**
179
     * 添加一个短选项的值
180
     * @param string $shortcut 短名称
181
     * @param mixed  $value    值
182
     * @throws \RuntimeException
183
     */
184
    private function addShortOption(string $shortcut, $value): void
185
    {
186
        if (!$this->definition->hasShortcut($shortcut)) {
187
            throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut));
188
        }
189
190
        $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value);
191
    }
192
193
    /**
194
     * 添加一个完整选项的值
195
     * @param string $name  选项名
196
     * @param mixed  $value 值
197
     * @throws \RuntimeException
198
     */
199
    private function addLongOption(string $name, $value): void
200
    {
201
        if (!$this->definition->hasOption($name)) {
202
            throw new \RuntimeException(sprintf('The "--%s" option does not exist.', $name));
203
        }
204
205
        $option = $this->definition->getOption($name);
206
207
        if (false === $value) {
208
            $value = null;
209
        }
210
211
        if (null !== $value && !$option->acceptValue()) {
212
            throw new \RuntimeException(sprintf('The "--%s" option does not accept a value.', $name, $value));
213
        }
214
215
        if (null === $value && $option->acceptValue() && count($this->parsed)) {
216
            $next = array_shift($this->parsed);
217
            if (isset($next[0]) && '-' !== $next[0]) {
218
                $value = $next;
219
            } elseif (empty($next)) {
220
                $value = '';
221
            } else {
222
                array_unshift($this->parsed, $next);
223
            }
224
        }
225
226
        if (null === $value) {
227
            if ($option->isValueRequired()) {
228
                throw new \RuntimeException(sprintf('The "--%s" option requires a value.', $name));
229
            }
230
231
            if (!$option->isArray()) {
232
                $value = $option->isValueOptional() ? $option->getDefault() : true;
233
            }
234
        }
235
236
        if ($option->isArray()) {
237
            $this->options[$name][] = $value;
238
        } else {
239
            $this->options[$name] = $value;
240
        }
241
    }
242
243
    /**
244
     * 获取第一个参数
245
     * @return string|null
246
     */
247
    public function getFirstArgument()
248
    {
249
        foreach ($this->tokens as $token) {
250
            if ($token && '-' === $token[0]) {
251
                continue;
252
            }
253
254
            return $token;
255
        }
256
        return;
257
    }
258
259
    /**
260
     * 检查原始参数是否包含某个值
261
     * @param string|array $values 需要检查的值
262
     * @return bool
263
     */
264
    public function hasParameterOption($values): bool
265
    {
266
        $values = (array) $values;
267
268
        foreach ($this->tokens as $token) {
269
            foreach ($values as $value) {
270
                if ($token === $value || 0 === strpos($token, $value . '=')) {
271
                    return true;
272
                }
273
            }
274
        }
275
276
        return false;
277
    }
278
279
    /**
280
     * 获取原始选项的值
281
     * @param string|array $values  需要检查的值
282
     * @param mixed        $default 默认值
283
     * @return mixed The option value
284
     */
285
    public function getParameterOption($values, $default = false)
286
    {
287
        $values = (array) $values;
288
        $tokens = $this->tokens;
289
290
        while (0 < count($tokens)) {
291
            $token = array_shift($tokens);
292
293
            foreach ($values as $value) {
294
                if ($token === $value || 0 === strpos($token, $value . '=')) {
295
                    if (false !== $pos = strpos($token, '=')) {
296
                        return substr($token, $pos + 1);
297
                    }
298
299
                    return array_shift($tokens);
300
                }
301
            }
302
        }
303
304
        return $default;
305
    }
306
307
    /**
308
     * 验证输入
309
     * @throws \RuntimeException
310
     */
311
    public function validate()
312
    {
313
        if (count($this->arguments) < $this->definition->getArgumentRequiredCount()) {
314
            throw new \RuntimeException('Not enough arguments.');
315
        }
316
    }
317
318
    /**
319
     * 检查输入是否是交互的
320
     * @return bool
321
     */
322
    public function isInteractive(): bool
323
    {
324
        return $this->interactive;
325
    }
326
327
    /**
328
     * 设置输入的交互
329
     * @param bool
330
     */
331
    public function setInteractive(bool $interactive): void
332
    {
333
        $this->interactive = $interactive;
334
    }
335
336
    /**
337
     * 获取所有的参数
338
     * @return Argument[]
339
     */
340
    public function getArguments(): array
341
    {
342
        return array_merge($this->definition->getArgumentDefaults(), $this->arguments);
343
    }
344
345
    /**
346
     * 根据名称获取参数
347
     * @param string $name 参数名
348
     * @return mixed
349
     * @throws \InvalidArgumentException
350
     */
351
    public function getArgument(string $name)
352
    {
353
        if (!$this->definition->hasArgument($name)) {
354
            throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
355
        }
356
357
        return $this->arguments[$name] ?? $this->definition->getArgument($name)
358
            ->getDefault();
359
    }
360
361
    /**
362
     * 设置参数的值
363
     * @param string $name  参数名
364
     * @param string $value 值
365
     * @throws \InvalidArgumentException
366
     */
367
    public function setArgument(string $name, $value)
368
    {
369
        if (!$this->definition->hasArgument($name)) {
370
            throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
371
        }
372
373
        $this->arguments[$name] = $value;
374
    }
375
376
    /**
377
     * 检查是否存在某个参数
378
     * @param string|int $name 参数名或位置
379
     * @return bool
380
     */
381
    public function hasArgument($name): bool
382
    {
383
        return $this->definition->hasArgument($name);
384
    }
385
386
    /**
387
     * 获取所有的选项
388
     * @return Option[]
389
     */
390
    public function getOptions(): array
391
    {
392
        return array_merge($this->definition->getOptionDefaults(), $this->options);
393
    }
394
395
    /**
396
     * 获取选项值
397
     * @param string $name 选项名称
398
     * @return mixed
399
     * @throws \InvalidArgumentException
400
     */
401
    public function getOption(string $name)
402
    {
403
        if (!$this->definition->hasOption($name)) {
404
            throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
405
        }
406
407
        return $this->options[$name] ?? $this->definition->getOption($name)->getDefault();
408
    }
409
410
    /**
411
     * 设置选项值
412
     * @param string      $name  选项名
413
     * @param string|bool $value 值
414
     * @throws \InvalidArgumentException
415
     */
416
    public function setOption(string $name, $value): void
417
    {
418
        if (!$this->definition->hasOption($name)) {
419
            throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
420
        }
421
422
        $this->options[$name] = $value;
423
    }
424
425
    /**
426
     * 是否有某个选项
427
     * @param string $name 选项名
428
     * @return bool
429
     */
430
    public function hasOption(string $name): bool
431
    {
432
        return $this->definition->hasOption($name) && isset($this->options[$name]);
433
    }
434
435
    /**
436
     * 转义指令
437
     * @param string $token
438
     * @return string
439
     */
440
    public function escapeToken(string $token): string
441
    {
442
        return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token);
443
    }
444
445
    /**
446
     * 返回传递给命令的参数的字符串
447
     * @return string
448
     */
449
    public function __toString()
450
    {
451
        $tokens = array_map(function ($token) {
452
            if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) {
453
                return $match[1] . $this->escapeToken($match[2]);
454
            }
455
456
            if ($token && '-' !== $token[0]) {
457
                return $this->escapeToken($token);
458
            }
459
460
            return $token;
461
        }, $this->tokens);
462
463
        return implode(' ', $tokens);
464
    }
465
}
466