Passed
Pull Request — master (#1115)
by Maxim
22:29
created

Console   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 189
Duplicated Lines 0 %

Test Coverage

Coverage 90.32%

Importance

Changes 0
Metric Value
wmc 35
eloc 89
dl 0
loc 189
ccs 84
cts 93
cp 0.9032
rs 9.6
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A start() 0 7 1
A run() 0 27 3
A getApplication() 0 27 4
A addCommands() 0 15 5
F configureIO() 0 66 21
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Spiral\Console;
6
7
use Psr\Container\ContainerInterface;
8
use Psr\EventDispatcher\EventDispatcherInterface;
9
use Spiral\Console\Config\ConsoleConfig;
10
use Spiral\Console\Exception\LocatorException;
11
use Spiral\Core\Attribute\Proxy;
12
use Spiral\Core\Container;
13
use Spiral\Core\Scope;
14
use Spiral\Core\ScopeInterface;
15
use Spiral\Events\EventDispatcherAwareInterface;
16
use Symfony\Component\Console\Application;
17
use Symfony\Component\Console\Exception\CommandNotFoundException;
18
use Symfony\Component\Console\Input\ArgvInput;
19
use Symfony\Component\Console\Input\ArrayInput;
20
use Symfony\Component\Console\Input\InputInterface;
21
use Symfony\Component\Console\Input\StreamableInputInterface;
22
use Symfony\Component\Console\Output\BufferedOutput;
23
use Symfony\Component\Console\Output\ConsoleOutput;
24
use Symfony\Component\Console\Output\OutputInterface;
25
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface as SymfonyEventDispatcherInterface;
26
27
#[\Spiral\Core\Attribute\Scope('console')]
28
final class Console
29
{
30
    // Undefined response code for command (errors). See below.
31
    public const CODE_NONE = 102;
32
33
    private ?Application $application = null;
34
35 107
    public function __construct(
36
        private readonly ConsoleConfig $config,
37
        private readonly ?LocatorInterface $locator = null,
38
        #[Proxy] private readonly ContainerInterface $container = new Container(),
39
        private readonly ScopeInterface $scope = new Container(),
40
        private readonly ?EventDispatcherInterface $dispatcher = null,
41
    ) {
42 107
    }
43
44
    /**
45
     * Run console application.
46
     *
47
     * @throws \Throwable
48
     */
49 5
    public function start(InputInterface $input = new ArgvInput(), OutputInterface $output = new ConsoleOutput()): int
50
    {
51 5
        return $this->run(
52 5
            $input->getFirstArgument() ?? 'list',
53 5
            $input,
54 5
            $output,
55 5
        )->getCode();
56
    }
57
58
    /**
59
     * Run selected command.
60
     *
61
     * @throws \Throwable
62
     * @throws CommandNotFoundException
63
     */
64 106
    public function run(
65
        ?string $command,
66
        array|InputInterface $input = [],
67
        OutputInterface $output = new BufferedOutput(),
68
    ): CommandOutput {
69 106
        $input = \is_array($input) ? new ArrayInput($input) : $input;
0 ignored issues
show
introduced by
The condition is_array($input) is always true.
Loading history...
70
71 106
        $this->configureIO($input, $output);
72
73 106
        if ($command !== null) {
74 105
            $input = new InputProxy($input, ['firstArgument' => $command]);
75
        }
76
77
        /**
78
         * @psalm-suppress InvalidArgument
79
         */
80 106
        $code = $this->scope->runScope(
81 106
            new Scope(
0 ignored issues
show
Bug introduced by
new Spiral\Core\Scope(ar...ace::class => $output)) of type Spiral\Core\Scope is incompatible with the type array expected by parameter $bindings of Spiral\Core\ScopeInterface::runScope(). ( Ignorable by Annotation )

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

81
            /** @scrutinizer ignore-type */ new Scope(
Loading history...
82 106
                bindings: [
83 106
                    InputInterface::class => $input,
84 106
                    OutputInterface::class => $output,
85 106
                ],
86 106
            ),
87 106
            fn () => $this->getApplication()->doRun($input, $output),
88 106
        );
89
90 103
        return new CommandOutput($code ?? self::CODE_NONE, $output);
91
    }
92
93
    /**
94
     * Get associated Symfony Console Application.
95
     *
96
     * @throws LocatorException
97
     */
98 106
    public function getApplication(): Application
99
    {
100 106
        if ($this->application !== null) {
101 30
            return $this->application;
102
        }
103
104 106
        $this->application = new Application($this->config->getName(), $this->config->getVersion());
105 106
        $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

105
        $this->application->/** @scrutinizer ignore-call */ 
106
                            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...
106 106
        $this->application->setAutoExit(false);
107 106
        if ($this->dispatcher instanceof SymfonyEventDispatcherInterface) {
108
            $this->application->setDispatcher($this->dispatcher);
109
        }
110
111 106
        if ($this->locator !== null) {
112 26
            $this->addCommands($this->locator->locateCommands());
113
        }
114
115
        // Register user defined commands
116 106
        $static = new StaticLocator(
117 106
            $this->config->getCommands(),
118 106
            $this->config->getInterceptors(),
119 106
            $this->container,
120 106
        );
121
122 106
        $this->addCommands($static->locateCommands());
123
124 106
        return $this->application;
125
    }
126
127 106
    private function addCommands(iterable $commands): void
128
    {
129 106
        $interceptors = $this->config->getInterceptors();
130
131 106
        foreach ($commands as $command) {
132 106
            if ($command instanceof Command) {
133 83
                $command->setContainer($this->container);
134 83
                $command->setInterceptors($interceptors);
135
            }
136
137 106
            if ($this->dispatcher !== null && $command instanceof EventDispatcherAwareInterface) {
138 1
                $command->setEventDispatcher($this->dispatcher);
139
            }
140
141 106
            $this->application->add($command);
142
        }
143
    }
144
145
    /**
146
     * Extracted in order to manage command lifecycle.
147
     *
148
     * @see Application::configureIO()
149
     */
150 106
    private function configureIO(InputInterface $input, OutputInterface $output): void
151
    {
152 106
        if ($input->hasParameterOption(['--ansi'], true)) {
153
            $output->setDecorated(true);
154 106
        } elseif ($input->hasParameterOption(['--no-ansi'], true)) {
155
            $output->setDecorated(false);
156
        }
157
158 106
        if ($input->hasParameterOption(['--no-interaction', '-n'], true)) {
159
            $input->setInteractive(false);
160 106
        } elseif (\function_exists('posix_isatty')) {
161 106
            $inputStream = null;
162
163 106
            if ($input instanceof StreamableInputInterface) {
164 106
                $inputStream = $input->getStream();
165
            }
166
167 106
            if ($inputStream !== null && !@posix_isatty($inputStream) && false === getenv('SHELL_INTERACTIVE')) {
168
                $input->setInteractive(false);
169
            }
170
        }
171
172 106
        match ($shellVerbosity = (int)getenv('SHELL_VERBOSITY')) {
0 ignored issues
show
Unused Code introduced by
The assignment to $shellVerbosity is dead and can be removed.
Loading history...
173
            -1 => $output->setVerbosity(OutputInterface::VERBOSITY_QUIET),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $output->setVerbosity(Sy...rface::VERBOSITY_QUIET) targeting Symfony\Component\Consol...terface::setVerbosity() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
174 81
            1 => $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $output->setVerbosity(Sy...ace::VERBOSITY_VERBOSE) targeting Symfony\Component\Consol...terface::setVerbosity() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
175 1
            2 => $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $output->setVerbosity(Sy...VERBOSITY_VERY_VERBOSE) targeting Symfony\Component\Consol...terface::setVerbosity() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
176 12
            3 => $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $output->setVerbosity(Sy...rface::VERBOSITY_DEBUG) targeting Symfony\Component\Consol...terface::setVerbosity() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
177 12
            default => $shellVerbosity = 0
178 106
        };
179
180 106
        if ($input->hasParameterOption(['--quiet', '-q'], true)) {
181
            $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
182
            $shellVerbosity = -1;
183
        } else {
184
            if (
185 106
                $input->hasParameterOption('-vvv', true)
186 105
                || $input->hasParameterOption('--verbose=3', true)
187 106
                || 3 === $input->getParameterOption('--verbose', false, true)
188
            ) {
189 1
                $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
190 1
                $shellVerbosity = 3;
191
            } elseif (
192 105
                $input->hasParameterOption('-vv', true)
193 104
                || $input->hasParameterOption('--verbose=2', true)
194 105
                || 2 === $input->getParameterOption('--verbose', false, true)
195
            ) {
196 1
                $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
197 1
                $shellVerbosity = 2;
198
            } elseif (
199 104
                $input->hasParameterOption('-v', true)
200 104
                || $input->hasParameterOption('--verbose=1', true)
201 104
                || $input->hasParameterOption('--verbose', true)
202 104
                || $input->getParameterOption('--verbose', false, true)
203
            ) {
204 1
                $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
205 1
                $shellVerbosity = 1;
206
            }
207
        }
208
209 106
        if (-1 === $shellVerbosity) {
210
            $input->setInteractive(false);
211
        }
212
213 106
        \putenv('SHELL_VERBOSITY=' . $shellVerbosity);
214 106
        $_ENV['SHELL_VERBOSITY'] = $shellVerbosity;
215 106
        $_SERVER['SHELL_VERBOSITY'] = $shellVerbosity;
216
    }
217
}
218