Completed
Push — master ( 90ac5a...649bdf )
by Amine
11s
created

Command   F

Complexity

Total Complexity 62

Size/Duplication

Total Lines 348
Duplicated Lines 10.06 %

Coupling/Cohesion

Components 5
Dependencies 10

Importance

Changes 0
Metric Value
wmc 62
lcom 5
cbo 10
dl 35
loc 348
rs 3.8461
c 0
b 0
f 0

25 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 console() 0 11 3
A fs() 0 13 4
A templatesLoader() 0 8 2
A templatesPath() 0 4 1
A template() 0 5 2
A action() 8 8 2
A command() 0 10 3
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
        $this->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
    public function console(ConsoleInterface $value = null)
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
    public function templatesLoader(TemplateLoaderInterface $value = null)
224
    {
225
        if (null === $value) {
226
            return $this->templatesLoader;
227
        }
228
        $this->templatesLoader = $value;
229
        return $this;
230
    }
231
232
    public function templatesPath(string $path, string $cachePath = null) {
233
        $this->templatesLoader = new TemplateLoader($path, $cachePath);
234
        return $this;
235
    }
236
237
    public function template(string $name) {
238
        if (null == $this->templatesLoader)
239
            throw new \Exception("Please initialize the templates loader before trying to load templates!");
240
        return $this->templatesLoader->load($name);
241
    }
242
243
    /**
244
     * action getter and setter.
245
     *
246
     * @param  callable
247
     * @return mixed
248
     */
249 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...
250
    {
251
        if (null === $value) {
252
            return $this->action;
253
        }
254
        $this->action = $value;
255
        return $this;
256
    }
257
258
    public function command(string $name, Command $command = null)
259
    {
260
        if (null === $command) {
261
            if (!array_key_exists($name, $this->commands))
262
                throw new \InvalidArgumentException("subcommand '{$name}' not found!");
263
            return $this->commands[$name];
264
        }
265
        $this->commands[$name] = $command;
266
        return $this;
267
    }
268
269
    protected function setupSubCommands()
270
    {
271
        return $this->command('--help', new HelpCommand($this))
272
             ->command('--version', new VersionCommand($this));
273
    }
274
275
    public function describe(string $name, string $description = null)
276
    {
277
        if (null === $description)
278
            return array_key_exists($name, $this->descriptions)
279
                ? $this->descriptions[$name] : '';
280
        if (substr($name, 0, 2) == '--' && array_key_exists($name, $this->options())) {
281
            $this->descriptions[$name] = $description;
282
            return $this;
283
        }
284
        try {
285
            $this->syntax->field($name);
286
            // throws exception if field is missing
287
            $this->descriptions[$name] = $description;
288
            return $this;
289
        } catch (\Exception $e) {
290
            throw new \InvalidArgumentException("Unknown field '{$name}'");
291
        }
292
    }
293
294
    public function run(array $args = null, array $options = [], bool $rawArgs = true)
0 ignored issues
show
Coding Style introduced by
run uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
295
    {
296
        try {
297
            $this->clear();
298
299
            if ($rawArgs) {
300
                if (null === $args) {
301
                    $args = $GLOBALS['argv'];
302
                    array_shift($args);
303
                }
304
305
                if (!empty($args) && array_key_exists($args[0], $this->commands)) {
306
                    $name = $args[0];
307
                    array_shift($args);
308
                    return $this->command($name)->run($args);
309
                }
310
311
                $this->parseArguments($args);
312
            } else {
313
                $this->args = (object) $args;
314 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...
315
                    if (!array_key_exists($name, $this->options))
316
                        throw new \Exception("Unknown option '{$name}'");
317
                    $this->options[$name] = true;
318
                }
319
            }
320
321
            if (null === $this->action)
322
                $this->execute();
323
            else
324
                ($this->action)($this);
325
        } catch (\Exception $e) {
326
            $this->handleError($e);
327
        }
328
    }
329
330
    public function clear()
331
    {
332
        $this->args = null;
333
        foreach($this->options as $name => $value) {
334
            $this->options[$name] = false;
335
        }
336
    }
337
338
    public function parseArguments(array $args)
339
    {
340
        if (null === $this->syntax) {
341
            $this->args = null;
342
            return;
343
        }
344
345
        $arguments = [];
346 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...
347
            if (array_key_exists($arg, $this->options))
348
                $this->options[$arg] = true;
349
            else
350
                $arguments[] = $arg;
351
        }
352
        $arguments = T::join($arguments, ' ');
353
        $this->args = $this->syntax->parse($arguments);
354
    }
355
356
    protected function handleError(\Exception $e) {
357
        $output = (new ExceptionPrinter)->print($e);
358
        $this->console()->error($output);
359
    }
360
361
    protected function init() {}
362
    protected function execute() {}
363
364
}
365