Passed
Pull Request — master (#1095)
by Aleksei
13:52 queued 04:04
created

Command::defineOptions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
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\Interceptor\AttributeInterceptor;
18
use Spiral\Console\Traits\HelpersTrait;
19
use Spiral\Core\CoreInterceptorInterface;
20
use Spiral\Core\CoreInterface;
21
use Spiral\Core\Exception\ScopeException;
22
use Spiral\Core\InterceptorPipeline;
23
use Spiral\Events\EventDispatcherAwareInterface;
24
use Spiral\Interceptors\Context\CallContext;
25
use Spiral\Interceptors\Context\Target;
26
use Spiral\Interceptors\HandlerInterface;
27
use Spiral\Interceptors\InterceptorInterface;
28
use Symfony\Component\Console\Command\Command as SymfonyCommand;
29
use Symfony\Component\Console\Input\InputInterface;
30
use Symfony\Component\Console\Output\OutputInterface;
31
use Symfony\Component\Console\Style\SymfonyStyle;
32
33
/**
34
 * Provides automatic command configuration and access to global container scope.
35
 */
36
abstract class Command extends SymfonyCommand implements EventDispatcherAwareInterface
37
{
38
    use HelpersTrait;
39
40
    /** Command name. */
41
    protected const NAME = '';
42
    /** Short command description. */
43
    protected const DESCRIPTION = null;
44
    /** Command signature. */
45
    protected const SIGNATURE = null;
46
    /** Command options specified in Symfony format. For more complex definitions redefine getOptions() method. */
47
    protected const OPTIONS = [];
48
    /** Command arguments specified in Symfony format. For more complex definitions redefine getArguments() method. */
49
    protected const ARGUMENTS = [];
50
51
    protected ?ContainerInterface $container = null;
52
    protected ?EventDispatcherInterface $eventDispatcher = null;
53
54
    /** @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...
55
    protected array $interceptors = [];
56
57
    /** @internal */
58 99
    public function setContainer(ContainerInterface $container): void
59
    {
60 99
        $this->container = $container;
61
    }
62
63
    /**
64
     * @internal
65
     * @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...
66
     */
67 99
    public function setInterceptors(array $interceptors): void
68
    {
69 99
        $this->interceptors = $interceptors;
70
    }
71
72 1
    public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void
73
    {
74 1
        $this->eventDispatcher = $eventDispatcher;
75
    }
76
77
    /**
78
     * Pass execution to "perform" method using container to resolve method dependencies.
79
     */
80 97
    protected function execute(InputInterface $input, OutputInterface $output): int
81
    {
82 97
        if ($this->container === null) {
83 1
            throw new ScopeException('Container is not set');
84
        }
85
86 96
        $method = method_exists($this, 'perform') ? 'perform' : '__invoke';
87
88 96
        $core = $this->buildCore();
89
90
        try {
91 96
            [$this->input, $this->output] = [$this->prepareInput($input), $this->prepareOutput($input, $output)];
92
93 96
            $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

93
            $this->eventDispatcher?->dispatch(new CommandStarting($this, $this->input, /** @scrutinizer ignore-type */ $this->output));
Loading history...
94
95 96
            $arguments = ['input' => $this->input, 'output' => $this->output, 'command' => $this];
96
97
            // Executing perform method with method injection
98 96
            $code = $core instanceof HandlerInterface
0 ignored issues
show
introduced by
$core is always a sub-type of Spiral\Interceptors\HandlerInterface.
Loading history...
99 96
                ? (int)$core->handle(new CallContext(
100 96
                    Target::fromReflection(new \ReflectionMethod(static::class, $method)),
101 96
                    $arguments,
102 96
                ))
103
                : (int)$core->callAction(static::class, $method, $arguments);
104
105 93
            $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

105
            $this->eventDispatcher?->dispatch(new CommandFinished($this, $code, $this->input, /** @scrutinizer ignore-type */ $this->output));
Loading history...
106
107 93
            return $code;
108
        } finally {
109 96
            [$this->input, $this->output] = [null, null];
110
        }
111
    }
112
113 96
    protected function buildCore(): CoreInterface|HandlerInterface
114
    {
115 96
        $core = $this->container->get(CommandCore::class);
0 ignored issues
show
Bug introduced by
The method get() 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

115
        /** @scrutinizer ignore-call */ 
116
        $core = $this->container->get(CommandCore::class);

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...
116
117 96
        $interceptableCore = (new InterceptorPipeline($this->eventDispatcher))->withCore($core);
0 ignored issues
show
Deprecated Code introduced by
The class Spiral\Core\InterceptorPipeline has been deprecated: use {@see \Spiral\Interceptors\Handler\InterceptorPipeline} instead ( Ignorable by Annotation )

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

117
        $interceptableCore = (/** @scrutinizer ignore-deprecated */ new InterceptorPipeline($this->eventDispatcher))->withCore($core);
Loading history...
118
119 96
        foreach ($this->interceptors as $interceptor) {
120 1
            $interceptableCore->addInterceptor($this->container->get($interceptor));
121
        }
122 96
        $interceptableCore->addInterceptor($this->container->get(AttributeInterceptor::class));
123
124 96
        return $interceptableCore;
125
    }
126
127 96
    protected function prepareInput(InputInterface $input): InputInterface
128
    {
129 96
        return $input;
130
    }
131
132 96
    protected function prepareOutput(InputInterface $input, OutputInterface $output): OutputInterface
133
    {
134 96
        return new SymfonyStyle($input, $output);
135
    }
136
137
    /**
138
     * Configures the command.
139
     */
140 115
    protected function configure(): void
141
    {
142 115
        $configurator = new Configurator([
143 115
            new SignatureBasedConfigurator(new SignatureParser()),
144 115
            new AttributeBasedConfigurator(new AttributeParser((new Factory())->create())),
145 115
        ]);
146 115
        $configurator->configure($this, new \ReflectionClass($this));
147
    }
148
149
    /**
150
     * Define command options.
151
     */
152 92
    protected function defineOptions(): array
153
    {
154 92
        return static::OPTIONS;
155
    }
156
157
    /**
158
     * Define command arguments.
159
     */
160 92
    protected function defineArguments(): array
161
    {
162 92
        return static::ARGUMENTS;
163
    }
164
165 97
    protected function interact(InputInterface $input, OutputInterface $output): void
166
    {
167 97
        parent::interact($input, $output);
168
169 97
        $this->container?->get(PromptArguments::class)->promptMissedArguments($this, $input, $output);
170
    }
171
}
172