Issues (74)

src/Provide/Router/CliRouter.php (9 issues)

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\Package\Types;
13
use BEAR\Sunday\Extension\Router\RouterInterface;
14
use Exception;
15
use Override;
16
use Ray\Di\Di\Named;
17
use Throwable;
18
19
use function basename;
20
use function file_exists;
21
use function file_put_contents;
22
use function http_build_query;
23
use function parse_str;
24
use function parse_url;
25
use function strtoupper;
26
use function unlink;
27
28
use const PHP_URL_PATH;
29
use const PHP_URL_QUERY;
30
31
/**
32
 * @psalm-import-type Globals from RouterInterface
33
 * @psalm-import-type Server from RouterInterface
34
 * @psalm-import-type QueryParams from Types
35
 * @psalm-type CliServer = array{
36
 *     argc: int,
37
 *     argv: array<int, string>,
38
 *     REQUEST_URI: string,
39
 *     REQUEST_METHOD: string,
40
 *     CONTENT_TYPE?: string,
41
 *     HTTP_CONTENT_TYPE?: string,
42
 *     HTTP_RAW_POST_DATA?: string
43
 * }
44
 */
45
final class CliRouter implements RouterInterface
46
{
47
    private Stdio $stdIo;
48
    private Throwable|null $terminateException = null;
49
50
    public function __construct(
51
        #[Named('original')]
52
        private RouterInterface $router,
53
        Stdio|null $stdIo = null,
54
        #[StdIn]
55
        private string $stdIn = '',
56
    ) {
57
        $this->stdIo = $stdIo ?: (new CliFactory())->newStdio();
58
    }
59
60
    public function __destruct()
61
    {
62
        file_exists($this->stdIn) && unlink($this->stdIn);
63
    }
64
65
    public function __wakeup(): void
66
    {
67
        $this->stdIo = (new CliFactory())->newStdio();
68
    }
69
70
    public function setTerminateException(Throwable $e): void
71
    {
72
        $this->terminateException = $e;
73
    }
74
75
    /**
76
     * @psalm-api
77
     * @deprecated Use constructor injection
78
     * @codeCoverageIgnore
79
     */
80
    public function setStdIn(
81
        string $stdIn,
82
    ): void {
83
        $this->stdIn = $stdIn;
84
    }
85
86
    /**
87
     * {@inheritDoc}
88
     *
89
     * @param Globals $globals
0 ignored issues
show
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...
90
     * @param Server  $server
0 ignored issues
show
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...
91
     */
92
    #[Override]
93
    public function match(array $globals, array $server)
94
    {
95
        /** @var CliServer $server */
96
        $this->validateArgs($server['argc'], $server['argv']);
97
        // covert console $_SERVER to web $_SERVER $GLOBALS
98
        /** @psalm-suppress InvalidArgument */
99
        [$method, $query, $server] = $this->parseServer($server);
0 ignored issues
show
$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

99
        [$method, $query, $server] = $this->parseServer(/** @scrutinizer ignore-type */ $server);
Loading history...
100
        /** @psalm-suppress MixedArgumentTypeCoercion */
101
        [$webGlobals, $webServer] = $this->addQuery($method, $query, $globals, $server);
0 ignored issues
show
$query of type BEAR\Package\Provide\Router\QueryParams is incompatible with the type array expected by parameter $query of BEAR\Package\Provide\Router\CliRouter::addQuery(). ( Ignorable by Annotation )

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

101
        [$webGlobals, $webServer] = $this->addQuery($method, /** @scrutinizer ignore-type */ $query, $globals, $server);
Loading history...
102
103
        return $this->router->match($webGlobals, $webServer);
104
    }
105
106
    /**
107
     * {@inheritDoc}
108
     */
109
    #[Override]
110
    public function generate($name, $data)
111
    {
112
        return $this->router->generate($name, $data);
113
    }
114
115
    /**
116
     * Set user input query to $globals or &$server
117
     *
118
     * @param QueryParams $query
0 ignored issues
show
The type BEAR\Package\Provide\Router\QueryParams 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...
119
     * @param Globals     $globals
120
     * @param Server      $server
121
     *
122
     * @return array{0:Globals, 1:Server}
123
     */
124
    private function addQuery(string $method, array $query, array $globals, array $server): array
125
    {
126
        if ($method === 'get') {
127
            $globals['_GET'] = $query;
128
129
            return [$globals, $server];
130
        }
131
132
        if ($method === 'post') {
133
            $globals['_POST'] = $query;
134
135
            return [$globals, $server];
136
        }
137
138
        $server = $this->getStdIn($method, $query, $server);
139
140
        return [$globals, $server];
141
    }
142
143
    private function error(string $command): void
144
    {
145
        $help = new CliRouterHelp(new OptionFactory());
146
        $this->stdIo->outln($help->getHelp($command));
147
    }
148
149
    /** @SuppressWarnings(PHPMD) */
150
    private function terminate(int $status): void
151
    {
152
        if ($this->terminateException instanceof Exception) {
153
            throw $this->terminateException;
154
        }
155
156
        // @codeCoverageIgnoreStart
157
        exit($status);
0 ignored issues
show
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...
158
    }
159
160
    // @codeCoverageIgnoreEnd
161
162
    /**
163
     * Return StdIn in PUT, PATCH or DELETE
164
     *
165
     * @param QueryParams $query
166
     * @param Server      $server
167
     *
168
     * @return Server
169
     */
170
    private function getStdIn(string $method, array $query, array $server): array
171
    {
172
        if ($method === 'put' || $method === 'patch' || $method === 'delete') {
173
            $server[HttpMethodParams::CONTENT_TYPE] = HttpMethodParams::FORM_URL_ENCODE;
174
            file_put_contents($this->stdIn, http_build_query($query));
175
176
            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...
177
        }
178
179
        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...
180
    }
181
182
    /** @param array<int, string> $argv */
183
    private function validateArgs(int $argc, array $argv): void
184
    {
185
        if ($argc >= 3) {
186
            return;
187
        }
188
189
        $this->error(basename($argv[0]));
190
        $this->terminate(Status::USAGE);
191
        // @codeCoverageIgnoreStart
192
    }
193
194
    // @codeCoverageIgnoreEnd
195
196
    /**
197
     * Return $method, $query, $server from $server
198
     *
199
     * @param Server $server
200
     *
201
     * @return array{string, QueryParams, Server}
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{string, QueryParams, Server} at position 2 could not be parsed: Expected ':' at position 2, but found 'string'.
Loading history...
202
     */
203
    private function parseServer(array $server): array
204
    {
205
        /** @var array{argv: array<string>} $server */
206
        [, $method, $uri] = $server['argv'];
207
        $urlQuery = (string) parse_url($uri, PHP_URL_QUERY);
208
        $urlPath = (string) parse_url($uri, PHP_URL_PATH);
209
        $query = [];
210
        if ($urlQuery !== '') {
211
            parse_str($urlQuery, $query);
212
        }
213
214
        $server = [
215
            'REQUEST_METHOD' => strtoupper($method),
216
            'REQUEST_URI' => $urlPath,
217
        ];
218
219
        /** @var QueryParams $query */
220
        return [$method, $query, $server];
221
    }
222
}
223