Passed
Push — master ( 083961...8c32c7 )
by butschster
08:05 queued 12s
created

Console   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 193
Duplicated Lines 0 %

Test Coverage

Coverage 84.71%

Importance

Changes 0
Metric Value
eloc 94
c 0
b 0
f 0
dl 0
loc 193
ccs 72
cts 85
cp 0.8471
rs 9.6
wmc 35

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
A start() 0 10 1
A run() 0 17 3
F configureIO() 0 78 24
A getApplication() 0 19 3
A addCommands() 0 8 3
1
<?php
2
3
/**
4
 * Spiral Framework.
5
 *
6
 * @license   MIT
7
 * @author    Anton Titov (Wolfy-J)
8
 */
9
10
declare(strict_types=1);
11
12
namespace Spiral\Console;
13
14
use Psr\Container\ContainerInterface;
15
use Spiral\Console\Config\ConsoleConfig;
16
use Spiral\Console\Exception\LocatorException;
17
use Spiral\Core\Container;
18
use Spiral\Core\ContainerScope;
19
use Symfony\Component\Console\Application;
20
use Symfony\Component\Console\Exception\CommandNotFoundException;
21
use Symfony\Component\Console\Input\ArgvInput;
22
use Symfony\Component\Console\Input\ArrayInput;
23
use Symfony\Component\Console\Input\InputInterface;
24
use Symfony\Component\Console\Input\StreamableInputInterface;
25
use Symfony\Component\Console\Output\BufferedOutput;
26
use Symfony\Component\Console\Output\ConsoleOutput;
27
use Symfony\Component\Console\Output\OutputInterface;
28
29
final class Console
30
{
31
    // Undefined response code for command (errors). See below.
32
    public const CODE_NONE = 102;
33
34
    private ConsoleConfig $config;
35
36
    private ?LocatorInterface $locator;
37
38
    private ContainerInterface $container;
39
40
    private ?Application $application = null;
41
42 133
    public function __construct(
43
        ConsoleConfig $config,
44
        LocatorInterface $locator = null,
45
        ContainerInterface $container = null
46
    ) {
47 133
        $this->config = $config;
48 133
        $this->locator = $locator;
49 133
        $this->container = $container ?? new Container();
50
    }
51
52
    /**
53
     * Run console application.
54
     *
55
     * @param InputInterface|null  $input
56
     * @param OutputInterface|null $output
57
     *
58
     * @throws \Throwable
59
     */
60 6
    public function start(InputInterface $input = null, OutputInterface $output = null): int
61
    {
62 6
        $input ??= new ArgvInput();
63 6
        $output ??= new ConsoleOutput();
64
65 6
        return ContainerScope::runScope($this->container, fn () => $this->run(
66 6
            $input->getFirstArgument() ?? 'list',
67
            $input,
68
            $output
69 6
        )->getCode());
70
    }
71
72
    /**
73
     * Run selected command.
74
     *
75
     * @param string               $command
76
     * @param InputInterface|array $input
77
     * @param OutputInterface|null $output
78
     *
79
     * @throws \Throwable
80
     * @throws CommandNotFoundException
81
     */
82 133
    public function run(
83
        ?string $command,
84
        $input = [],
85
        OutputInterface $output = null
86
    ): CommandOutput {
87 133
        $input = is_array($input) ? new ArrayInput($input) : $input;
88 133
        $output ??= new BufferedOutput();
89
90 133
        $this->configureIO($input, $output);
91
92 133
        if ($command !== null) {
93 132
            $input = new InputProxy($input, ['firstArgument' => $command]);
94
        }
95
96 133
        $code = ContainerScope::runScope($this->container, fn () => $this->getApplication()->doRun($input, $output));
97
98 124
        return new CommandOutput($code ?? self::CODE_NONE, $output);
99
    }
100
101
    /**
102
     * Get associated Symfony Console Application.
103
     *
104
     *
105
     * @throws LocatorException
106
     */
107 133
    public function getApplication(): Application
108
    {
109 133
        if ($this->application !== null) {
110 36
            return $this->application;
111
        }
112
113 133
        $this->application = new Application($this->config->getName(), $this->config->getVersion());
114 133
        $this->application->setCatchExceptions(false);
0 ignored issues
show
Bug introduced by
The method setCatchExceptions() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

114
        $this->application->/** @scrutinizer ignore-call */ 
115
                            setCatchExceptions(false);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
115 133
        $this->application->setAutoExit(false);
116
117 133
        if ($this->locator !== null) {
118 90
            $this->addCommands($this->locator->locateCommands());
119
        }
120
121
        // Register user defined commands
122 133
        $static = new StaticLocator($this->config->getCommands(), $this->container);
123 133
        $this->addCommands($static->locateCommands());
124
125 133
        return $this->application;
126
    }
127
128 133
    private function addCommands(iterable $commands): void
129
    {
130 133
        foreach ($commands as $command) {
131 133
            if ($command instanceof Command) {
132 125
                $command->setContainer($this->container);
133
            }
134
135 133
            $this->application->add($command);
136
        }
137
    }
138
139
    /**
140
     * Extracted in order to manage command lifecycle.
141
     *
142
     * @see Application::configureIO()
143
     */
144 133
    private function configureIO(InputInterface $input, OutputInterface $output): void
145
    {
146 133
        if (true === $input->hasParameterOption(['--ansi'], true)) {
147
            $output->setDecorated(true);
148 133
        } elseif (true === $input->hasParameterOption(['--no-ansi'], true)) {
149
            $output->setDecorated(false);
150
        }
151
152 133
        if (true === $input->hasParameterOption(['--no-interaction', '-n'], true)) {
153
            $input->setInteractive(false);
154 133
        } elseif (\function_exists('posix_isatty')) {
155 133
            $inputStream = null;
156
157 133
            if ($input instanceof StreamableInputInterface) {
158 133
                $inputStream = $input->getStream();
159
            }
160
161 133
            if (!@posix_isatty($inputStream) && false === getenv('SHELL_INTERACTIVE')) {
162
                $input->setInteractive(false);
163
            }
164
        }
165
166 133
        switch ($shellVerbosity = (int)getenv('SHELL_VERBOSITY')) {
167
            case -1:
168
                $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
169
                break;
170 133
            case 1:
171 63
                $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
172 63
                break;
173 71
            case 2:
174
                $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
175
                break;
176 71
            case 3:
177 64
                $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
178 64
                break;
179
            default:
180 7
                $shellVerbosity = 0;
181 7
                break;
182
        }
183
184
        if (
185 133
            true === $input->hasParameterOption(['--quiet', '-q'], true)
186
        ) {
187
            $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
188
            $shellVerbosity = -1;
189
        } else {
190
            if (
191 133
                $input->hasParameterOption('-vvv', true)
192 133
                || $input->hasParameterOption('--verbose=3', true)
193 133
                || 3 === $input->getParameterOption('--verbose', false, true)
194
            ) {
195 4
                $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
196 4
                $shellVerbosity = 3;
197
            } elseif (
198 133
                $input->hasParameterOption('-vv', true)
199 133
                || $input->hasParameterOption('--verbose=2', true)
200 133
                || 2 === $input->getParameterOption('--verbose', false, true)
201
            ) {
202
                $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
203
                $shellVerbosity = 2;
204
            } elseif (
205 133
                $input->hasParameterOption('-v', true)
206 133
                || $input->hasParameterOption('--verbose=1', true)
207 133
                || $input->hasParameterOption('--verbose', true)
208 133
                || $input->getParameterOption('--verbose', false, true)
209
            ) {
210 1
                $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
211 1
                $shellVerbosity = 1;
212
            }
213
        }
214
215 133
        if (-1 === $shellVerbosity) {
216
            $input->setInteractive(false);
217
        }
218
219 133
        putenv('SHELL_VERBOSITY=' . $shellVerbosity);
220 133
        $_ENV['SHELL_VERBOSITY'] = $shellVerbosity;
221 133
        $_SERVER['SHELL_VERBOSITY'] = $shellVerbosity;
222
    }
223
}
224