Console   A
last analyzed

Complexity

Total Complexity 35

Size/Duplication

Total Lines 188
Duplicated Lines 0 %

Test Coverage

Coverage 90.32%

Importance

Changes 0
Metric Value
wmc 35
eloc 89
dl 0
loc 188
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
F configureIO() 0 66 21
A getApplication() 0 27 4
A addCommands() 0 15 5
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
130 106
        foreach ($commands as $command) {
131 106
            if ($command instanceof Command) {
132 83
                $command->setContainer($this->container);
133 83
                $command->setInterceptors($interceptors);
134
            }
135
136 106
            if ($this->dispatcher !== null && $command instanceof EventDispatcherAwareInterface) {
137 1
                $command->setEventDispatcher($this->dispatcher);
138
            }
139
140 106
            $this->application->add($command);
141
        }
142
    }
143
144
    /**
145
     * Extracted in order to manage command lifecycle.
146
     *
147
     * @see Application::configureIO()
148
     */
149 106
    private function configureIO(InputInterface $input, OutputInterface $output): void
150
    {
151 106
        if ($input->hasParameterOption(['--ansi'], true)) {
152
            $output->setDecorated(true);
153 106
        } elseif ($input->hasParameterOption(['--no-ansi'], true)) {
154
            $output->setDecorated(false);
155
        }
156
157 106
        if ($input->hasParameterOption(['--no-interaction', '-n'], true)) {
158
            $input->setInteractive(false);
159 106
        } elseif (\function_exists('posix_isatty')) {
160 106
            $inputStream = null;
161
162 106
            if ($input instanceof StreamableInputInterface) {
163 106
                $inputStream = $input->getStream();
164
            }
165
166 106
            if ($inputStream !== null && !@posix_isatty($inputStream) && \getenv('SHELL_INTERACTIVE') === false) {
167
                $input->setInteractive(false);
168
            }
169
        }
170
171 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...
172
            -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...
173 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...
174 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...
175 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...
176 12
            default => $shellVerbosity = 0,
177 106
        };
178
179 106
        if ($input->hasParameterOption(['--quiet', '-q'], true)) {
180
            $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
181
            $shellVerbosity = -1;
182
        } else {
183
            if (
184 106
                $input->hasParameterOption('-vvv', true)
185 105
                || $input->hasParameterOption('--verbose=3', true)
186 106
                || $input->getParameterOption('--verbose', false, true) === 3
187
            ) {
188 1
                $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
189 1
                $shellVerbosity = 3;
190
            } elseif (
191 105
                $input->hasParameterOption('-vv', true)
192 104
                || $input->hasParameterOption('--verbose=2', true)
193 105
                || $input->getParameterOption('--verbose', false, true) === 2
194
            ) {
195 1
                $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
196 1
                $shellVerbosity = 2;
197
            } elseif (
198 104
                $input->hasParameterOption('-v', true)
199 104
                || $input->hasParameterOption('--verbose=1', true)
200 104
                || $input->hasParameterOption('--verbose', true)
201 104
                || $input->getParameterOption('--verbose', false, true)
202
            ) {
203 1
                $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
204 1
                $shellVerbosity = 1;
205
            }
206
        }
207
208 106
        if ($shellVerbosity === -1) {
209
            $input->setInteractive(false);
210
        }
211
212 106
        \putenv('SHELL_VERBOSITY=' . $shellVerbosity);
213 106
        $_ENV['SHELL_VERBOSITY'] = $shellVerbosity;
214 106
        $_SERVER['SHELL_VERBOSITY'] = $shellVerbosity;
215
    }
216
}
217