Completed
Push — master ( dd8846...82fafa )
by Amine
03:04
created

Command   F

Complexity

Total Complexity 68

Size/Duplication

Total Lines 377
Duplicated Lines 19.89 %

Coupling/Cohesion

Components 4
Dependencies 10

Importance

Changes 0
Metric Value
wmc 68
lcom 4
cbo 10
dl 75
loc 377
rs 2.9411
c 0
b 0
f 0

27 Methods

Rating   Name   Duplication   Size   Complexity  
A create() 0 6 2
A __construct() 0 13 1
A name() 8 8 2
A version() 0 8 2
A description() 8 8 2
A descriptions() 0 8 2
A syntax() 0 8 2
A options() 0 12 3
A option() 0 6 2
A args() 0 8 2
A fs() 0 13 4
A console() 11 11 3
A templatesLoader() 11 11 3
A templatesPath() 7 7 2
A template() 0 5 2
A action() 8 8 2
A commands() 11 11 3
A command() 0 10 3
A hasCommand() 0 4 1
A setupSubCommands() 0 5 1
B describe() 0 18 6
D run() 5 35 9
A clear() 0 7 2
A parseArguments() 6 17 4
A handleError() 0 4 1
A init() 0 1 1
A execute() 0 1 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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

1
<?php namespace Tarsana\Command;
2
3
use Tarsana\Command\Commands\HelpCommand;
4
use Tarsana\Command\Commands\VersionCommand;
5
use Tarsana\Command\Console\Console;
6
use Tarsana\Command\Console\ExceptionPrinter;
7
use Tarsana\Command\Interfaces\Console\ConsoleInterface;
8
use Tarsana\Command\Interfaces\Template\TemplateLoaderInterface;
9
use Tarsana\Command\SubCommand;
10
use Tarsana\Command\Template\TemplateLoader;
11
use Tarsana\IO\Filesystem;
12
use Tarsana\IO\FilesystemInterface;
13
use Tarsana\Syntax\Exceptions\ParseException;
14
use Tarsana\Syntax\Factory as S;
15
use Tarsana\Syntax\Text as T;
16
17
class Command {
18
19
    protected $name;
20
    protected $version;
21
    protected $description;
22
23
    protected $syntax;
24
    protected $descriptions;
25
26
    protected $options;
27
    protected $args;
28
29
    protected $action;
30
    protected $commands;
31
32
    protected $console;
33
    protected $fs;
34
    protected $templatesLoader;
35
36
    public static function create(callable $action = null) {
37
        $command = new Command;
38
        if (null !== $action)
39
            $command->action($action);
40
        return $command;
41
    }
42
43
    public function __construct()
44
    {
45
        $this->commands([])
46
             ->setupSubCommands()
47
             ->name('Unknown')
48
             ->version('1.0.0')
49
             ->description('...')
50
             ->descriptions([])
51
             ->options([])
52
             ->console(new Console)
53
             ->fs(new Filesystem('.'))
54
             ->init();
55
    }
56
57
    /**
58
     * name getter and setter.
59
     *
60
     * @param  string
61
     * @return mixed
62
     */
63 View Code Duplication
    public function name(string $value = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
64
    {
65
        if (null === $value) {
66
            return $this->name;
67
        }
68
        $this->name = $value;
69
        return $this;
70
    }
71
72
    /**
73
     * version getter and setter.
74
     *
75
     * @param  string
76
     * @return mixed
77
     */
78
    public function version(string $value = null)
79
    {
80
        if (null === $value) {
81
            return $this->version;
82
        }
83
        $this->version = $value;
84
        return $this;
85
    }
86
87
    /**
88
     * description getter and setter.
89
     *
90
     * @param  string
91
     * @return mixed
92
     */
93 View Code Duplication
    public function description(string $value = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
94
    {
95
        if (null === $value) {
96
            return $this->description;
97
        }
98
        $this->description = $value;
99
        return $this;
100
    }
101
102
    /**
103
     * descriptions getter and setter.
104
     *
105
     * @param  string
106
     * @return mixed
107
     */
108
    public function descriptions(array $value = null)
109
    {
110
        if (null === $value) {
111
            return $this->descriptions;
112
        }
113
        $this->descriptions = $value;
114
        return $this;
115
    }
116
117
    /**
118
     * syntax getter and setter.
119
     *
120
     * @param  string|null $syntax
121
     * @return Syntax|self
122
     */
123
    public function syntax(string $syntax = null)
124
    {
125
        if (null === $syntax)
126
            return $this->syntax;
127
128
        $this->syntax = S::syntax()->parse("{{$syntax}| }");
129
        return $this;
130
    }
131
132
    /**
133
     * options getter and setter.
134
     *
135
     * @param  array
136
     * @return mixed
137
     */
138
    public function options(array $options = null)
139
    {
140
        if (null === $options) {
141
            return $this->options;
142
        }
143
144
        $this->options = [];
145
        foreach($options as $option)
146
            $this->options[$option] = false;
147
148
        return $this;
149
    }
150
151
    /**
152
     * option getter.
153
     *
154
     * @param  string
155
     * @return mixed
156
     */
157
    public function option(string $name)
158
    {
159
        if (!array_key_exists($name, $this->options))
160
            throw new \InvalidArgumentException("Unknown option '{$name}'");
161
        return $this->options[$name];
162
    }
163
164
    /**
165
     * args getter and setter.
166
     *
167
     * @param  stdClass
168
     * @return mixed
169
     */
170
    public function args(\stdClass $value = null)
171
    {
172
        if (null === $value) {
173
            return $this->args;
174
        }
175
        $this->args = $value;
176
        return $this;
177
    }
178
179
    /**
180
     * console getter and setter.
181
     *
182
     * @param  ConsoleInterface
183
     * @return mixed
184
     */
185 View Code Duplication
    public function console(ConsoleInterface $value = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
186
    {
187
        if (null === $value) {
188
            return $this->console;
189
        }
190
        $this->console = $value;
191
        foreach ($this->commands as $name => $command) {
192
            $command->console = $value;
193
        }
194
        return $this;
195
    }
196
197
    /**
198
     * fs getter and setter.
199
     *
200
     * @param  Tarsana\IO\Filesystem|string
201
     * @return mixed
202
     */
203
    public function fs($value = null)
204
    {
205
        if (null === $value) {
206
            return $this->fs;
207
        }
208
        if (is_string($value))
209
            $value = new Filesystem($value);
210
        $this->fs = $value;
211
        foreach ($this->commands as $name => $command) {
212
            $command->fs = $value;
213
        }
214
        return $this;
215
    }
216
217
    /**
218
     * templatesLoader getter and setter.
219
     *
220
     * @param  Tarsana\Command\Interfaces\Template\TemplateLoaderInterface
221
     * @return mixed
222
     */
223 View Code Duplication
    public function templatesLoader(TemplateLoaderInterface $value = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
224
    {
225
        if (null === $value) {
226
            return $this->templatesLoader;
227
        }
228
        $this->templatesLoader = $value;
229
        foreach ($this->commands as $name => $command) {
230
            $command->templatesLoader = $value;
231
        }
232
        return $this;
233
    }
234
235 View Code Duplication
    public function templatesPath(string $path, string $cachePath = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
236
        $this->templatesLoader = new TemplateLoader($path, $cachePath);
237
        foreach ($this->commands as $name => $command) {
238
            $command->templatesLoader = $this->templatesLoader();
239
        }
240
        return $this;
241
    }
242
243
    public function template(string $name) {
244
        if (null === $this->templatesLoader)
245
            throw new \Exception("Please initialize the templates loader before trying to load templates!");
246
        return $this->templatesLoader->load($name);
247
    }
248
249
    /**
250
     * action getter and setter.
251
     *
252
     * @param  callable
253
     * @return mixed
254
     */
255 View Code Duplication
    public function action(callable $value = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
256
    {
257
        if (null === $value) {
258
            return $this->action;
259
        }
260
        $this->action = $value;
261
        return $this;
262
    }
263
264
    /**
265
     * commands getter and setter.
266
     *
267
     * @param  array
268
     * @return mixed
269
     */
270 View Code Duplication
    public function commands(array $value = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
271
    {
272
        if (null === $value) {
273
            return $this->commands;
274
        }
275
        $this->commands = [];
276
        foreach ($value as $name => $command) {
277
            $this->command($name, $command);
278
        }
279
        return $this;
280
    }
281
282
    public function command(string $name, Command $command = null)
283
    {
284
        if (null === $command) {
285
            if (!array_key_exists($name, $this->commands))
286
                throw new \InvalidArgumentException("subcommand '{$name}' not found!");
287
            return $this->commands[$name];
288
        }
289
        $this->commands[$name] = $command;
290
        return $this;
291
    }
292
293
    public function hasCommand(string $name) : bool
294
    {
295
        return array_key_exists($name, $this->commands);
296
    }
297
298
    protected function setupSubCommands()
299
    {
300
        return $this->command('--help', new HelpCommand($this))
301
             ->command('--version', new VersionCommand($this));
302
    }
303
304
    public function describe(string $name, string $description = null)
305
    {
306
        if (null === $description)
307
            return array_key_exists($name, $this->descriptions)
308
                ? $this->descriptions[$name] : '';
309
        if (substr($name, 0, 2) == '--' && array_key_exists($name, $this->options())) {
310
            $this->descriptions[$name] = $description;
311
            return $this;
312
        }
313
        try {
314
            $this->syntax->field($name);
315
            // throws exception if field is missing
316
            $this->descriptions[$name] = $description;
317
            return $this;
318
        } catch (\Exception $e) {
319
            throw new \InvalidArgumentException("Unknown field '{$name}'");
320
        }
321
    }
322
323
    public function run(array $args = null, array $options = [], bool $rawArgs = true)
324
    {
325
        try {
326
            $this->clear();
327
328
            if ($rawArgs) {
329
                if (null === $args) {
330
                    $args = $GLOBALS['argv'];
331
                    array_shift($args);
332
                }
333
334
                if (!empty($args) && array_key_exists($args[0], $this->commands)) {
335
                    $name = $args[0];
336
                    array_shift($args);
337
                    return $this->command($name)->run($args);
338
                }
339
340
                $this->parseArguments($args);
341
            } else {
342
                $this->args = (object) $args;
343 View Code Duplication
                foreach ($options as $name) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
344
                    if (!array_key_exists($name, $this->options))
345
                        throw new \Exception("Unknown option '{$name}'");
346
                    $this->options[$name] = true;
347
                }
348
            }
349
350
            if (null === $this->action)
351
                $this->execute();
352
            else
353
                ($this->action)($this);
354
        } catch (\Exception $e) {
355
            $this->handleError($e);
356
        }
357
    }
358
359
    public function clear()
360
    {
361
        $this->args = null;
362
        foreach($this->options as $name => $value) {
363
            $this->options[$name] = false;
364
        }
365
    }
366
367
    public function parseArguments(array $args)
368
    {
369
        if (null === $this->syntax) {
370
            $this->args = null;
371
            return;
372
        }
373
374
        $arguments = [];
375 View Code Duplication
        foreach ($args as &$arg) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
376
            if (array_key_exists($arg, $this->options))
377
                $this->options[$arg] = true;
378
            else
379
                $arguments[] = $arg;
380
        }
381
        $arguments = T::join($arguments, ' ');
382
        $this->args = $this->syntax->parse($arguments);
383
    }
384
385
    protected function handleError(\Exception $e) {
386
        $output = (new ExceptionPrinter)->print($e);
387
        $this->console()->error($output);
388
    }
389
390
    protected function init() {}
391
    protected function execute() {}
392
393
}
394