CliRouter   A
last analyzed

Complexity

Total Complexity 23

Size/Duplication

Total Lines 176
Duplicated Lines 0 %

Importance

Changes 8
Bugs 1 Features 0
Metric Value
wmc 23
eloc 47
dl 0
loc 176
rs 10
c 8
b 1
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A terminate() 0 8 2
A parseServer() 0 18 2
A setTerminateException() 0 3 1
A setStdIn() 0 4 1
A __construct() 0 8 2
A __destruct() 0 3 2
A __wakeup() 0 3 1
A error() 0 4 1
A generate() 0 4 1
A addQuery() 0 17 3
A match() 0 12 1
A validateArgs() 0 8 2
A getStdIn() 0 10 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BEAR\Package\Provide\Router;
6
7
use Aura\Cli\CliFactory;
8
use Aura\Cli\Context\OptionFactory;
9
use Aura\Cli\Status;
10
use Aura\Cli\Stdio;
11
use BEAR\Package\Annotation\StdIn;
12
use BEAR\Sunday\Extension\Router\RouterInterface;
13
use Exception;
14
use Override;
15
use Ray\Di\Di\Named;
16
use Throwable;
17
18
use function basename;
19
use function file_exists;
20
use function file_put_contents;
21
use function http_build_query;
22
use function parse_str;
23
use function parse_url;
24
use function strtoupper;
25
use function unlink;
26
27
use const PHP_URL_PATH;
28
use const PHP_URL_QUERY;
29
30
/**
31
 * @psalm-import-type Globals from RouterInterface
32
 * @psalm-import-type Server from RouterInterface
33
 * @psalm-type CliServer = array{
34
 *     argc: int,
35
 *     argv: array<int, string>,
36
 *     REQUEST_URI: string,
37
 *     REQUEST_METHOD: string,
38
 *     CONTENT_TYPE?: string,
39
 *     HTTP_CONTENT_TYPE?: string,
40
 *     HTTP_RAW_POST_DATA?: string
41
 * }
42
 */
43
final class CliRouter implements RouterInterface
44
{
45
    private Stdio $stdIo;
46
    private Throwable|null $terminateException = null;
47
48
    public function __construct(
49
        #[Named('original')]
50
        private RouterInterface $router,
51
        Stdio|null $stdIo = null,
52
        #[StdIn]
53
        private string $stdIn = '',
54
    ) {
55
        $this->stdIo = $stdIo ?: (new CliFactory())->newStdio();
56
    }
57
58
    public function __destruct()
59
    {
60
        file_exists($this->stdIn) && unlink($this->stdIn);
61
    }
62
63
    public function __wakeup(): void
64
    {
65
        $this->stdIo = (new CliFactory())->newStdio();
66
    }
67
68
    public function setTerminateException(Throwable $e): void
69
    {
70
        $this->terminateException = $e;
71
    }
72
73
    /**
74
     * @psalm-api
75
     * @deprecated Use constructor injection
76
     * @codeCoverageIgnore
77
     */
78
    public function setStdIn(
79
        string $stdIn,
80
    ): void {
81
        $this->stdIn = $stdIn;
82
    }
83
84
    /**
85
     * {@inheritDoc}
86
     *
87
     * @param Globals $globals
0 ignored issues
show
Bug introduced by
The type BEAR\Package\Provide\Router\Globals was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
88
     * @param Server  $server
0 ignored issues
show
Bug introduced by
The type BEAR\Package\Provide\Router\Server was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
89
     */
90
    #[Override]
91
    public function match(array $globals, array $server)
92
    {
93
        /** @var CliServer $server */
94
        $this->validateArgs($server['argc'], $server['argv']);
95
        // covert console $_SERVER to web $_SERVER $GLOBALS
96
        /** @psalm-suppress InvalidArgument */
97
        [$method, $query, $server] = $this->parseServer($server);
0 ignored issues
show
Bug introduced by
$server of type BEAR\Package\Provide\Router\CliServer is incompatible with the type array expected by parameter $server of BEAR\Package\Provide\Rou...liRouter::parseServer(). ( Ignorable by Annotation )

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

97
        [$method, $query, $server] = $this->parseServer(/** @scrutinizer ignore-type */ $server);
Loading history...
98
        /** @psalm-suppress MixedArgumentTypeCoercion */
99
        [$webGlobals, $webServer] = $this->addQuery($method, $query, $globals, $server); // @phpstan-ignore-line
100
101
        return $this->router->match($webGlobals, $webServer);
102
    }
103
104
    /**
105
     * {@inheritDoc}
106
     */
107
    #[Override]
108
    public function generate($name, $data)
109
    {
110
        return $this->router->generate($name, $data);
111
    }
112
113
    /**
114
     * Set user input query to $globals or &$server
115
     *
116
     * @param array<string, array<string, mixed>> $query
117
     * @param Globals                             $globals
118
     * @param Server                              $server
119
     *
120
     * @return array{0:Globals, 1:Server}
121
     */
122
    private function addQuery(string $method, array $query, array $globals, array $server): array
123
    {
124
        if ($method === 'get') {
125
            $globals['_GET'] = $query;
126
127
            return [$globals, $server];
128
        }
129
130
        if ($method === 'post') {
131
            $globals['_POST'] = $query;
132
133
            return [$globals, $server];
134
        }
135
136
        $server = $this->getStdIn($method, $query, $server);
137
138
        return [$globals, $server];
139
    }
140
141
    private function error(string $command): void
142
    {
143
        $help = new CliRouterHelp(new OptionFactory());
144
        $this->stdIo->outln($help->getHelp($command));
145
    }
146
147
    /** @SuppressWarnings(PHPMD) */
148
    private function terminate(int $status): void
149
    {
150
        if ($this->terminateException instanceof Exception) {
151
            throw $this->terminateException;
152
        }
153
154
        // @codeCoverageIgnoreStart
155
        exit($status);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
156
    }
157
158
    // @codeCoverageIgnoreEnd
159
160
    /**
161
     * Return StdIn in PUT, PATCH or DELETE
162
     *
163
     * @param  array<string, array<string, mixed>|string> $query
164
     * @param Server                                     $server
165
     *
166
     * @return Server
167
     */
168
    private function getStdIn(string $method, array $query, array $server): array
169
    {
170
        if ($method === 'put' || $method === 'patch' || $method === 'delete') {
171
            $server[HttpMethodParams::CONTENT_TYPE] = HttpMethodParams::FORM_URL_ENCODE;
172
            file_put_contents($this->stdIn, http_build_query($query));
173
174
            return $server;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $server returns the type array which is incompatible with the documented return type BEAR\Package\Provide\Router\Server.
Loading history...
175
        }
176
177
        return $server;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $server returns the type array which is incompatible with the documented return type BEAR\Package\Provide\Router\Server.
Loading history...
178
    }
179
180
    /** @param array<int, string> $argv */
181
    private function validateArgs(int $argc, array $argv): void
182
    {
183
        if ($argc >= 3) {
184
            return;
185
        }
186
187
        $this->error(basename($argv[0]));
188
        $this->terminate(Status::USAGE);
189
        // @codeCoverageIgnoreStart
190
    }
191
192
    // @codeCoverageIgnoreEnd
193
194
    /**
195
     * Return $method, $query, $server from $server
196
     *
197
     * @param Server $server
198
     *
199
     * @return array{string, array<string, mixed>, Server}
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{string, array<string, mixed>, Server} at position 2 could not be parsed: Expected ':' at position 2, but found 'string'.
Loading history...
200
     */
201
    private function parseServer(array $server): array
202
    {
203
        /** @var array{argv: array<string>} $server */
204
        [, $method, $uri] = $server['argv'];
205
        $urlQuery = (string) parse_url($uri, PHP_URL_QUERY);
206
        $urlPath = (string) parse_url($uri, PHP_URL_PATH);
207
        $query = [];
208
        if ($urlQuery !== '') {
209
            parse_str($urlQuery, $query);
210
        }
211
212
        $server = [
213
            'REQUEST_METHOD' => strtoupper($method),
214
            'REQUEST_URI' => $urlPath,
215
        ];
216
217
        /** @var array<string, array<mixed>|string> $query */
218
        return [$method, $query, $server];
219
    }
220
}
221