Completed
Branch 09branch (0a5c88)
by Anton
05:50
created

ConsoleDispatcher::run()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 33
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 17
nc 2
nop 3
dl 0
loc 33
rs 8.8571
c 0
b 0
f 0
1
<?php
2
/**
3
 * spiral
4
 *
5
 * @author    Wolfy-J
6
 */
7
8
namespace Spiral\Console;
9
10
use Spiral\Console\Configs\ConsoleConfig;
11
use Spiral\Console\Exceptions\ConsoleException;
12
use Spiral\Console\Logging\DebugHandler;
13
use Spiral\Core\Component;
14
use Spiral\Core\Container;
15
use Spiral\Core\Container\SingletonInterface;
16
use Spiral\Core\ContainerInterface;
17
use Spiral\Core\Core;
18
use Spiral\Core\DispatcherInterface;
19
use Spiral\Core\MemoryInterface;
20
use Spiral\Core\NullMemory;
21
use Spiral\Debug\LogManager;
22
use Spiral\Debug\SnapshotInterface;
23
use Symfony\Component\Console\Application as ConsoleApplication;
24
use Symfony\Component\Console\Input\ArgvInput;
25
use Symfony\Component\Console\Input\ArrayInput;
26
use Symfony\Component\Console\Input\InputInterface;
27
use Symfony\Component\Console\Output\BufferedOutput;
28
use Symfony\Component\Console\Output\ConsoleOutput;
29
use Symfony\Component\Console\Output\OutputInterface;
30
31
/**
32
 * Used as application dispatcher in console mode. Can execute automatically locate and execute
33
 * every available Symfony command.
34
 */
35
class ConsoleDispatcher extends Component implements SingletonInterface, DispatcherInterface
36
{
37
    /**
38
     * Undefined response code for command (errors). See below.
39
     */
40
    const CODE_UNDEFINED = 102;
41
42
    /**
43
     * @var ConsoleApplication
44
     */
45
    private $application = null;
46
47
    /**
48
     * Active console output.
49
     *
50
     * @var OutputInterface
51
     */
52
    private $output = null;
53
54
    /**
55
     * @var ConsoleConfig
56
     */
57
    protected $config;
58
59
    /**
60
     * @invisible
61
     * @var ContainerInterface
62
     */
63
    protected $container;
64
65
    /**
66
     * @invisible
67
     * @var MemoryInterface
68
     */
69
    protected $memory;
70
71
    /**
72
     * @invisible
73
     * @var LocatorInterface
74
     */
75
    protected $locator;
76
77
    /**
78
     * @param ConsoleConfig           $config
79
     * @param ContainerInterface|null $container
80
     * @param MemoryInterface|null    $memory
81
     * @param LocatorInterface|null   $locator
82
     */
83
    public function __construct(
84
        ConsoleConfig $config,
85
        ContainerInterface $container = null,
86
        MemoryInterface $memory = null,
87
        LocatorInterface $locator = null
88
    ) {
89
        $this->config = $config;
90
        $this->container = $container ?? new Container();
0 ignored issues
show
Documentation Bug introduced by
It seems like $container ?? new \Spiral\Core\Container() can also be of type object<Spiral\Core\Container>. However, the property $container is declared as type object<Spiral\Core\ContainerInterface>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
91
        $this->memory = $memory ?? new NullMemory();
92
        $this->locator = $locator ?? new NullLocator();
93
    }
94
95
    /**
96
     * {@inheritdoc}
97
     *
98
     * @param InputInterface  $input
99
     * @param OutputInterface $output
100
     */
101
    public function start(InputInterface $input = null, OutputInterface $output = null)
102
    {
103
        //Let's keep output reference to render exceptions
104
        $this->output = $output ?? new ConsoleOutput();
105
106
        //Execute default command
107
        $this->run(
108
            $this->config->defaultCommand(),
109
            $input ?? new ArgvInput(),
110
            $this->output
111
        );
112
    }
113
114
    /**
115
     * Execute console command by it's name.
116
     *
117
     * @param string|null          $command Default command when null.
118
     * @param array|InputInterface $input
119
     * @param OutputInterface      $output
120
     *
121
     * @return CommandOutput
122
     *
123
     * @throws ConsoleException
124
     */
125
    public function run(
126
        string $command = null,
127
        $input = [],
128
        OutputInterface $output = null
129
    ): CommandOutput {
130
        if (is_array($input)) {
131
            $input = new ArrayInput($input + compact('command'));
132
        }
133
134
        $output = $output ?? new BufferedOutput();
135
136
        //Each command are executed in a specific environment
137
        $scope = self::staticContainer($this->container);
138
139
        //This handler will allow us to enable verbosity mode
140
        $debugHandler = $this->container->get(LogManager::class)->debugHandler(
141
            new DebugHandler($output)
142
        );
143
144
        try {
145
            /**
146
             * Debug: this method creates scope for [[InputInterface]] and [[OutputInterface]].
147
             */
148
            $code = $this->consoleApplication()->find($command)->run($input, $output);
149
        } finally {
150
            //Restore default debug handler
151
            $this->container->get(LogManager::class)->debugHandler($debugHandler);
152
153
            self::staticContainer($scope);
154
        }
155
156
        return new CommandOutput($code ?? self::CODE_UNDEFINED, $output);
157
    }
158
159
    /**
160
     * Get or create instance of ConsoleApplication.
161
     *
162
     * @return ConsoleApplication
163
     */
164
    public function consoleApplication()
165
    {
166
        if (!empty($this->application)) {
167
            //Already initiated
168
            return $this->application;
169
        }
170
171
        $this->application = new ConsoleApplication(
172
            'Spiral, Console Toolkit',
173
            Core::VERSION
174
        );
175
176
        $this->application->setCatchExceptions(false);
177
178
        foreach ($this->getCommands() as $command) {
179
            $this->application->add($this->container->get($command));
180
        }
181
182
        return $this->application;
183
    }
184
185
    /**
186
     * Locate every available Symfony command using Tokenizer.
187
     *
188
     * @param bool $reset Ignore cache.
189
     *
190
     * @return array
191
     */
192
    public function getCommands(bool $reset = false): array
193
    {
194
        $commands = (array)$this->memory->loadData('commands');
195
        if (!empty($commands) && !$reset) {
196
            //Reading from cache
197
            return $commands + $this->config->userCommands();
198
        }
199
200
        if ($this->config->locateCommands()) {
201
            //Automatically locate commands
202
            $commands = $this->locator->locateCommands();
203
        }
204
205
        //Warming up cache
206
        $this->memory->saveData('commands', $commands);
207
208
        return $commands + $this->config->userCommands();
209
    }
210
211
    /**
212
     * {@inheritdoc}
213
     *
214
     * @param OutputInterface $output
215
     */
216
    public function handleSnapshot(SnapshotInterface $snapshot, OutputInterface $output = null)
217
    {
218
        $output = $output ??  $this->output ?? new ConsoleOutput(OutputInterface::VERBOSITY_VERBOSE);
219
220
        //Rendering exception in console
221
        $this->consoleApplication()->renderException($snapshot->getException(), $output);
0 ignored issues
show
Compatibility introduced by
$snapshot->getException() of type object<Throwable> is not a sub-type of object<Exception>. It seems like you assume a concrete implementation of the interface Throwable to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
222
    }
223
}