CliRouter::validateArgs()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 4
dl 0
loc 8
ccs 0
cts 0
cp 0
rs 10
c 1
b 0
f 0
cc 2
nc 2
nop 2
crap 6
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 15
 * }
42
 */
43 15
final class CliRouter implements RouterInterface
44 15
{
45 15
    private Stdio $stdIo;
46
    private Throwable|null $terminateException = null;
47
48
    public function __construct(
49
        #[Named('original')]
50 6
        private RouterInterface $router,
51
        Stdio|null $stdIo = null,
52 6
        #[StdIn]
53 6
        private string $stdIn = '',
54
    ) {
55 2
        $this->stdIo = $stdIo ?: (new CliFactory())->newStdio();
56
    }
57 2
58 2
    public function __destruct()
59
    {
60 2
        file_exists($this->stdIn) && unlink($this->stdIn);
61
    }
62 2
63 2
    public function __wakeup(): void
64
    {
65
        $this->stdIo = (new CliFactory())->newStdio();
66
    }
67
68
    public function setTerminateException(Throwable $e): void
69 15
    {
70
        $this->terminateException = $e;
71 15
    }
72 15
73
    /**
74
     * @psalm-api
75
     * @deprecated Use constructor injection
76
     * @codeCoverageIgnore
77 8
     */
78
    public function setStdIn(
79 8
        string $stdIn,
80 6
    ): void {
81 6
        $this->stdIn = $stdIn;
82
    }
83 6
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 1
     */
90
    #[Override]
91 1
    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 6
        [$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 6
        [$webGlobals, $webServer] = $this->addQuery($method, $query, $globals, $server); // @phpstan-ignore-line
100 1
101
        return $this->router->match($webGlobals, $webServer);
102 1
    }
103
104 5
    /**
105 1
     * {@inheritDoc}
106
     */
107 1
    #[Override]
108
    public function generate($name, $data)
109 4
    {
110 4
        return $this->router->generate($name, $data);
111
    }
112 2
113
    /**
114 2
     * Set user input query to $globals or &$server
115 2
     *
116 2
     * @param array<string, array<string, mixed>> $query
117
     * @param Globals                             $globals
118
     * @param Server                              $server
119
     *
120
     * @return array{0:Globals, 1:Server}
121 2
     */
122
    private function addQuery(string $method, array $query, array $globals, array $server): array
123 2
    {
124 2
        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 4
        }
135
136 4
        $server = $this->getStdIn($method, $query, $server);
137 3
138 3
        return [$globals, $server];
139
    }
140 3
141
    private function error(string $command): void
142
    {
143 1
        $help = new CliRouterHelp(new OptionFactory());
144
        $this->stdIo->outln($help->getHelp($command));
145
    }
146 8
147
    /** @SuppressWarnings(PHPMD) */
148 8
    private function terminate(int $status): void
149 2
    {
150 2
        if ($this->terminateException instanceof Exception) {
151
            throw $this->terminateException;
152 6
        }
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 6
158
    // @codeCoverageIgnoreEnd
159 6
160 6
    /**
161 6
     * Return StdIn in PUT, PATCH or DELETE
162 6
     *
163 5
     * @param  array<string, array<string, mixed>|string> $query
164
     * @param Server                                     $server
165
     *
166 6
     * @return Server
167 6
     */
168
    private function getStdIn(string $method, array $query, array $server): array
169
    {
170 6
        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