Passed
Push — master ( e7d330...e6cdc9 )
by Aleksei
11:08 queued 15s
created

Command::setContainer()   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 1
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\InterceptableCore;
23
use Spiral\Events\EventDispatcherAwareInterface;
24
use Symfony\Component\Console\Command\Command as SymfonyCommand;
25
use Symfony\Component\Console\Input\InputInterface;
26
use Symfony\Component\Console\Output\OutputInterface;
27
use Symfony\Component\Console\Style\SymfonyStyle;
28
29
/**
30
 * Provides automatic command configuration and access to global container scope.
31
 */
32
abstract class Command extends SymfonyCommand implements EventDispatcherAwareInterface
33
{
34
    use HelpersTrait;
35
36
    /** Command name. */
37
    protected const NAME = '';
38
    /** Short command description. */
39
    protected const DESCRIPTION = null;
40
    /** Command signature. */
41
    protected const SIGNATURE = null;
42
    /** Command options specified in Symfony format. For more complex definitions redefine getOptions() method. */
43
    protected const OPTIONS = [];
44
    /** Command arguments specified in Symfony format. For more complex definitions redefine getArguments() method. */
45
    protected const ARGUMENTS = [];
46
47
    protected ?ContainerInterface $container = null;
48
    protected ?EventDispatcherInterface $eventDispatcher = null;
49
50
    /** @var array<class-string<CoreInterceptorInterface>> */
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<class-string<CoreInterceptorInterface>> at position 2 could not be parsed: Unknown type name 'class-string' at position 2 in array<class-string<CoreInterceptorInterface>>.
Loading history...
51
    protected array $interceptors = [];
52
53
    /** {@internal} */
54 99
    public function setContainer(ContainerInterface $container): void
55
    {
56 99
        $this->container = $container;
57
    }
58
59
    /**
60
     * {@internal}
61
     * @param array<class-string<CoreInterceptorInterface>> $interceptors
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<class-string<CoreInterceptorInterface>> at position 2 could not be parsed: Unknown type name 'class-string' at position 2 in array<class-string<CoreInterceptorInterface>>.
Loading history...
62
     */
63 99
    public function setInterceptors(array $interceptors): void
64
    {
65 99
        $this->interceptors = $interceptors;
66
    }
67
68 1
    public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): void
69
    {
70 1
        $this->eventDispatcher = $eventDispatcher;
71
    }
72
73
    /**
74
     * Pass execution to "perform" method using container to resolve method dependencies.
75
     */
76 97
    protected function execute(InputInterface $input, OutputInterface $output): int
77
    {
78 97
        if ($this->container === null) {
79 1
            throw new ScopeException('Container is not set');
80
        }
81
82 96
        $method = method_exists($this, 'perform') ? 'perform' : '__invoke';
83
84 96
        $core = $this->buildCore();
85
86
        try {
87 96
            [$this->input, $this->output] = [$this->prepareInput($input), $this->prepareOutput($input, $output)];
88
89 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

89
            $this->eventDispatcher?->dispatch(new CommandStarting($this, $this->input, /** @scrutinizer ignore-type */ $this->output));
Loading history...
90
91
            // Executing perform method with method injection
92 96
            $code = (int)$core->callAction(static::class, $method, [
93 96
                'input' => $this->input,
94 96
                'output' => $this->output,
95 96
                'command' => $this,
96 96
            ]);
97
98 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

98
            $this->eventDispatcher?->dispatch(new CommandFinished($this, $code, $this->input, /** @scrutinizer ignore-type */ $this->output));
Loading history...
99
100 93
            return $code;
101
        } finally {
102 96
            [$this->input, $this->output] = [null, null];
103
        }
104
    }
105
106 96
    protected function buildCore(): CoreInterface
107
    {
108 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

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