Passed
Pull Request — master (#1240)
by Aleksei
28:51 queued 14:26
created

Console   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 191
Duplicated Lines 0 %

Test Coverage

Coverage 89.58%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 36
eloc 92
c 1
b 0
f 0
dl 0
loc 191
ccs 86
cts 96
cp 0.8958
rs 9.52

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
F configureIO() 0 66 21
A addCommands() 0 18 6
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 107
    ) {}
42
43
    /**
44
     * Run console application.
45
     *
46
     * @throws \Throwable
47
     */
48 5
    public function start(InputInterface $input = new ArgvInput(), OutputInterface $output = new ConsoleOutput()): int
49
    {
50 5
        return $this->run(
51 5
            $input->getFirstArgument() ?? 'list',
52 5
            $input,
53 5
            $output,
54 5
        )->getCode();
55
    }
56
57
    /**
58
     * Run selected command.
59
     *
60
     * @throws \Throwable
61
     * @throws CommandNotFoundException
62
     */
63 106
    public function run(
64
        ?string $command,
65
        array|InputInterface $input = [],
66
        OutputInterface $output = new BufferedOutput(),
67
    ): CommandOutput {
68 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...
69
70 106
        $this->configureIO($input, $output);
71
72 106
        if ($command !== null) {
73 105
            $input = new InputProxy($input, ['firstArgument' => $command]);
74
        }
75
76
        /**
77
         * @psalm-suppress InvalidArgument
78
         */
79 106
        $code = $this->scope->runScope(
80 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

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

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