Console::runCallable()   B
last analyzed

Complexity

Conditions 6
Paths 31

Size

Total Lines 26
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
dl 0
loc 26
ccs 0
cts 17
cp 0
rs 8.439
c 0
b 0
f 0
cc 6
eloc 17
nc 31
nop 3
crap 42
1
<?php
2
3
namespace Buttress\Concrete\Console;
4
5
use Buttress\Concrete\CommandBus\Provider\DefaultProvider;
6
use Buttress\Concrete\Console\Command\CacheCommand;
7
use Buttress\Concrete\CommandBus\Command\HandlerLocator;
8
use Buttress\Concrete\Console\Command\Collection\Collection;
9
use Buttress\Concrete\Console\Command\HelpCommand;
10
use Buttress\Concrete\Console\Command\PackageCommand;
11
use Buttress\Concrete\Console\Command\SiteCommand;
12
use Buttress\Concrete\Exception\BaseException;
13
use Buttress\Concrete\Exception\ErrorHandler;
14
use Buttress\Concrete\Exception\RuntimeException;
15
use Buttress\Concrete\Exception\VersionMismatchException;
16
use Buttress\Concrete\Locator\Site;
17
use Buttress\Concrete\Route\Dispatcher;
18
use Buttress\Concrete\Route\RouteCollector;
19
use League\CLImate\Argument\Argument;
20
use League\CLImate\Argument\Manager;
21
use League\CLImate\CLImate;
22
use Psr\Container\ContainerInterface;
23
use Psr\Log\LoggerInterface;
24
use Whoops\Handler\PlainTextHandler;
25
use Whoops\Run;
26
27
/**
28
 * The main entry point to the c5console project
29
 */
30
class Console
31
{
32
33
    /** @var \Psr\Container\ContainerInterface */
34
    public $container;
35
36
    /** @var \Buttress\Concrete\Console\Command\Collection */
37
    public $collection;
38
39
    /** @var string The last invoked filename */
40
    public $filename;
41
42
    /** @var string The last invoked command string */
43
    public $commandName;
44
45
    /** @var \Buttress\Concrete\Locator\Site */
46
    protected $site;
47
48
    /** @var \Buttress\Concrete\Exception\ErrorHandler */
49
    protected $errorHandler;
50
51
    /**
52
     * A list of the available console commands
53
     * @var string[]
54
     */
55
    protected $commands = [
56
        CacheCommand::class,
57
        HelpCommand::class,
58
        PackageCommand::class,
59
        SiteCommand::class
60
    ];
61
62
    /**
63
     * A list of CommandBus Handler Providers
64
     * @var string[]
65
     */
66
    protected $providers = [
67
        DefaultProvider::class
68
    ];
69
70
    public function __construct(
71
        ContainerInterface $container,
72
        Collection $collection,
73
        Site $site,
74
        ErrorHandler $e
75
    ) {
76
        $this->container = $container;
77
        $this->collection = $collection;
0 ignored issues
show
Documentation Bug introduced by
It seems like $collection of type object<Buttress\Concrete...\Collection\Collection> is incompatible with the declared type object<Buttress\Concrete...ole\Command\Collection> of property $collection.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
78
        $this->site = $site;
79
        $this->errorHandler = $e;
80
    }
81
82
    public function run(array $arguments)
83
    {
84
        $this->filename = array_shift($arguments);
85
        $this->commandName = array_shift($arguments);
86
87
        $site = $this->site;
88
        $cli = $this->container->get(CLImate::class);
89
90
        $dispatcher = Dispatcher::simpleDispatcher(function (RouteCollector $routes) use ($site) {
91
            foreach ($this->collection->all() as $command) {
92
                $command->registerRoutes($routes, $site);
93
            }
94
        });
95
96
        return $this->dispatch($dispatcher, $cli);
97
    }
98
99
    /**
100
     * Prepare to run, this method is used for loading service providers and things like that.
101
     */
102
    public function prepare()
103
    {
104
        $this->registerCommands();
105
        $this->registerHandlers();
106
107
        $manager = new Manager();
108
        $manager->add('verbose', [
109
            'prefix' => 'v',
110
            'longPrefix' => 'verbose',
111
            'noValue' => true
112
        ]);
113
        $manager->parse();
114
115
        if ($manager->get('verbose')) {
116
            $this->errorHandler->setVerbose(true);
117
        }
118
119
        $this->registerErrorHandler();
120
        return $this;
121
    }
122
123
    /**
124
     * @param $dispatcher
125
     * @return int
126
     */
127
    protected function dispatch(Dispatcher $dispatcher, CLImate $cli)
128
    {
129
        $command = $this->commandName ?: 'help';
130
131
        $result = $dispatcher->dispatch($command);
132
        switch ($result[0]) {
133
            case 0:
134
                $cli->error(sprintf('Command "%s" not found.', $command));
135
                return 1;
136
            case 1:
137
                list(, $callable, $data) = $result;
138
139
                return $this->runCallable($cli, $callable, $data);
140
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
141
            case 2:
142
                $cli->error(sprintf('Unexpected routing error.'));
143
                return 1;
144
        }
145
    }
146
147
    protected function registerCommands()
148
    {
149
        foreach ($this->commands as $command) {
150
            $this->collection->add($this->container->get($command));
151
        }
152
    }
153
154
    protected function registerHandlers()
155
    {
156
        $handler = $this->container->get(HandlerLocator::class);
157
        foreach ($this->providers as $provider) {
158
            $this->container->get($provider)->register($handler, $this->site);
159
        }
160
    }
161
162
    /**
163
     * Run a callable
164
     * @param \League\CLImate\CLImate $cli
165
     * @param callable $callable
166
     * @param array $data
167
     * @return int
168
     */
169
    private function runCallable(CLImate $cli, $callable, array $data)
170
    {
171
        try {
172
            if (is_string($callable)) {
173
                if (strpos($callable, '::')) {
174
                    list($class, $method) = explode('::', $callable, 2);
175
                    $callable = [$this->container->get($class), $method];
176
                } else {
177
                    $callable = $this->container->get($callable);
178
                }
179
            }
180
181
            if (!is_callable($callable)) {
182
                throw new RuntimeException('Invalid route callable');
183
            }
184
185
            return $callable($this->site, ...$data);
186
        } catch (VersionMismatchException $e) {
187
            $cli->error('Invalid Version: ' . $e->getMessage())
188
                ->dim(sprintf('Detected version "%s"', $e->getVersion()));
189
        } catch (BaseException $e) {
190
            $cli->error('Runtime Error: ' . $e->getMessage());
191
        }
192
193
        return 1;
194
    }
195
196
    public function registerErrorHandler()
197
    {
198
        $this->errorHandler->register();
199
    }
200
}
201