BaseCommand   C
last analyzed

Complexity

Total Complexity 54

Size/Duplication

Total Lines 445
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 97.04%

Importance

Changes 0
Metric Value
wmc 54
lcom 1
cbo 2
dl 0
loc 445
ccs 131
cts 135
cp 0.9704
rs 6.4799
c 0
b 0
f 0

24 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 15 4
A clearAll() 0 10 1
A getInstance() 0 4 1
A addCommandName() 0 4 1
A getCommandName() 0 4 1
A addConfigs() 0 6 2
A addGlobalConfigs() 0 8 3
A addGlobalOptions() 0 8 3
A getConfigs() 0 4 1
A addCommandArgument() 0 4 1
A addGlobalCommandArgument() 0 6 2
A getCommandArguments() 0 4 2
A addCommandSubject() 0 4 1
A addCommandSubject2() 0 4 1
A addPath() 0 4 1
B normalizeOptions() 0 24 7
A getCommand() 0 18 2
A getCLICommandArguments() 0 10 2
A getCLICommandName() 0 4 1
A getCLIConfigs() 0 17 3
A getCLIGlobalOptions() 0 11 3
A getCLIPath() 0 9 2
B getCLISubjects() 0 26 7
A getBinaryVersion() 0 8 2

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
/**
4
 * GitElephant - An abstraction layer for git written in PHP
5
 * Copyright (C) 2013  Matteo Giachino
6
 *
7
 * This program is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU General Public License as published by
9
 * the Free Software Foundation, either version 3 of the License, or
10
 * (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License
18
 * along with this program.  If not, see [http://www.gnu.org/licenses/].
19
 */
20
21
namespace GitElephant\Command;
22
23
use GitElephant\Repository;
24
use PhpCollection\Map;
25
26
/**
27
 * BaseCommand
28
 *
29
 * The base class for all the command generators
30
 *
31
 * @author Matteo Giachino <[email protected]>
32
 */
33
class BaseCommand
34
{
35
    /**
36
     * the command name
37
     *
38
     * @var string|null
39
     */
40
    private $commandName = null;
41
42
    /**
43
     * config options
44
     *
45
     * @var array
46
     */
47
    private $configs = [];
48
49
    /**
50
     * global configs
51
     *
52
     * @var array
53
     */
54
    private $globalConfigs = [];
55
56
    /**
57
     * global options
58
     *
59
     * @var array
60
     */
61
    private $globalOptions = [];
62
63
    /**
64
     * the command arguments
65
     *
66
     * @var array
67
     */
68
    private $commandArguments = [];
69
70
    /**
71
     * the global command arguments
72
     *
73
     * @var array
74
     */
75
    private $globalCommandArguments = [];
76
77
    /**
78
     * the command subject
79
     *
80
     * @var string|SubCommandCommand|null
81
     */
82
    private $commandSubject = null;
83
84
    /**
85
     * the command second subject (i.e. for branch)
86
     *
87
     * @var string|SubCommandCommand|null
88
     */
89
    private $commandSubject2 = null;
90
91
    /**
92
     * the path
93
     *
94
     * @var string|null
95
     */
96
    private $path = null;
97
98
    /**
99
     * @var string|null
100
     */
101
    private $binaryVersion;
102
103
    /**
104
     * @var Repository|null
105
     */
106
    private $repo;
107
108
    /**
109
     * constructor
110
     *
111
     * should be called by all child classes' constructors to permit use of
112
     * global configs, options and command arguments
113
     *
114
     * @param null|\GitElephant\Repository $repo The repo object to read
115
     */
116 130
    public function __construct(Repository $repo = null)
117
    {
118 130
        if (!is_null($repo)) {
119 100
            $this->addGlobalConfigs($repo->getGlobalConfigs());
120 100
            $this->addGlobalOptions($repo->getGlobalOptions());
121
122 100
            $arguments = $repo->getGlobalCommandArguments();
123 100
            if (!empty($arguments)) {
124 1
                foreach ($arguments as $argument) {
125 1
                    $this->addGlobalCommandArgument($argument);
126
                }
127
            }
128 100
            $this->repo = $repo;
129
        }
130 130
    }
131
132
    /**
133
     * Clear all previous variables
134
     */
135 116
    public function clearAll(): void
136
    {
137 116
        $this->commandName = null;
138 116
        $this->configs = [];
139 116
        $this->commandArguments = [];
140 116
        $this->commandSubject = null;
141 116
        $this->commandSubject2 = null;
142 116
        $this->path = null;
143 116
        $this->binaryVersion = null;
144 116
    }
145
146
    /**
147
     * Get a new instance of this command
148
     *
149
     * @param Repository $repo
150
     * @return static
151
     */
152 121
    public static function getInstance(Repository $repo = null)
153
    {
154 121
        return new static($repo);
155
    }
156
157
    /**
158
     * Add the command name
159
     *
160
     * @param string $commandName the command name
161
     */
162 117
    protected function addCommandName(string $commandName): void
163
    {
164 117
        $this->commandName = $commandName;
165 117
    }
166
167
    /**
168
     * Get command name
169
     *
170
     * @return string
171
     */
172 11
    protected function getCommandName(): string
173
    {
174 11
        return $this->commandName;
175
    }
176
177
    /**
178
     * Set Configs
179
     *
180
     * @param array|Map $configs the config variable. i.e. { "color.status" => "false", "color.diff" => "true" }
181
     */
182 3
    public function addConfigs($configs): void
183
    {
184 3
        foreach ($configs as $config => $value) {
185 3
            $this->configs[$config] = $value;
186
        }
187 3
    }
188
189
    /**
190
     * Set global configs
191
     *
192
     * @param array|Map $configs the config variable. i.e. { "color.status" => "false", "color.diff" => "true" }
193
     */
194 100
    protected function addGlobalConfigs($configs): void
195
    {
196 100
        if (!empty($configs)) {
197 1
            foreach ($configs as $config => $value) {
198 1
                $this->globalConfigs[$config] = $value;
199
            }
200
        }
201 100
    }
202
203
    /**
204
     * Set global option
205
     *
206
     * @param array|Map $options a global option
207
     */
208 100
    protected function addGlobalOptions($options): void
209
    {
210 100
        if (!empty($options)) {
211 1
            foreach ($options as $name => $value) {
212 1
                $this->globalOptions[$name] = $value;
213
            }
214
        }
215 100
    }
216
217
    /**
218
     * Get Configs
219
     *
220
     * @return array
221
     */
222
    public function getConfigs(): array
223
    {
224
        return $this->configs;
225
    }
226
227
    /**
228
     * Add a command argument
229
     *
230
     * @param string $commandArgument the command argument
231
     */
232 108
    protected function addCommandArgument($commandArgument): void
233
    {
234 108
        $this->commandArguments[] = $commandArgument;
235 108
    }
236
237
    /**
238
     * Add a global command argument
239
     *
240
     * @param string $commandArgument the command argument
241
     */
242 1
    protected function addGlobalCommandArgument($commandArgument): void
243
    {
244 1
        if (!empty($commandArgument)) {
245 1
            $this->globalCommandArguments[] = $commandArgument;
246
        }
247 1
    }
248
249
    /**
250
     * Get all added command arguments
251
     *
252
     * @return array
253
     */
254 11
    protected function getCommandArguments(): array
255
    {
256 11
        return $this->commandArguments !== [] ? $this->commandArguments : [];
257
    }
258
259
    /**
260
     * Add a command subject
261
     *
262
     * @param SubCommandCommand|array|string $commandSubject the command subject
263
     */
264 106
    protected function addCommandSubject($commandSubject): void
265
    {
266 106
        $this->commandSubject = $commandSubject;
0 ignored issues
show
Documentation Bug introduced by
It seems like $commandSubject can also be of type array. However, the property $commandSubject is declared as type string|object<GitElephan...SubCommandCommand>|null. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
267 106
    }
268
269
    /**
270
     * Add a second command subject
271
     *
272
     * @param SubCommandCommand|array|string $commandSubject2 the second command subject
273
     */
274 22
    protected function addCommandSubject2($commandSubject2): void
275
    {
276 22
        $this->commandSubject2 = $commandSubject2;
0 ignored issues
show
Documentation Bug introduced by
It seems like $commandSubject2 can also be of type array. However, the property $commandSubject2 is declared as type string|object<GitElephan...SubCommandCommand>|null. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
277 22
    }
278
279
    /**
280
     * Add a path to the git command
281
     *
282
     * @param string $path path
283
     */
284 19
    protected function addPath($path): void
285
    {
286 19
        $this->path = $path;
287 19
    }
288
289
    /**
290
     * Normalize any valid option to its long name
291
     * an provide a structure that can be more intelligently
292
     * handled by other routines
293
     *
294
     * @param array $options       command options
295
     * @param array $switchOptions list of valid options that are switch like
296
     * @param array $valueOptions  list of valid options that must have a value assignment
297
     *
298
     * @return array Associative array of valid, normalized command options
299
     */
300 15
    public function normalizeOptions(
301
        array $options = [],
302
        array $switchOptions = [],
303
        $valueOptions = []
304
    ): array {
305 15
        $normalizedOptions = [];
306
307 15
        foreach ($options as $option) {
308 7
            if (array_key_exists($option, $switchOptions)) {
309 7
                $normalizedOptions[$switchOptions[$option]] = $switchOptions[$option];
310
            } else {
311 1
                $parts = preg_split('/([\s=])+/', $option, 2, PREG_SPLIT_DELIM_CAPTURE);
312 1
                if (!empty($parts) && is_array($parts)) {
313 1
                    $optionName = $parts[0];
314 1
                    if (in_array($optionName, $valueOptions)) {
315 1
                        $value = $parts[1] === '=' ? $option : [$parts[0], $parts[2]];
316 1
                        $normalizedOptions[$optionName] = $value;
317
                    }
318
                }
319
            }
320
        }
321
322 15
        return $normalizedOptions;
323
    }
324
325
    /**
326
     * Get the current command
327
     *
328
     * @return string
329
     * @throws \RuntimeException
330
     */
331 118
    public function getCommand(): string
332
    {
333 118
        if (is_null($this->commandName)) {
334 1
            throw new \RuntimeException("You should pass a commandName to execute a command");
335
        }
336
337 117
        $command = '';
338 117
        $command .= $this->getCLIConfigs();
339 117
        $command .= $this->getCLIGlobalOptions();
340 117
        $command .= $this->getCLICommandName();
341 117
        $command .= $this->getCLICommandArguments();
342 117
        $command .= $this->getCLISubjects();
343 117
        $command .= $this->getCLIPath();
344
345 117
        $command = preg_replace('/\\s{2,}/', ' ', $command);
346
347 117
        return trim($command);
348
    }
349
350
    /**
351
     * get a string of CLI-formatted command arguments
352
     *
353
     * @return string The command argument string
354
     */
355 118
    private function getCLICommandArguments(): string
356
    {
357 118
        $command = '';
358 118
        $combinedArguments = array_merge($this->globalCommandArguments, $this->commandArguments);
359 118
        if (count($combinedArguments) > 0) {
360 108
            $command .= ' ' . implode(' ', array_map('escapeshellarg', $combinedArguments));
361
        }
362
363 118
        return $command;
364
    }
365
366
    /**
367
     * get a string of CLI-formatted command name
368
     *
369
     * @return string The command name string
370
     */
371 118
    private function getCLICommandName(): string
372
    {
373 118
        return ' ' . $this->commandName;
374
    }
375
376
    /**
377
     * get a string of CLI-formatted configs
378
     *
379
     * @return string The config string
380
     */
381 118
    private function getCLIConfigs(): string
382
    {
383 118
        $command = '';
384 118
        $combinedConfigs = array_merge($this->globalConfigs, $this->configs);
385 118
        if (count($combinedConfigs) > 0) {
386 4
            foreach ($combinedConfigs as $config => $value) {
387 4
                $command .= sprintf(
388 4
                    ' %s %s=%s',
389 4
                    escapeshellarg('-c'),
390 4
                    escapeshellarg($config),
391 4
                    escapeshellarg($value)
392
                );
393
            }
394
        }
395
396 118
        return $command;
397
    }
398
399
    /**
400
     * get a string of CLI-formatted global options
401
     *
402
     * @return string The global options string
403
     */
404 118
    private function getCLIGlobalOptions(): string
405
    {
406 118
        $command = '';
407 118
        if (count($this->globalOptions) > 0) {
408 1
            foreach ($this->globalOptions as $name => $value) {
409 1
                $command .= sprintf(' %s=%s', escapeshellarg($name), escapeshellarg($value));
410
            }
411
        }
412
413 118
        return $command;
414
    }
415
416
    /**
417
     * get a string of CLI-formatted path
418
     *
419
     * @return string The path string
420
     */
421 118
    private function getCLIPath(): string
422
    {
423 118
        $command = '';
424 118
        if (!is_null($this->path)) {
425 17
            $command .= sprintf(' -- %s', escapeshellarg($this->path));
426
        }
427
428 118
        return $command;
429
    }
430
431
    /**
432
     * get a string of CLI-formatted subjects
433
     *
434
     * @throws \RuntimeException
435
     * @return string The subjects string
436
     */
437 118
    private function getCLISubjects(): string
438
    {
439 118
        $command = '';
440 118
        if (!is_null($this->commandSubject)) {
441 107
            $command .= ' ';
442 107
            if ($this->commandSubject instanceof SubCommandCommand) {
443 10
                $command .= $this->commandSubject->getCommand();
444 106
            } elseif (is_array($this->commandSubject)) {
445 1
                $command .= implode(' ', array_map('escapeshellarg', $this->commandSubject));
446
            } else {
447 105
                $command .= escapeshellarg($this->commandSubject);
448
            }
449
        }
450 118
        if (!is_null($this->commandSubject2)) {
451 23
            $command .= ' ';
452 23
            if ($this->commandSubject2 instanceof SubCommandCommand) {
453
                $command .= $this->commandSubject2->getCommand();
454 23
            } elseif (is_array($this->commandSubject2)) {
455
                $command .= implode(' ', array_map('escapeshellarg', $this->commandSubject2));
456
            } else {
457 23
                $command .= escapeshellarg($this->commandSubject2);
458
            }
459
        }
460
461 118
        return $command;
462
    }
463
464
    /**
465
     * Get the version of the git binary
466
     *
467
     * @return string|null
468
     */
469 4
    public function getBinaryVersion(): ?string
470
    {
471 4
        if (is_null($this->binaryVersion)) {
472 4
            $this->binaryVersion = $this->repo->getCaller()->getBinaryVersion();
473
        }
474
475 4
        return $this->binaryVersion;
476
    }
477
}
478