Completed
Branch 09branch (0b333e)
by Anton
02:57
created

ConsoleDispatcher::run()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 17
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 12
nc 2
nop 3
dl 0
loc 17
rs 9.4285
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\ArrayInput;
25
use Symfony\Component\Console\Input\InputInterface;
26
use Symfony\Component\Console\Output\BufferedOutput;
27
use Symfony\Component\Console\Output\ConsoleOutput;
28
use Symfony\Component\Console\Output\OutputInterface;
29
30
/**
31
 * Used as application dispatcher in console mode. Can execute automatically locate and execute
32
 * every available Symfony command.
33
 */
34
class ConsoleDispatcher extends Component implements SingletonInterface, DispatcherInterface
35
{
36
    /**
37
     * Undefined response code for command (errors). See below.
38
     */
39
    const CODE_UNDEFINED = 102;
40
41
    /**
42
     * @var ConsoleApplication
43
     */
44
    private $application = null;
45
46
    /**
47
     * Active console output.
48
     *
49
     * @var OutputInterface
50
     */
51
    private $output = null;
52
53
    /**
54
     * @var ConsoleConfig
55
     */
56
    protected $config;
57
58
    /**
59
     * @invisible
60
     * @var ContainerInterface
61
     */
62
    protected $container;
63
64
    /**
65
     * @invisible
66
     * @var MemoryInterface
67
     */
68
    protected $memory;
69
70
    /**
71
     * @invisible
72
     * @var LocatorInterface
73
     */
74
    protected $locator;
75
76
    /**
77
     * @param ConsoleConfig           $config
78
     * @param ContainerInterface|null $container
79
     * @param MemoryInterface|null    $memory
80
     * @param LocatorInterface|null   $locator
81
     */
82
    public function __construct(
83
        ConsoleConfig $config,
84
        ContainerInterface $container = null,
85
        MemoryInterface $memory = null,
86
        LocatorInterface $locator = null
87
    ) {
88
        $this->config = $config;
89
        $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...
90
        $this->memory = $memory ?? new NullMemory();
91
        $this->locator = $locator ?? new NullLocator();
92
    }
93
94
    /**
95
     * {@inheritdoc}
96
     *
97
     * @param InputInterface  $input
98
     * @param OutputInterface $output
99
     */
100
    public function start(InputInterface $input = null, OutputInterface $output = null)
101
    {
102
        //Let's keep output reference to render exceptions
103
        $this->output = $output ?? new ConsoleOutput();
104
105
        $this->runScoped(function () use ($input) {
106
            $this->consoleApplication()->run($input, $this->output);
107
        }, $this->output);
108
    }
109
110
    /**
111
     * Execute console command by it's name. Attention, this method will automatically set debug
112
     * handler which will display log messages into console when verbosity is ON, hovewer, already
113
     * existed Logger instances would not be affected.
114
     *
115
     * @param string|null          $command Default command when null.
116
     * @param array|InputInterface $input
117
     * @param OutputInterface      $output
118
     *
119
     * @return CommandOutput
120
     *
121
     * @throws ConsoleException
122
     */
123
    public function run(
124
        string $command = null,
125
        $input = [],
126
        OutputInterface $output = null
127
    ): CommandOutput {
128
        if (is_array($input)) {
129
            $input = new ArrayInput($input + compact('command'));
130
        }
131
132
        $output = $output ?? new BufferedOutput();
133
134
        $code = $this->runScoped(function () use ($input, $output, $command) {
135
            $this->consoleApplication()->find($command)->run($input, $output);
136
        }, $output);
137
138
        return new CommandOutput($code ?: self::CODE_UNDEFINED, $output);
139
    }
140
141
    /**
142
     * Get or create instance of ConsoleApplication.
143
     *
144
     * @return ConsoleApplication
145
     */
146
    public function consoleApplication()
147
    {
148
        if (!empty($this->application)) {
149
            //Already initiated
150
            return $this->application;
151
        }
152
153
        $this->application = new ConsoleApplication(
154
            'Spiral, Console Toolkit',
155
            Core::VERSION
156
        );
157
158
        $this->application->setCatchExceptions(false);
159
160
        foreach ($this->getCommands() as $command) {
161
            $this->application->add($this->container->get($command));
162
        }
163
164
        return $this->application;
165
    }
166
167
    /**
168
     * Locate every available Symfony command using Tokenizer.
169
     *
170
     * @param bool $reset Ignore cache.
171
     *
172
     * @return array
173
     */
174
    public function getCommands(bool $reset = false): array
175
    {
176
        $commands = (array)$this->memory->loadData('commands');
177
        if (!empty($commands) && !$reset) {
178
            //Reading from cache
179
            return $commands + $this->config->userCommands();
180
        }
181
182
        if ($this->config->locateCommands()) {
183
            //Automatically locate commands
184
            $commands = $this->locator->locateCommands();
185
        }
186
187
        //Warming up cache
188
        $this->memory->saveData('commands', $commands);
189
190
        return $commands + $this->config->userCommands();
191
    }
192
193
    /**
194
     * {@inheritdoc}
195
     *
196
     * @param OutputInterface $output
197
     */
198
    public function handleSnapshot(SnapshotInterface $snapshot, OutputInterface $output = null)
199
    {
200
        $output = $output ??  $this->output ?? new ConsoleOutput(OutputInterface::VERBOSITY_VERBOSE);
201
202
        //Rendering exception in console
203
        $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...
204
    }
205
206
    /**
207
     * Run method in console IoC scope.
208
     *
209
     * @param \Closure                                          $closure
210
     * @param \Symfony\Component\Console\Output\OutputInterface $output
211
     *
212
     * @return mixed
213
     */
214
    private function runScoped(\Closure $closure, OutputInterface $output)
215
    {
216
        //Each command are executed in a specific environment
217
        $scope = self::staticContainer($this->container);
218
219
        //This handler will allow us to enable verbosity mode
220
        $debugHandler = $this->container->get(LogManager::class)->debugHandler(
221
            new DebugHandler($output)
222
        );
223
224
        try {
225
            return $closure->call($this);
226
        } finally {
227
            //Restore default debug handler and container scope
228
            $this->container->get(LogManager::class)->debugHandler($debugHandler);
229
            self::staticContainer($scope);
230
        }
231
    }
232
}