CliRouter   A
last analyzed

Complexity

Total Complexity 23

Size/Duplication

Total Lines 189
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
wmc 23
lcom 1
cbo 5
dl 0
loc 189
rs 10
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 2
A __destruct() 0 4 2
A __wakeup() 0 4 1
A setTerminateException() 0 4 1
A setStdIn() 0 4 1
A match() 0 9 1
A generate() 0 4 1
A addQuery() 0 18 3
A error() 0 5 1
A terminate() 0 10 2
A getStdIn() 0 11 4
A validateArgs() 0 7 2
A parseServer() 0 19 2
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 Ray\Di\Di\Inject;
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
class CliRouter implements RouterInterface
31
{
32
    /** @var RouterInterface */
33
    private $router;
34
35
    /** @var Stdio */
36
    private $stdIo;
37
38
    /** @var string */
39
    private $stdIn = '';
40
41
    /** @var Throwable|null */
42
    private $terminateException;
43
44
    /**
45
     * @Named("original")
46
     */
47
    public function __construct(RouterInterface $router, ?Stdio $stdIo = null)
48
    {
49
        $this->router = $router;
50
        $this->stdIo = $stdIo ?: (new CliFactory())->newStdio();
51
    }
52
53
    /**
54
     * {@inheritdoc}
55
     */
56
    public function __destruct()
57
    {
58
        file_exists($this->stdIn) && unlink($this->stdIn);
59
    }
60
61
    public function __wakeup()
62
    {
63
        $this->stdIo = (new CliFactory())->newStdio();
64
    }
65
66
    public function setTerminateException(Throwable $e): void
67
    {
68
        $this->terminateException = $e;
69
    }
70
71
    /**
72
     * @Inject
73
     * @StdIn
74
     */
75
    public function setStdIn(string $stdIn): void
76
    {
77
        $this->stdIn = $stdIn;
78
    }
79
80
    /**
81
     * {@inheritdoc}
82
     *
83
     * @param array{_GET: array<string, string|array>, _POST: array<string, string|array>}                                               $globals
84
     * @param array{argc: int, argv: array<int, string>, CONTENT_TYPE?: string, HTTP_CONTENT_TYPE?: string, HTTP_RAW_POST_DATA?: string} $server
85
     *
86
     * @psalm-suppress ImplementedParamTypeMismatch
87
     */
88
    public function match(array $globals, array $server)
89
    {
90
        $this->validateArgs($server['argc'], $server['argv']);
91
        // covert console $_SERVER to web $_SERVER $GLOBALS
92
        [$method, $query, $server] = $this->parseServer($server);
0 ignored issues
show
Bug introduced by
The variable $method does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $query does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
93
        [$webGlobals, $webServer] = $this->addQuery($method, $query, $globals, $server);
0 ignored issues
show
Bug introduced by
The variable $webGlobals does not exist. Did you mean $globals?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
Bug introduced by
The variable $webServer does not exist. Did you mean $server?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
94
95
        return $this->router->match($webGlobals, $webServer);
0 ignored issues
show
Bug introduced by
The variable $webGlobals does not exist. Did you mean $globals?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
Bug introduced by
The variable $webServer does not exist. Did you mean $server?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
96
    }
97
98
    /**
99
     * {@inheritdoc}
100
     */
101
    public function generate($name, $data)
102
    {
103
        return $this->router->generate($name, $data);
104
    }
105
106
    /**
107
     * Set user input query to $globals or &$server
108
     *
109
     * @param array<string, array|string>                                                                                                        $query
110
     * @param array{_GET: array<string, string|array>, _POST: array<string, string|array>}                                                       $globals
111
     * @param array{CONTENT_TYPE?: string, HTTP_CONTENT_TYPE?: string, HTTP_RAW_POST_DATA?: string, REQUEST_METHOD: string, REQUEST_URI: string} $server
112
     *
113
     * @return array{
0 ignored issues
show
Documentation introduced by
The doc-type array{ could not be parsed: Unknown type name "array{" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
114
     *                array{_GET: array<string, string|array>, _POST: array<string, string|array>},
115
     *                array{CONTENT_TYPE?: string, HTTP_CONTENT_TYPE?: string, HTTP_RAW_POST_DATA?: string, REQUEST_METHOD: string, REQUEST_URI: string}
116
     *                }
117
     */
118
    private function addQuery(string $method, array $query, array $globals, array $server): array
119
    {
120
        if ($method === 'get') {
121
            $globals['_GET'] = $query;
122
123
            return [$globals, $server];
124
        }
125
126
        if ($method === 'post') {
127
            $globals['_POST'] = $query;
128
129
            return [$globals, $server];
130
        }
131
132
        $server = $this->getStdIn($method, $query, $server);
133
134
        return [$globals, $server];
135
    }
136
137
    private function error(string $command): void
138
    {
139
        $help = new CliRouterHelp(new OptionFactory());
140
        $this->stdIo->outln($help->getHelp($command));
141
    }
142
143
    /**
144
     * @SuppressWarnings(PHPMD)
145
     */
146
    private function terminate(int $status): void
147
    {
148
        if ($this->terminateException instanceof Exception) {
149
            throw $this->terminateException;
150
        }
151
152
        // @codeCoverageIgnoreStart
153
        exit($status);
154
        // @codeCoverageIgnoreEnd
155
    }
156
157
    /**
158
     * Return StdIn in PUT, PATCH or DELETE
159
     *
160
     * @param array<string, mixed>                                                                                                               $query
161
     * @param array{CONTENT_TYPE?: string, HTTP_CONTENT_TYPE?: string, HTTP_RAW_POST_DATA?: string, REQUEST_METHOD: string, REQUEST_URI: string} $server
162
     *
163
     * @return array{CONTENT_TYPE?: string, HTTP_CONTENT_TYPE?: string, HTTP_RAW_POST_DATA?: string, REQUEST_METHOD: string, REQUEST_URI: string}
0 ignored issues
show
Documentation introduced by
The doc-type array{CONTENT_TYPE?: could not be parsed: Unknown type name "array{CONTENT_TYPE?:" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
164
     */
165
    private function getStdIn(string $method, array $query, array $server): array
166
    {
167
        if ($method === 'put' || $method === 'patch' || $method === 'delete') {
168
            $server[HttpMethodParams::CONTENT_TYPE] = HttpMethodParams::FORM_URL_ENCODE;
169
            file_put_contents($this->stdIn, http_build_query($query));
170
171
            return $server;
172
        }
173
174
        return $server;
175
    }
176
177
    /**
178
     * @param array<int, string> $argv
179
     */
180
    private function validateArgs(int $argc, array $argv): void
181
    {
182
        if ($argc < 3) {
183
            $this->error(basename($argv[0]));
184
            $this->terminate(Status::USAGE);
185
        }
186
    }
187
188
    /**
189
     * Return $method, $query, $server from $server
190
     *
191
     * @param array{CONTENT_TYPE?: string, HTTP_CONTENT_TYPE?: string, HTTP_RAW_POST_DATA?: string, argv: array<int, string>} $server
192
     *
193
     * @return array{
0 ignored issues
show
Documentation introduced by
The doc-type array{ could not be parsed: Unknown type name "array{" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
194
     *                string,
195
     *                array<string, string|array>,
196
     *                array{CONTENT_TYPE?: string, HTTP_CONTENT_TYPE?: string, HTTP_RAW_POST_DATA?: string, REQUEST_METHOD: string, REQUEST_URI: string}
197
     *                }
198
     */
199
    private function parseServer(array $server): array
200
    {
201
        /** @var array{argv: array<string>} $server */
202
        [, $method, $uri] = $server['argv'];
0 ignored issues
show
Bug introduced by
The variable $method does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $uri does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
203
        $urlQuery = parse_url($uri, PHP_URL_QUERY);
204
        $urlPath = (string) parse_url($uri, PHP_URL_PATH);
205
        $query = [];
206
        if ($urlQuery) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $urlQuery of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
207
            parse_str($urlQuery, $query);
208
        }
209
210
        $server = [
211
            'REQUEST_METHOD' => strtoupper($method),
212
            'REQUEST_URI' => $urlPath,
213
        ];
214
215
        /** @var array<string, array|string> $query */
216
        return [$method, $query, $server];
217
    }
218
}
219