| 1 |
<?php |
| 2 |
|
| 3 |
namespace Http\Discovery; |
| 4 |
|
| 5 |
use Psr\Http\Message\RequestFactoryInterface; |
| 6 |
use Psr\Http\Message\RequestInterface; |
| 7 |
use Psr\Http\Message\ResponseFactoryInterface; |
| 8 |
use Psr\Http\Message\ResponseInterface; |
| 9 |
use Psr\Http\Message\ServerRequestFactoryInterface; |
| 10 |
use Psr\Http\Message\ServerRequestInterface; |
| 11 |
use Psr\Http\Message\StreamFactoryInterface; |
| 12 |
use Psr\Http\Message\StreamInterface; |
| 13 |
use Psr\Http\Message\UploadedFileFactoryInterface; |
| 14 |
use Psr\Http\Message\UploadedFileInterface; |
| 15 |
use Psr\Http\Message\UriFactoryInterface; |
| 16 |
use Psr\Http\Message\UriInterface; |
| 17 |
|
| 18 |
/** |
| 19 |
* A generic PSR-17 implementation. |
| 20 |
* |
| 21 |
* You can create this class with concrete factory instances or let |
| 22 |
* it use discovery to find suitable implementations as needed. |
| 23 |
* |
| 24 |
* This class also provides two additional methods that are not in PSR17, |
| 25 |
* to help with creating PSR-7 objects from PHP superglobals: |
| 26 |
* - createServerRequestFromGlobals() |
| 27 |
* - createUriFromGlobals() |
| 28 |
* |
| 29 |
* The code in this class is inspired by the "nyholm/psr7", "guzzlehttp/psr7" |
| 30 |
* and "symfony/http-foundation" packages, all licenced under MIT. |
| 31 |
* |
| 32 |
* Copyright (c) 2004-2023 Fabien Potencier <fabien@symfony.com> |
| 33 |
* Copyright (c) 2015 Michael Dowling <mtdowling@gmail.com> |
| 34 |
* Copyright (c) 2015 Márk Sági-Kazár <mark.sagikazar@gmail.com> |
| 35 |
* Copyright (c) 2015 Graham Campbell <hello@gjcampbell.co.uk> |
| 36 |
* Copyright (c) 2016 Tobias Schultze <webmaster@tubo-world.de> |
| 37 |
* Copyright (c) 2016 George Mponos <gmponos@gmail.com> |
| 38 |
* Copyright (c) 2016-2018 Tobias Nyholm <tobias.nyholm@gmail.com> |
| 39 |
* |
| 40 |
* @author Nicolas Grekas <p@tchwork.com> |
| 41 |
*/ |
| 42 |
class Psr17Factory implements RequestFactoryInterface, ResponseFactoryInterface, ServerRequestFactoryInterface, StreamFactoryInterface, UploadedFileFactoryInterface, UriFactoryInterface |
| 43 |
{ |
| 44 |
private $requestFactory; |
| 45 |
private $responseFactory; |
| 46 |
private $serverRequestFactory; |
| 47 |
private $streamFactory; |
| 48 |
private $uploadedFileFactory; |
| 49 |
private $uriFactory; |
| 50 |
|
| 51 |
public function __construct( |
| 52 |
?RequestFactoryInterface $requestFactory = null, |
| 53 |
?ResponseFactoryInterface $responseFactory = null, |
| 54 |
?ServerRequestFactoryInterface $serverRequestFactory = null, |
| 55 |
?StreamFactoryInterface $streamFactory = null, |
| 56 |
?UploadedFileFactoryInterface $uploadedFileFactory = null, |
| 57 |
?UriFactoryInterface $uriFactory = null |
| 58 |
) { |
| 59 |
$this->requestFactory = $requestFactory; |
| 60 |
$this->responseFactory = $responseFactory; |
| 61 |
$this->serverRequestFactory = $serverRequestFactory; |
| 62 |
$this->streamFactory = $streamFactory; |
| 63 |
$this->uploadedFileFactory = $uploadedFileFactory; |
| 64 |
$this->uriFactory = $uriFactory; |
| 65 |
|
| 66 |
$this->setFactory($requestFactory); |
| 67 |
$this->setFactory($responseFactory); |
| 68 |
$this->setFactory($serverRequestFactory); |
| 69 |
$this->setFactory($streamFactory); |
| 70 |
$this->setFactory($uploadedFileFactory); |
| 71 |
$this->setFactory($uriFactory); |
| 72 |
} |
| 73 |
|
| 74 |
/** |
| 75 |
* @param UriInterface|string $uri |
| 76 |
*/ |
| 77 |
public function createRequest(string $method, $uri): RequestInterface |
| 78 |
{ |
| 79 |
$factory = $this->requestFactory ?? $this->setFactory(Psr17FactoryDiscovery::findRequestFactory()); |
| 80 |
|
| 81 |
return $factory->createRequest(...\func_get_args()); |
| 82 |
} |
| 83 |
|
| 84 |
public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface |
| 85 |
{ |
| 86 |
$factory = $this->responseFactory ?? $this->setFactory(Psr17FactoryDiscovery::findResponseFactory()); |
| 87 |
|
| 88 |
return $factory->createResponse(...\func_get_args()); |
| 89 |
} |
| 90 |
|
| 91 |
/** |
| 92 |
* @param UriInterface|string $uri |
| 93 |
*/ |
| 94 |
public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface |
| 95 |
{ |
| 96 |
$factory = $this->serverRequestFactory ?? $this->setFactory(Psr17FactoryDiscovery::findServerRequestFactory()); |
| 97 |
|
| 98 |
return $factory->createServerRequest(...\func_get_args()); |
| 99 |
} |
| 100 |
|
| 101 |
public function createServerRequestFromGlobals(?array $server = null, ?array $get = null, ?array $post = null, ?array $cookie = null, ?array $files = null, ?StreamInterface $body = null): ServerRequestInterface |
| 102 |
{ |
| 103 |
$server = $server ?? $_SERVER; |
| 104 |
$request = $this->createServerRequest($server['REQUEST_METHOD'] ?? 'GET', $this->createUriFromGlobals($server), $server); |
| 105 |
|
| 106 |
return $this->buildServerRequestFromGlobals($request, $server, $files ?? $_FILES) |
| 107 |
->withQueryParams($get ?? $_GET) |
| 108 |
->withParsedBody($post ?? $_POST) |
| 109 |
->withCookieParams($cookie ?? $_COOKIE) |
| 110 |
->withBody($body ?? $this->createStreamFromFile('php://input', 'r+')); |
| 111 |
} |
| 112 |
|
| 113 |
public function createStream(string $content = ''): StreamInterface |
| 114 |
{ |
| 115 |
$factory = $this->streamFactory ?? $this->setFactory(Psr17FactoryDiscovery::findStreamFactory()); |
| 116 |
|
| 117 |
return $factory->createStream($content); |
| 118 |
} |
| 119 |
|
| 120 |
public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface |
| 121 |
{ |
| 122 |
$factory = $this->streamFactory ?? $this->setFactory(Psr17FactoryDiscovery::findStreamFactory()); |
| 123 |
|
| 124 |
return $factory->createStreamFromFile($filename, $mode); |
| 125 |
} |
| 126 |
|
| 127 |
/** |
| 128 |
* @param resource $resource |
| 129 |
*/ |
| 130 |
public function createStreamFromResource($resource): StreamInterface |
| 131 |
{ |
| 132 |
$factory = $this->streamFactory ?? $this->setFactory(Psr17FactoryDiscovery::findStreamFactory()); |
| 133 |
|
| 134 |
return $factory->createStreamFromResource($resource); |
| 135 |
} |
| 136 |
|
| 137 |
public function createUploadedFile(StreamInterface $stream, ?int $size = null, int $error = \UPLOAD_ERR_OK, ?string $clientFilename = null, ?string $clientMediaType = null): UploadedFileInterface |
| 138 |
{ |
| 139 |
$factory = $this->uploadedFileFactory ?? $this->setFactory(Psr17FactoryDiscovery::findUploadedFileFactory()); |
| 140 |
|
| 141 |
return $factory->createUploadedFile(...\func_get_args()); |
| 142 |
} |
| 143 |
|
| 144 |
public function createUri(string $uri = ''): UriInterface |
| 145 |
{ |
| 146 |
$factory = $this->uriFactory ?? $this->setFactory(Psr17FactoryDiscovery::findUriFactory()); |
| 147 |
|
| 148 |
return $factory->createUri(...\func_get_args()); |
| 149 |
} |
| 150 |
|
| 151 |
public function createUriFromGlobals(?array $server = null): UriInterface |
| 152 |
{ |
| 153 |
return $this->buildUriFromGlobals($this->createUri(''), $server ?? $_SERVER); |
| 154 |
} |
| 155 |
|
| 156 |
private function setFactory($factory) |
| 157 |
{ |
| 158 |
if (!$this->requestFactory && $factory instanceof RequestFactoryInterface) { |
| 159 |
$this->requestFactory = $factory; |
| 160 |
} |
| 161 |
if (!$this->responseFactory && $factory instanceof ResponseFactoryInterface) { |
| 162 |
$this->responseFactory = $factory; |
| 163 |
} |
| 164 |
if (!$this->serverRequestFactory && $factory instanceof ServerRequestFactoryInterface) { |
| 165 |
$this->serverRequestFactory = $factory; |
| 166 |
} |
| 167 |
if (!$this->streamFactory && $factory instanceof StreamFactoryInterface) { |
| 168 |
$this->streamFactory = $factory; |
| 169 |
} |
| 170 |
if (!$this->uploadedFileFactory && $factory instanceof UploadedFileFactoryInterface) { |
| 171 |
$this->uploadedFileFactory = $factory; |
| 172 |
} |
| 173 |
if (!$this->uriFactory && $factory instanceof UriFactoryInterface) { |
| 174 |
$this->uriFactory = $factory; |
| 175 |
} |
| 176 |
|
| 177 |
return $factory; |
| 178 |
} |
| 179 |
|
| 180 |
private function buildServerRequestFromGlobals(ServerRequestInterface $request, array $server, array $files): ServerRequestInterface |
| 181 |
{ |
| 182 |
$request = $request |
| 183 |
->withProtocolVersion(isset($server['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $server['SERVER_PROTOCOL']) : '1.1') |
| 184 |
->withUploadedFiles($this->normalizeFiles($files)); |
| 185 |
|
| 186 |
$headers = []; |
| 187 |
foreach ($server as $k => $v) { |
| 188 |
if (0 === strpos($k, 'HTTP_')) { |
| 189 |
$k = substr($k, 5); |
| 190 |
} elseif (!\in_array($k, ['CONTENT_TYPE', 'CONTENT_LENGTH', 'CONTENT_MD5'], true)) { |
| 191 |
continue; |
| 192 |
} |
| 193 |
$k = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $k)))); |
| 194 |
|
| 195 |
$headers[$k] = $v; |
| 196 |
} |
| 197 |
|
| 198 |
if (!isset($headers['Authorization'])) { |
| 199 |
if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) { |
| 200 |
$headers['Authorization'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; |
| 201 |
} elseif (isset($_SERVER['PHP_AUTH_USER'])) { |
| 202 |
$headers['Authorization'] = 'Basic '.base64_encode($_SERVER['PHP_AUTH_USER'].':'.($_SERVER['PHP_AUTH_PW'] ?? '')); |
| 203 |
} elseif (isset($_SERVER['PHP_AUTH_DIGEST'])) { |
| 204 |
$headers['Authorization'] = $_SERVER['PHP_AUTH_DIGEST']; |
| 205 |
} |
| 206 |
} |
| 207 |
|
| 208 |
foreach ($headers as $k => $v) { |
| 209 |
try { |
| 210 |
$request = $request->withHeader($k, $v); |
| 211 |
} catch (\InvalidArgumentException $e) { |
| 212 |
// ignore invalid headers |
| 213 |
} |
| 214 |
} |
| 215 |
|
| 216 |
return $request; |
| 217 |
} |
| 218 |
|
| 219 |
private function buildUriFromGlobals(UriInterface $uri, array $server): UriInterface |
| 220 |
{ |
| 221 |
$uri = $uri->withScheme(!empty($server['HTTPS']) && 'off' !== strtolower($server['HTTPS']) ? 'https' : 'http'); |
| 222 |
|
| 223 |
$hasPort = false; |
| 224 |
if (isset($server['HTTP_HOST'])) { |
| 225 |
$parts = parse_url('http://'.$server['HTTP_HOST']); |
| 226 |
|
| 227 |
$uri = $uri->withHost($parts['host'] ?? 'localhost'); |
| 228 |
|
| 229 |
if ($parts['port'] ?? false) { |
| 230 |
$hasPort = true; |
| 231 |
$uri = $uri->withPort($parts['port']); |
| 232 |
} |
| 233 |
} else { |
| 234 |
$uri = $uri->withHost($server['SERVER_NAME'] ?? $server['SERVER_ADDR'] ?? 'localhost'); |
| 235 |
} |
| 236 |
|
| 237 |
if (!$hasPort && isset($server['SERVER_PORT'])) { |
| 238 |
$uri = $uri->withPort($server['SERVER_PORT']); |
| 239 |
} |
| 240 |
|
| 241 |
$hasQuery = false; |
| 242 |
if (isset($server['REQUEST_URI'])) { |
| 243 |
$requestUriParts = explode('?', $server['REQUEST_URI'], 2); |
| 244 |
$uri = $uri->withPath($requestUriParts[0]); |
| 245 |
if (isset($requestUriParts[1])) { |
| 246 |
$hasQuery = true; |
| 247 |
$uri = $uri->withQuery($requestUriParts[1]); |
| 248 |
} |
| 249 |
} |
| 250 |
|
| 251 |
if (!$hasQuery && isset($server['QUERY_STRING'])) { |
| 252 |
$uri = $uri->withQuery($server['QUERY_STRING']); |
| 253 |
} |
| 254 |
|
| 255 |
return $uri; |
| 256 |
} |
| 257 |
|
| 258 |
private function normalizeFiles(array $files): array |
| 259 |
{ |
| 260 |
foreach ($files as $k => $v) { |
| 261 |
if ($v instanceof UploadedFileInterface) { |
| 262 |
continue; |
| 263 |
} |
| 264 |
if (!\is_array($v)) { |
| 265 |
unset($files[$k]); |
| 266 |
} elseif (!isset($v['tmp_name'])) { |
| 267 |
$files[$k] = $this->normalizeFiles($v); |
| 268 |
} else { |
| 269 |
$files[$k] = $this->createUploadedFileFromSpec($v); |
| 270 |
} |
| 271 |
} |
| 272 |
|
| 273 |
return $files; |
| 274 |
} |
| 275 |
|
| 276 |
/** |
| 277 |
* Create and return an UploadedFile instance from a $_FILES specification. |
| 278 |
* |
| 279 |
* @param array $value $_FILES struct |
| 280 |
* |
| 281 |
* @return UploadedFileInterface|UploadedFileInterface[] |
| 282 |
*/ |
| 283 |
private function createUploadedFileFromSpec(array $value) |
| 284 |
{ |
| 285 |
if (!is_array($tmpName = $value['tmp_name'])) { |
| 286 |
$file = is_file($tmpName) ? $this->createStreamFromFile($tmpName, 'r') : $this->createStream(); |
| 287 |
|
| 288 |
return $this->createUploadedFile($file, $value['size'], $value['error'], $value['name'], $value['type']); |
| 289 |
} |
| 290 |
|
| 291 |
foreach ($tmpName as $k => $v) { |
| 292 |
$tmpName[$k] = $this->createUploadedFileFromSpec([ |
| 293 |
'tmp_name' => $v, |
| 294 |
'size' => $value['size'][$k] ?? null, |
| 295 |
'error' => $value['error'][$k] ?? null, |
| 296 |
'name' => $value['name'][$k] ?? null, |
| 297 |
'type' => $value['type'][$k] ?? null, |
| 298 |
]); |
| 299 |
} |
| 300 |
|
| 301 |
return $tmpName; |
| 302 |
} |
| 303 |
} |
| 304 |
|