1<?php 2 3namespace GuzzleHttp\Psr7; 4 5use InvalidArgumentException; 6use Psr\Http\Message\ServerRequestInterface; 7use Psr\Http\Message\StreamInterface; 8use Psr\Http\Message\UploadedFileInterface; 9use Psr\Http\Message\UriInterface; 10 11/** 12 * Server-side HTTP request 13 * 14 * Extends the Request definition to add methods for accessing incoming data, 15 * specifically server parameters, cookies, matched path parameters, query 16 * string arguments, body parameters, and upload file information. 17 * 18 * "Attributes" are discovered via decomposing the request (and usually 19 * specifically the URI path), and typically will be injected by the application. 20 * 21 * Requests are considered immutable; all methods that might change state are 22 * implemented such that they retain the internal state of the current 23 * message and return a new instance that contains the changed state. 24 */ 25class ServerRequest extends Request implements ServerRequestInterface 26{ 27 /** 28 * @var array 29 */ 30 private $attributes = []; 31 32 /** 33 * @var array 34 */ 35 private $cookieParams = []; 36 37 /** 38 * @var array|object|null 39 */ 40 private $parsedBody; 41 42 /** 43 * @var array 44 */ 45 private $queryParams = []; 46 47 /** 48 * @var array 49 */ 50 private $serverParams; 51 52 /** 53 * @var array 54 */ 55 private $uploadedFiles = []; 56 57 /** 58 * @param string $method HTTP method 59 * @param string|UriInterface $uri URI 60 * @param array $headers Request headers 61 * @param string|resource|StreamInterface|null $body Request body 62 * @param string $version Protocol version 63 * @param array $serverParams Typically the $_SERVER superglobal 64 */ 65 public function __construct( 66 $method, 67 $uri, 68 array $headers = [], 69 $body = null, 70 $version = '1.1', 71 array $serverParams = [] 72 ) { 73 $this->serverParams = $serverParams; 74 75 parent::__construct($method, $uri, $headers, $body, $version); 76 } 77 78 /** 79 * Return an UploadedFile instance array. 80 * 81 * @param array $files A array which respect $_FILES structure 82 * 83 * @return array 84 * 85 * @throws InvalidArgumentException for unrecognized values 86 */ 87 public static function normalizeFiles(array $files) 88 { 89 $normalized = []; 90 91 foreach ($files as $key => $value) { 92 if ($value instanceof UploadedFileInterface) { 93 $normalized[$key] = $value; 94 } elseif (is_array($value) && isset($value['tmp_name'])) { 95 $normalized[$key] = self::createUploadedFileFromSpec($value); 96 } elseif (is_array($value)) { 97 $normalized[$key] = self::normalizeFiles($value); 98 continue; 99 } else { 100 throw new InvalidArgumentException('Invalid value in files specification'); 101 } 102 } 103 104 return $normalized; 105 } 106 107 /** 108 * Create and return an UploadedFile instance from a $_FILES specification. 109 * 110 * If the specification represents an array of values, this method will 111 * delegate to normalizeNestedFileSpec() and return that return value. 112 * 113 * @param array $value $_FILES struct 114 * 115 * @return array|UploadedFileInterface 116 */ 117 private static function createUploadedFileFromSpec(array $value) 118 { 119 if (is_array($value['tmp_name'])) { 120 return self::normalizeNestedFileSpec($value); 121 } 122 123 return new UploadedFile( 124 $value['tmp_name'], 125 (int) $value['size'], 126 (int) $value['error'], 127 $value['name'], 128 $value['type'] 129 ); 130 } 131 132 /** 133 * Normalize an array of file specifications. 134 * 135 * Loops through all nested files and returns a normalized array of 136 * UploadedFileInterface instances. 137 * 138 * @param array $files 139 * 140 * @return UploadedFileInterface[] 141 */ 142 private static function normalizeNestedFileSpec(array $files = []) 143 { 144 $normalizedFiles = []; 145 146 foreach (array_keys($files['tmp_name']) as $key) { 147 $spec = [ 148 'tmp_name' => $files['tmp_name'][$key], 149 'size' => $files['size'][$key], 150 'error' => $files['error'][$key], 151 'name' => $files['name'][$key], 152 'type' => $files['type'][$key], 153 ]; 154 $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec); 155 } 156 157 return $normalizedFiles; 158 } 159 160 /** 161 * Return a ServerRequest populated with superglobals: 162 * $_GET 163 * $_POST 164 * $_COOKIE 165 * $_FILES 166 * $_SERVER 167 * 168 * @return ServerRequestInterface 169 */ 170 public static function fromGlobals() 171 { 172 $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET'; 173 $headers = getallheaders(); 174 $uri = self::getUriFromGlobals(); 175 $body = new CachingStream(new LazyOpenStream('php://input', 'r+')); 176 $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1'; 177 178 $serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER); 179 180 return $serverRequest 181 ->withCookieParams($_COOKIE) 182 ->withQueryParams($_GET) 183 ->withParsedBody($_POST) 184 ->withUploadedFiles(self::normalizeFiles($_FILES)); 185 } 186 187 private static function extractHostAndPortFromAuthority($authority) 188 { 189 $uri = 'http://' . $authority; 190 $parts = parse_url($uri); 191 if (false === $parts) { 192 return [null, null]; 193 } 194 195 $host = isset($parts['host']) ? $parts['host'] : null; 196 $port = isset($parts['port']) ? $parts['port'] : null; 197 198 return [$host, $port]; 199 } 200 201 /** 202 * Get a Uri populated with values from $_SERVER. 203 * 204 * @return UriInterface 205 */ 206 public static function getUriFromGlobals() 207 { 208 $uri = new Uri(''); 209 210 $uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http'); 211 212 $hasPort = false; 213 if (isset($_SERVER['HTTP_HOST'])) { 214 list($host, $port) = self::extractHostAndPortFromAuthority($_SERVER['HTTP_HOST']); 215 if ($host !== null) { 216 $uri = $uri->withHost($host); 217 } 218 219 if ($port !== null) { 220 $hasPort = true; 221 $uri = $uri->withPort($port); 222 } 223 } elseif (isset($_SERVER['SERVER_NAME'])) { 224 $uri = $uri->withHost($_SERVER['SERVER_NAME']); 225 } elseif (isset($_SERVER['SERVER_ADDR'])) { 226 $uri = $uri->withHost($_SERVER['SERVER_ADDR']); 227 } 228 229 if (!$hasPort && isset($_SERVER['SERVER_PORT'])) { 230 $uri = $uri->withPort($_SERVER['SERVER_PORT']); 231 } 232 233 $hasQuery = false; 234 if (isset($_SERVER['REQUEST_URI'])) { 235 $requestUriParts = explode('?', $_SERVER['REQUEST_URI'], 2); 236 $uri = $uri->withPath($requestUriParts[0]); 237 if (isset($requestUriParts[1])) { 238 $hasQuery = true; 239 $uri = $uri->withQuery($requestUriParts[1]); 240 } 241 } 242 243 if (!$hasQuery && isset($_SERVER['QUERY_STRING'])) { 244 $uri = $uri->withQuery($_SERVER['QUERY_STRING']); 245 } 246 247 return $uri; 248 } 249 250 /** 251 * {@inheritdoc} 252 */ 253 public function getServerParams() 254 { 255 return $this->serverParams; 256 } 257 258 /** 259 * {@inheritdoc} 260 */ 261 public function getUploadedFiles() 262 { 263 return $this->uploadedFiles; 264 } 265 266 /** 267 * {@inheritdoc} 268 */ 269 public function withUploadedFiles(array $uploadedFiles) 270 { 271 $new = clone $this; 272 $new->uploadedFiles = $uploadedFiles; 273 274 return $new; 275 } 276 277 /** 278 * {@inheritdoc} 279 */ 280 public function getCookieParams() 281 { 282 return $this->cookieParams; 283 } 284 285 /** 286 * {@inheritdoc} 287 */ 288 public function withCookieParams(array $cookies) 289 { 290 $new = clone $this; 291 $new->cookieParams = $cookies; 292 293 return $new; 294 } 295 296 /** 297 * {@inheritdoc} 298 */ 299 public function getQueryParams() 300 { 301 return $this->queryParams; 302 } 303 304 /** 305 * {@inheritdoc} 306 */ 307 public function withQueryParams(array $query) 308 { 309 $new = clone $this; 310 $new->queryParams = $query; 311 312 return $new; 313 } 314 315 /** 316 * {@inheritdoc} 317 */ 318 public function getParsedBody() 319 { 320 return $this->parsedBody; 321 } 322 323 /** 324 * {@inheritdoc} 325 */ 326 public function withParsedBody($data) 327 { 328 $new = clone $this; 329 $new->parsedBody = $data; 330 331 return $new; 332 } 333 334 /** 335 * {@inheritdoc} 336 */ 337 public function getAttributes() 338 { 339 return $this->attributes; 340 } 341 342 /** 343 * {@inheritdoc} 344 */ 345 public function getAttribute($attribute, $default = null) 346 { 347 if (false === array_key_exists($attribute, $this->attributes)) { 348 return $default; 349 } 350 351 return $this->attributes[$attribute]; 352 } 353 354 /** 355 * {@inheritdoc} 356 */ 357 public function withAttribute($attribute, $value) 358 { 359 $new = clone $this; 360 $new->attributes[$attribute] = $value; 361 362 return $new; 363 } 364 365 /** 366 * {@inheritdoc} 367 */ 368 public function withoutAttribute($attribute) 369 { 370 if (false === array_key_exists($attribute, $this->attributes)) { 371 return $this; 372 } 373 374 $new = clone $this; 375 unset($new->attributes[$attribute]); 376 377 return $new; 378 } 379} 380