Passed
Push — master ( ed42e4...b3b06a )
by Kirill
04:44
created

Console::run()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 19
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 8
c 1
b 0
f 0
dl 0
loc 19
rs 10
cc 3
nc 4
nop 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
    /** @var ConsoleConfig */
35
    private $config;
36
37
    /** @var LocatorInterface|null */
38
    private $locator;
39
40
    /** @var ContainerInterface */
41
    private $container;
42
43
    /** @var Application|null */
44
    private $application = null;
45
46
    /**
47
     * @param ConsoleConfig      $config
48
     * @param ContainerInterface $container
49
     * @param LocatorInterface   $locator
50
     */
51
    public function __construct(
52
        ConsoleConfig $config,
53
        LocatorInterface $locator = null,
54
        ContainerInterface $container = null
55
    ) {
56
        $this->config = $config;
57
        $this->locator = $locator;
58
        $this->container = $container ?? new Container();
59
    }
60
61
    /**
62
     * Run console application.
63
     *
64
     * @param InputInterface|null  $input
65
     * @param OutputInterface|null $output
66
     * @return int
67
     *
68
     * @throws \Throwable
69
     */
70
    public function start(InputInterface $input = null, OutputInterface $output = null): int
71
    {
72
        $input = $input ?? new ArgvInput();
73
        $output = $output ?? new ConsoleOutput();
74
75
        return ContainerScope::runScope($this->container, function () use ($input, $output) {
76
            return $this->run(
77
                $input->getFirstArgument() ?? 'list',
78
                $input,
79
                $output
80
            )->getCode();
81
        });
82
    }
83
84
    /**
85
     * Run selected command.
86
     *
87
     * @param string               $command
88
     * @param InputInterface|array $input
89
     * @param OutputInterface|null $output
90
     * @return CommandOutput
91
     *
92
     * @throws \Throwable
93
     * @throws CommandNotFoundException
94
     */
95
    public function run(
96
        ?string $command,
97
        $input = [],
98
        OutputInterface $output = null
99
    ): CommandOutput {
100
        $input = is_array($input) ? new ArrayInput($input) : $input;
101
        $output = $output ?? new BufferedOutput();
102
103
        $this->configureIO($input, $output);
104
105
        if ($command !== null) {
106
            $input = new InputProxy($input, ['firstArgument' => $command]);
107
        }
108
109
        $code = ContainerScope::runScope($this->container, function () use ($input, $output) {
110
            return $this->getApplication()->doRun($input, $output);
111
        });
112
113
        return new CommandOutput($code ?? self::CODE_NONE, $output);
114
    }
115
116
    /**
117
     * Get associated Symfony Console Application.
118
     *
119
     * @return Application
120
     *
121
     * @throws LocatorException
122
     */
123
    public function getApplication(): Application
124
    {
125
        if ($this->application !== null) {
126
            return $this->application;
127
        }
128
129
        $this->application = new Application($this->config->getName(), $this->config->getVersion());
130
        $this->application->setCatchExceptions(false);
131
        $this->application->setAutoExit(false);
132
133
        if ($this->locator !== null) {
134
            $this->addCommands($this->locator->locateCommands());
135
        }
136
137
        // Register user defined commands
138
        $static = new StaticLocator($this->config->getCommands(), $this->container);
139
        $this->addCommands($static->locateCommands());
140
141
        return $this->application;
142
    }
143
144
    /**
145
     * @param iterable $commands
146
     */
147
    private function addCommands(iterable $commands): void
148
    {
149
        foreach ($commands as $command) {
150
            if ($command instanceof Command) {
151
                $command->setContainer($this->container);
152
            }
153
154
            $this->application->add($command);
0 ignored issues
show
Bug introduced by
The method add() 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

154
            $this->application->/** @scrutinizer ignore-call */ 
155
                                add($command);

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...
155
        }
156
    }
157
158
    /**
159
     * Extracted in order to manage command lifecycle.
160
     *
161
     * @param InputInterface  $input
162
     * @param OutputInterface $output
163
     * @see Application::configureIO()
164
     */
165
    private function configureIO(InputInterface $input, OutputInterface $output): void
166
    {
167
        if (true === $input->hasParameterOption(['--ansi'], true)) {
168
            $output->setDecorated(true);
169
        } elseif (true === $input->hasParameterOption(['--no-ansi'], true)) {
170
            $output->setDecorated(false);
171
        }
172
173
        if (true === $input->hasParameterOption(['--no-interaction', '-n'], true)) {
174
            $input->setInteractive(false);
175
        } elseif (\function_exists('posix_isatty')) {
176
            $inputStream = null;
177
178
            if ($input instanceof StreamableInputInterface) {
179
                $inputStream = $input->getStream();
180
            }
181
182
            if (!@posix_isatty($inputStream) && false === getenv('SHELL_INTERACTIVE')) {
183
                $input->setInteractive(false);
184
            }
185
        }
186
187
        switch ($shellVerbosity = (int)getenv('SHELL_VERBOSITY')) {
188
            case -1:
189
                $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
190
                break;
191
            case 1:
192
                $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
193
                break;
194
            case 2:
195
                $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
196
                break;
197
            case 3:
198
                $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
199
                break;
200
            default:
201
                $shellVerbosity = 0;
202
                break;
203
        }
204
205
        if (
206
            true === $input->hasParameterOption(['--quiet', '-q'], true)
207
        ) {
208
            $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
209
            $shellVerbosity = -1;
210
        } else {
211
            if (
212
                $input->hasParameterOption('-vvv', true)
213
                || $input->hasParameterOption('--verbose=3', true)
214
                || 3 === $input->getParameterOption('--verbose', false, true)
215
            ) {
216
                $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
217
                $shellVerbosity = 3;
218
            } elseif (
219
                $input->hasParameterOption('-vv', true)
220
                || $input->hasParameterOption('--verbose=2', true)
221
                || 2 === $input->getParameterOption('--verbose', false, true)
222
            ) {
223
                $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
224
                $shellVerbosity = 2;
225
            } elseif (
226
                $input->hasParameterOption('-v', true)
227
                || $input->hasParameterOption('--verbose=1', true)
228
                || $input->hasParameterOption('--verbose', true)
229
                || $input->getParameterOption('--verbose', false, true)
230
            ) {
231
                $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
232
                $shellVerbosity = 1;
233
            }
234
        }
235
236
        if (-1 === $shellVerbosity) {
237
            $input->setInteractive(false);
238
        }
239
240
        putenv('SHELL_VERBOSITY=' . $shellVerbosity);
241
        $_ENV['SHELL_VERBOSITY'] = $shellVerbosity;
242
        $_SERVER['SHELL_VERBOSITY'] = $shellVerbosity;
243
    }
244
}
245