ConsoleApplication   B
last analyzed

Complexity

Total Complexity 37

Size/Duplication

Total Lines 292
Duplicated Lines 2.74 %

Coupling/Cohesion

Components 2
Dependencies 15

Test Coverage

Coverage 90.1%

Importance

Changes 24
Bugs 4 Features 9
Metric Value
wmc 37
c 24
b 4
f 9
lcom 2
cbo 15
dl 8
loc 292
ccs 91
cts 101
cp 0.901
rs 7.8833

16 Methods

Rating   Name   Duplication   Size   Complexity  
A getConfig() 0 4 1
A getGlobalArgsFormat() 0 4 1
A getCommand() 0 4 1
A getCommands() 0 4 1
A hasCommand() 0 4 1
A hasCommands() 0 4 1
A getNamedCommands() 0 4 1
A hasNamedCommands() 0 4 1
A getDefaultCommands() 0 4 1
A hasDefaultCommands() 0 4 1
A resolveCommand() 8 13 4
A addCommand() 0 20 4
B __construct() 0 55 8
B run() 0 41 6
A exceptionToExitCode() 0 8 2
A validateCommandName() 0 12 3

How to fix   Duplicated Code   

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:

1
<?php
2
3
/*
4
 * This file is part of the webmozart/console package.
5
 *
6
 * (c) Bernhard Schussek <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Webmozart\Console;
13
14
use Exception;
15
use LogicException;
16
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
17
use Webmozart\Assert\Assert;
18
use Webmozart\Console\Api\Application\Application;
19
use Webmozart\Console\Api\Args\Format\ArgsFormat;
20
use Webmozart\Console\Api\Args\RawArgs;
21
use Webmozart\Console\Api\Command\CannotAddCommandException;
22
use Webmozart\Console\Api\Command\Command;
23
use Webmozart\Console\Api\Command\CommandCollection;
24
use Webmozart\Console\Api\Config\ApplicationConfig;
25
use Webmozart\Console\Api\Config\CommandConfig;
26
use Webmozart\Console\Api\Event\ConfigEvent;
27
use Webmozart\Console\Api\Event\ConsoleEvents;
28
use Webmozart\Console\Api\Event\PreResolveEvent;
29
use Webmozart\Console\Api\IO\InputStream;
30
use Webmozart\Console\Api\IO\IO;
31
use Webmozart\Console\Api\IO\OutputStream;
32
use Webmozart\Console\Args\ArgvArgs;
33
use Webmozart\Console\IO\ConsoleIO;
34
use Webmozart\Console\UI\Component\ExceptionTrace;
35
36
/**
37
 * A console application.
38
 *
39
 * @since  1.0
40
 *
41
 * @author Bernhard Schussek <[email protected]>
42
 */
43
class ConsoleApplication implements Application
44
{
45
    /**
46
     * @var ApplicationConfig
47
     */
48
    private $config;
49
50
    /**
51
     * @var CommandCollection
52
     */
53
    private $commands;
54
55
    /**
56
     * @var CommandCollection
57
     */
58
    private $namedCommands;
59
60
    /**
61
     * @var CommandCollection
62
     */
63
    private $defaultCommands;
64
65
    /**
66
     * @var ArgsFormat
67
     */
68
    private $globalArgsFormat;
69
70
    /**
71
     * @var EventDispatcherInterface
72
     */
73
    private $dispatcher;
74
75
    /**
76
     * @var ConsoleIO
77
     */
78
    private $preliminaryIo;
79
80
    /**
81
     * Creates a new console application.
82
     *
83
     * @param ApplicationConfig|callable $config The application configuration
84
     *                                           or a callable that creates the
85
     *                                           configuration.
86
     */
87 138
    public function __construct($config)
88
    {
89 138
        $this->preliminaryIo = new ConsoleIO();
90
91
        // Enable trace output for exceptions thrown during boot
92 138
        $this->preliminaryIo->setVerbosity(IO::VERY_VERBOSE);
93
94 138
        if (is_callable($config)) {
95
            try {
96 1
                $config = $config();
97
            } catch (Exception $e) {
98
                $trace = new ExceptionTrace($e);
99
                $trace->render($this->preliminaryIo);
100
101
                exit($this->exceptionToExitCode($e->getCode()));
0 ignored issues
show
Coding Style Compatibility introduced by
The method __construct() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
102
            }
103
        }
104
105 138
        Assert::isInstanceOf($config, 'Webmozart\Console\Api\Config\ApplicationConfig', 'The $config argument must be an ApplicationConfig or a callable. Got: %s');
106
107
        try {
108 137
            $dispatcher = $config->getEventDispatcher();
109
110 137
            if ($dispatcher && $dispatcher->hasListeners(ConsoleEvents::CONFIG)) {
111 1
                $dispatcher->dispatch(ConsoleEvents::CONFIG,
112 1
                    new ConfigEvent($config));
113
            }
114
115 137
            $this->config = $config;
116 137
            $this->dispatcher = $config->getEventDispatcher();
117 137
            $this->commands = new CommandCollection();
118 137
            $this->namedCommands = new CommandCollection();
119 137
            $this->defaultCommands = new CommandCollection();
120
121 137
            $this->globalArgsFormat = new ArgsFormat(array_merge(
122 137
                $config->getOptions(),
123 137
                $config->getArguments()
124
            ));
125
126 137
            foreach ($config->getCommandConfigs() as $commandConfig) {
127 137
                $this->addCommand($commandConfig);
128
            }
129 2
        } catch (Exception $e) {
130 2
            if (!$config->isExceptionCaught()) {
131 2
                throw $e;
132
            }
133
134
            // Render the trace to the preliminary IO
135
            $trace = new ExceptionTrace($e);
136
            $trace->render($this->preliminaryIo);
137
138
            // Ignore isTerminatedAfterRun() setting. This is a fatal error.
139
            exit($this->exceptionToExitCode($e->getCode()));
0 ignored issues
show
Coding Style Compatibility introduced by
The method __construct() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
140
        }
141 135
    }
142
143
    /**
144
     * {@inheritdoc}
145
     */
146 130
    public function getConfig()
147
    {
148 130
        return $this->config;
149
    }
150
151
    /**
152
     * {@inheritdoc}
153
     */
154 130
    public function getGlobalArgsFormat()
155
    {
156 130
        return $this->globalArgsFormat;
157
    }
158
159
    /**
160
     * {@inheritdoc}
161
     */
162 76
    public function getCommand($name)
163
    {
164 76
        return $this->commands->get($name);
165
    }
166
167
    /**
168
     * {@inheritdoc}
169
     */
170 17
    public function getCommands()
171
    {
172 17
        return clone $this->commands;
173
    }
174
175
    /**
176
     * {@inheritdoc}
177
     */
178 1
    public function hasCommand($name)
179
    {
180 1
        return $this->commands->contains($name);
181
    }
182
183
    /**
184
     * {@inheritdoc}
185
     */
186 2
    public function hasCommands()
187
    {
188 2
        return !$this->commands->isEmpty();
189
    }
190
191
    /**
192
     * {@inheritdoc}
193
     */
194 235
    public function getNamedCommands()
195
    {
196 235
        return $this->namedCommands;
197
    }
198
199
    /**
200
     * {@inheritdoc}
201
     */
202 2
    public function hasNamedCommands()
203
    {
204 2
        return count($this->namedCommands) > 0;
205
    }
206
207
    /**
208
     * {@inheritdoc}
209
     */
210 18
    public function getDefaultCommands()
211
    {
212 18
        return $this->defaultCommands;
213
    }
214
215
    /**
216
     * {@inheritdoc}
217
     */
218 2
    public function hasDefaultCommands()
219
    {
220 2
        return count($this->defaultCommands) > 0;
221
    }
222
223
    /**
224
     * {@inheritdoc}
225
     */
226 43
    public function resolveCommand(RawArgs $args)
227
    {
228 43 View Code Duplication
        if ($this->dispatcher && $this->dispatcher->hasListeners(ConsoleEvents::PRE_RESOLVE)) {
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...
229 32
            $event = new PreResolveEvent($args, $this);
230 32
            $this->dispatcher->dispatch(ConsoleEvents::PRE_RESOLVE, $event);
231
232 32
            if ($resolvedCommand = $event->getResolvedCommand()) {
233 9
                return $resolvedCommand;
234
            }
235
        }
236
237 34
        return $this->config->getCommandResolver()->resolveCommand($args, $this);
238
    }
239
240
    /**
241
     * {@inheritdoc}
242
     */
243 42
    public function run(RawArgs $args = null, InputStream $inputStream = null, OutputStream $outputStream = null, OutputStream $errorStream = null)
244
    {
245
        // Render errors to the preliminary IO until the final IO is created
246 42
        $io = $this->preliminaryIo;
247
248
        try {
249 42
            if (null === $args) {
250
                $args = new ArgvArgs();
251
            }
252
253 42
            $ioFactory = $this->config->getIOFactory();
254
255 42
            if (null === $ioFactory) {
256 1
                throw new LogicException('The IO factory must be set.');
257
            }
258
259
            /** @var IO $io */
260 41
            $io = call_user_func($ioFactory, $this, $args, $inputStream, $outputStream, $errorStream);
261
262 41
            $resolvedCommand = $this->resolveCommand($args);
263 41
            $command = $resolvedCommand->getCommand();
264 41
            $parsedArgs = $resolvedCommand->getArgs();
265
266 41
            $statusCode = $command->handle($parsedArgs, $io);
0 ignored issues
show
Documentation introduced by
$parsedArgs is of type object<Webmozart\Console\Api\Args\RawArgs>, but the function expects a object<Webmozart\Console\Api\Args\Args>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
267 5
        } catch (Exception $e) {
268 5
            if (!$this->config->isExceptionCaught()) {
269 2
                throw $e;
270
            }
271
272 3
            $trace = new ExceptionTrace($e);
273 3
            $trace->render($io);
274
275 3
            $statusCode = $this->exceptionToExitCode($e->getCode());
276
        }
277
278 40
        if ($this->config->isTerminatedAfterRun()) {
279
            exit($statusCode);
0 ignored issues
show
Coding Style Compatibility introduced by
The method run() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
280
        }
281
282 40
        return $statusCode;
283
    }
284
285
    /**
286
     * Converts an exception code to an exit code.
287
     *
288
     * @param int $code The exception code.
289
     *
290
     * @return int The exit code.
291
     */
292 3
    private function exceptionToExitCode($code)
293
    {
294 3
        if (!is_numeric($code)) {
295
            return 1;
296
        }
297
298 3
        return min(max((int) $code, 1), 255);
299
    }
300
301 121
    private function addCommand(CommandConfig $config)
302
    {
303 121
        if (!$config->isEnabled()) {
304 1
            return;
305
        }
306
307 121
        $this->validateCommandName($config);
308
309 120
        $command = new Command($config, $this);
310
311 120
        $this->commands->add($command);
312
313 120
        if ($config->isDefault()) {
314 83
            $this->defaultCommands->add($command);
315
        }
316
317 120
        if (!$config->isAnonymous()) {
318 119
            $this->namedCommands->add($command);
319
        }
320 120
    }
321
322 121
    private function validateCommandName(CommandConfig $config)
323
    {
324 121
        $name = $config->getName();
325
326 121
        if (!$name) {
327 1
            throw CannotAddCommandException::nameEmpty();
328
        }
329
330 120
        if ($this->commands->contains($name)) {
331 1
            throw CannotAddCommandException::nameExists($name);
332
        }
333 120
    }
334
}
335