Issues (36)

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

95
        [$method, $query, $server] = $this->parseServer(/** @scrutinizer ignore-type */ $server);
Loading history...
96
        /** @psalm-suppress MixedArgumentTypeCoercion */
97 6
        [$webGlobals, $webServer] = $this->addQuery($method, $query, $globals, $server); // @phpstan-ignore-line
98
99 6
        return $this->router->match($webGlobals, $webServer);
100 1
    }
101
102 1
    /**
103
     * {@inheritDoc}
104 5
     */
105 1
    public function generate($name, $data)
106
    {
107 1
        return $this->router->generate($name, $data);
108
    }
109 4
110 4
    /**
111
     * Set user input query to $globals or &$server
112 2
     *
113
     * @param array<string, array<string, mixed>> $query
114 2
     * @param Globals                             $globals
115 2
     * @param Server                              $server
116 2
     *
117
     * @return array{0:Globals, 1:Server}
118
     */
119
    private function addQuery(string $method, array $query, array $globals, array $server): array
120
    {
121 2
        if ($method === 'get') {
122
            $globals['_GET'] = $query;
123 2
124 2
            return [$globals, $server];
125
        }
126
127
        if ($method === 'post') {
128
            $globals['_POST'] = $query;
129
130
            return [$globals, $server];
131
        }
132
133
        $server = $this->getStdIn($method, $query, $server);
134 4
135
        return [$globals, $server];
136 4
    }
137 3
138 3
    private function error(string $command): void
139
    {
140 3
        $help = new CliRouterHelp(new OptionFactory());
141
        $this->stdIo->outln($help->getHelp($command));
142
    }
143 1
144
    /** @SuppressWarnings(PHPMD) */
145
    private function terminate(int $status): void
146 8
    {
147
        if ($this->terminateException instanceof Exception) {
148 8
            throw $this->terminateException;
149 2
        }
150 2
151
        // @codeCoverageIgnoreStart
152 6
        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...
153
    }
154
155
    // @codeCoverageIgnoreEnd
156
157 6
    /**
158
     * Return StdIn in PUT, PATCH or DELETE
159 6
     *
160 6
     * @param  array<string, array<string, mixed>|string> $query
161 6
     * @param Server                                     $server
162 6
     *
163 5
     * @return Server
164
     */
165
    private function getStdIn(string $method, array $query, array $server): array
166 6
    {
167 6
        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 6
171
            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...
172
        }
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
    /** @param array<int, string> $argv */
178
    private function validateArgs(int $argc, array $argv): void
179
    {
180
        if ($argc >= 3) {
181
            return;
182
        }
183
184
        $this->error(basename($argv[0]));
185
        $this->terminate(Status::USAGE);
186
        // @codeCoverageIgnoreStart
187
    }
188
189
    // @codeCoverageIgnoreEnd
190
191
    /**
192
     * Return $method, $query, $server from $server
193
     *
194
     * @param Server $server
195
     *
196
     * @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...
197
     */
198
    private function parseServer(array $server): array
199
    {
200
        /** @var array{argv: array<string>} $server */
201
        [, $method, $uri] = $server['argv'];
202
        $urlQuery = (string) parse_url($uri, PHP_URL_QUERY);
203
        $urlPath = (string) parse_url($uri, PHP_URL_PATH);
204
        $query = [];
205
        if ($urlQuery !== '') {
206
            parse_str($urlQuery, $query);
207
        }
208
209
        $server = [
210
            'REQUEST_METHOD' => strtoupper($method),
211
            'REQUEST_URI' => $urlPath,
212
        ];
213
214
        /** @var array<string, array<mixed>|string> $query */
215
        return [$method, $query, $server];
216
    }
217
}
218