1 | <?php |
||
20 | class PSR7Client |
||
21 | { |
||
22 | /** @var Worker */ |
||
23 | private $worker; |
||
24 | |||
25 | /** @var ServerRequestFactoryInterface */ |
||
26 | private $requestFactory; |
||
27 | |||
28 | /** @var StreamFactoryInterface */ |
||
29 | private $streamFactory; |
||
30 | |||
31 | /*** @var UploadedFileFactoryInterface */ |
||
32 | private $uploadsFactory; |
||
33 | |||
34 | /** @var array Valid values for HTTP protocol version */ |
||
35 | private static $allowedVersions = ['1.0', '1.1', '2',]; |
||
36 | |||
37 | /** |
||
38 | * @param Worker $worker |
||
39 | * @param ServerRequestFactoryInterface|null $requestFactory |
||
40 | * @param StreamFactoryInterface|null $streamFactory |
||
41 | * @param UploadedFileFactoryInterface|null $uploadsFactory |
||
42 | */ |
||
43 | public function __construct( |
||
44 | Worker $worker, |
||
45 | ServerRequestFactoryInterface $requestFactory = null, |
||
46 | StreamFactoryInterface $streamFactory = null, |
||
47 | UploadedFileFactoryInterface $uploadsFactory = null |
||
48 | ) { |
||
49 | $this->worker = $worker; |
||
50 | $this->requestFactory = $requestFactory ?? new Diactoros\ServerRequestFactory(); |
||
51 | $this->streamFactory = $streamFactory ?? new Diactoros\StreamFactory(); |
||
52 | $this->uploadsFactory = $uploadsFactory ?? new Diactoros\UploadedFileFactory(); |
||
53 | } |
||
54 | |||
55 | /** |
||
56 | * @return Worker |
||
57 | */ |
||
58 | public function getWorker(): Worker |
||
59 | { |
||
60 | return $this->worker; |
||
61 | } |
||
62 | |||
63 | /** |
||
64 | * @return ServerRequestInterface|null |
||
65 | */ |
||
66 | public function acceptRequest() |
||
67 | { |
||
68 | $body = $this->worker->receive($ctx); |
||
69 | if (empty($body) && empty($ctx)) { |
||
70 | // termination request |
||
71 | return null; |
||
72 | } |
||
73 | |||
74 | if (empty($ctx = json_decode($ctx, true))) { |
||
75 | // invalid context |
||
76 | return null; |
||
77 | } |
||
78 | |||
79 | $_SERVER = $this->configureServer($ctx); |
||
80 | |||
81 | $request = $this->requestFactory->createServerRequest( |
||
82 | $ctx['method'], |
||
83 | $ctx['uri'], |
||
84 | $_SERVER |
||
85 | ); |
||
86 | |||
87 | parse_str($ctx['rawQuery'], $query); |
||
88 | |||
89 | $request = $request |
||
90 | ->withProtocolVersion(static::fetchProtocolVersion($ctx['protocol'])) |
||
|
|||
91 | ->withCookieParams($ctx['cookies']) |
||
92 | ->withQueryParams($query) |
||
93 | ->withUploadedFiles($this->wrapUploads($ctx['uploads'])); |
||
94 | |||
95 | foreach ($ctx['attributes'] as $name => $value) { |
||
96 | $request = $request->withAttribute($name, $value); |
||
97 | } |
||
98 | |||
99 | foreach ($ctx['headers'] as $name => $value) { |
||
100 | $request = $request->withHeader($name, $value); |
||
101 | } |
||
102 | |||
103 | if ($ctx['parsed']) { |
||
104 | $request = $request->withParsedBody(json_decode($body, true)); |
||
105 | } else { |
||
106 | if ($body !== null) { |
||
107 | $request = $request->withBody($this->streamFactory->createStream($body)); |
||
108 | } |
||
109 | } |
||
110 | |||
111 | return $request; |
||
112 | } |
||
113 | |||
114 | /** |
||
115 | * Send response to the application server. |
||
116 | * |
||
117 | * @param ResponseInterface $response |
||
118 | */ |
||
119 | public function respond(ResponseInterface $response) |
||
120 | { |
||
121 | $headers = $response->getHeaders(); |
||
122 | if (empty($headers)) { |
||
123 | // this is required to represent empty header set as map and not as array |
||
124 | $headers = new \stdClass(); |
||
125 | } |
||
126 | |||
127 | $this->worker->send($response->getBody(), json_encode([ |
||
128 | 'status' => $response->getStatusCode(), |
||
129 | 'headers' => $headers |
||
130 | ])); |
||
131 | } |
||
132 | |||
133 | /** |
||
134 | * Returns altered copy of _SERVER variable. Sets ip-address, |
||
135 | * request-time and other values. |
||
136 | * |
||
137 | * @param array $ctx |
||
138 | * @return array |
||
139 | */ |
||
140 | protected function configureServer(array $ctx): array |
||
141 | { |
||
142 | $server = $_SERVER; |
||
143 | $server['REQUEST_TIME'] = time(); |
||
144 | $server['REQUEST_TIME_FLOAT'] = microtime(true); |
||
145 | $server['REMOTE_ADDR'] = $ctx['attributes']['ipAddress'] ?? $ctx['remoteAddr'] ?? '127.0.0.1'; |
||
146 | $server['REMOTE_ADDR'] = $ctx['attributes']['ipAddress'] ?? $ctx['remoteAddr'] ?? '127.0.0.1'; |
||
147 | $server['HTTP_USER_AGENT'] = $ctx['headers']['User-Agent'][0] ?? ''; |
||
148 | |||
149 | return $server; |
||
150 | } |
||
151 | |||
152 | /** |
||
153 | * Wraps all uploaded files with UploadedFile. |
||
154 | * |
||
155 | * @param array $files |
||
156 | * |
||
157 | * @return array |
||
158 | */ |
||
159 | private function wrapUploads($files): array |
||
189 | |||
190 | /** |
||
191 | * Normalize HTTP protocol version to valid values |
||
192 | * |
||
193 | * @param string $version |
||
194 | * @return string |
||
195 | */ |
||
196 | private static function fetchProtocolVersion(string $version): string |
||
211 | } |
||
212 |
Let’s assume you have a class which uses late-static binding:
}
The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the
getSomeVariable()
on that sub-class, you will receive a runtime error:In the case above, it makes sense to update
SomeClass
to useself
instead: