Passed
Pull Request — master (#1104)
by Maxim
10:52
created

Command::execute()   A

Complexity

Conditions 4
Paths 11

Size

Total Lines 43
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 26
dl 0
loc 43
ccs 30
cts 30
cp 1
rs 9.504
c 1
b 0
f 0
cc 4
nc 11
nop 2
crap 4
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\Attributes\Factory;
10
use Spiral\Console\Configurator\Attribute\Parser as AttributeParser;
11
use Spiral\Console\Configurator\AttributeBasedConfigurator;
12
use Spiral\Console\Configurator\Configurator;
13
use Spiral\Console\Configurator\Signature\Parser as SignatureParser;
14
use Spiral\Console\Configurator\SignatureBasedConfigurator;
15
use Spiral\Console\Event\CommandFinished;
16
use Spiral\Console\Event\CommandStarting;
17
use Spiral\Console\Traits\HelpersTrait;
18
use Spiral\Core\ContainerScope;
19
use Spiral\Core\CoreInterceptorInterface;
20
use Spiral\Core\CoreInterface;
21
use Spiral\Core\Exception\ScopeException;
22
use Spiral\Core\Scope;
23
use Spiral\Core\ScopeInterface;
24
use Spiral\Core\InterceptorPipeline;
25
use Spiral\Events\EventDispatcherAwareInterface;
26
use Spiral\Interceptors\Context\CallContext;
27
use Spiral\Interceptors\Context\Target;
28
use Spiral\Interceptors\HandlerInterface;
29
use Spiral\Interceptors\InterceptorInterface;
30
use Symfony\Component\Console\Command\Command as SymfonyCommand;
31
use Symfony\Component\Console\Input\InputInterface;
32
use Symfony\Component\Console\Output\OutputInterface;
33
use Symfony\Component\Console\Style\SymfonyStyle;
34
35
/**
36
 * Provides automatic command configuration and access to global container scope.
37
 */
38
#[\Spiral\Core\Attribute\Scope('console')]
39
abstract class Command extends SymfonyCommand implements EventDispatcherAwareInterface
40
{
41
    use HelpersTrait;
42
43
    /** Command name. */
44
    protected const NAME = '';
45
    /** Short command description. */
46
    protected const DESCRIPTION = null;
47
    /** Command signature. */
48
    protected const SIGNATURE = null;
49
    /** Command options specified in Symfony format. For more complex definitions redefine getOptions() method. */
50
    protected const OPTIONS = [];
51
    /** Command arguments specified in Symfony format. For more complex definitions redefine getArguments() method. */
52
    protected const ARGUMENTS = [];
53
54
    protected ?ContainerInterface $container = null;
55
    protected ?EventDispatcherInterface $eventDispatcher = null;
56
57
    /** @var array<class-string<CoreInterceptorInterface|InterceptorInterface>> */
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<class-string<CoreI...|InterceptorInterface>> at position 2 could not be parsed: Unknown type name 'class-string' at position 2 in array<class-string<CoreInterceptorInterface|InterceptorInterface>>.
Loading history...
58
    protected array $interceptors = [];
59
60
    /** @internal */
61 101
    public function setContainer(ContainerInterface $container): void
62
    {
63 101
        $this->container = $container;
64
    }
65
66
    /**
67
     * @internal
68
     * @param array<class-string<CoreInterceptorInterface|InterceptorInterface>> $interceptors
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<class-string<CoreI...|InterceptorInterface>> at position 2 could not be parsed: Unknown type name 'class-string' at position 2 in array<class-string<CoreInterceptorInterface|InterceptorInterface>>.
Loading history...
69
     */
70 101
    public function setInterceptors(array $interceptors): void
71
    {
72 101
        $this->interceptors = $interceptors;
73
    }
74
75 1
    public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void
76
    {
77 1
        $this->eventDispatcher = $eventDispatcher;
78
    }
79
80
    /**
81
     * Pass execution to "perform" method using container to resolve method dependencies.
82
     * @final
83
     * @TODO Change to final in v4.0
84
     */
85 99
    protected function execute(InputInterface $input, OutputInterface $output): int
86
    {
87 99
        if ($this->container === null) {
88 1
            throw new ScopeException('Container is not set');
89
        }
90
91 98
        $method = method_exists($this, 'perform') ? 'perform' : '__invoke';
92
93
        try {
94 98
            [$this->input, $this->output] = [$this->prepareInput($input), $this->prepareOutput($input, $output)];
95
96 98
            $this->eventDispatcher?->dispatch(new CommandStarting($this, $this->input, $this->output));
0 ignored issues
show
Bug introduced by
It seems like $this->output can also be of type null; however, parameter $output of Spiral\Console\Event\Com...Starting::__construct() does only seem to accept Symfony\Component\Console\Output\OutputInterface, maybe add an additional type check? ( Ignorable by Annotation )

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

96
            $this->eventDispatcher?->dispatch(new CommandStarting($this, $this->input, /** @scrutinizer ignore-type */ $this->output));
Loading history...
97
98
99
            // Executing perform method with method injection
100 98
            $code = $this->container->get(ScopeInterface::class)
101 98
                ->runScope(
102 98
                    new Scope(
103 98
                        name: 'console.command',
104 98
                        bindings: [
105 98
                            InputInterface::class => $input,
106 98
                            OutputInterface::class => $output,
107 98
                        ],
108 98
                        autowire: false,
109 98
                    ),
110 98
                    function () use ($method) {
111 98
                        $core = $this->buildCore();
0 ignored issues
show
Deprecated Code introduced by
The function Spiral\Console\Command::buildCore() has been deprecated: This method will be removed in v4.0. ( Ignorable by Annotation )

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

111
                        $core = /** @scrutinizer ignore-deprecated */ $this->buildCore();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
112 98
                        $arguments = ['input' => $this->input, 'output' => $this->output, 'command' => $this];
113
114 98
                        return $core instanceof HandlerInterface
115 98
                            ? (int)$core->handle(new CallContext(
116 98
                                Target::fromReflection(new \ReflectionMethod(static::class, $method)),
117 98
                                $arguments,
118 98
                            ))
119 95
                            : (int)$core->callAction(static::class, $method, $arguments);
120 98
                    },
121 98
                );
122
123 95
            $this->eventDispatcher?->dispatch(new CommandFinished($this, $code, $this->input, $this->output));
0 ignored issues
show
Bug introduced by
It seems like $this->output can also be of type null; however, parameter $output of Spiral\Console\Event\Com...Finished::__construct() does only seem to accept Symfony\Component\Console\Output\OutputInterface, maybe add an additional type check? ( Ignorable by Annotation )

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

123
            $this->eventDispatcher?->dispatch(new CommandFinished($this, $code, $this->input, /** @scrutinizer ignore-type */ $this->output));
Loading history...
124
125 95
            return $code;
126
        } finally {
127 98
            [$this->input, $this->output] = [null, null];
128
        }
129
    }
130
131
    /**
132
     * @deprecated This method will be removed in v4.0.
133
     */
134 98
    protected function buildCore(): CoreInterface|HandlerInterface
135
    {
136 98
        return ContainerScope::getContainer()
137 98
            ->get(CommandCoreFactory::class)
138 98
            ->make($this->interceptors, $this->eventDispatcher);
139
    }
140
141 98
    protected function prepareInput(InputInterface $input): InputInterface
142
    {
143 98
        return $input;
144
    }
145
146 98
    protected function prepareOutput(InputInterface $input, OutputInterface $output): OutputInterface
147
    {
148 98
        return new SymfonyStyle($input, $output);
149
    }
150
151
    /**
152
     * Configures the command.
153
     */
154 117
    protected function configure(): void
155
    {
156 117
        $configurator = new Configurator([
157 117
            new SignatureBasedConfigurator(new SignatureParser()),
158 117
            new AttributeBasedConfigurator(new AttributeParser((new Factory())->create())),
159 117
        ]);
160 117
        $configurator->configure($this, new \ReflectionClass($this));
161
    }
162
163
    /**
164
     * Define command options.
165
     */
166 94
    protected function defineOptions(): array
167
    {
168 94
        return static::OPTIONS;
169
    }
170
171
    /**
172
     * Define command arguments.
173
     */
174 94
    protected function defineArguments(): array
175
    {
176 94
        return static::ARGUMENTS;
177
    }
178
179 99
    protected function interact(InputInterface $input, OutputInterface $output): void
180
    {
181 99
        parent::interact($input, $output);
182
183 99
        $this->container?->get(PromptArguments::class)->promptMissedArguments($this, $input, $output);
184
    }
185
}
186