1<?php 2 3namespace GuzzleHttp\Psr7; 4 5use InvalidArgumentException; 6use Psr\Http\Message\ServerRequestInterface; 7use Psr\Http\Message\UriInterface; 8use Psr\Http\Message\StreamInterface; 9use Psr\Http\Message\UploadedFileInterface; 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 null|array|object 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|null|resource|StreamInterface $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 * @return array|UploadedFileInterface 115 */ 116 private static function createUploadedFileFromSpec(array $value) 117 { 118 if (is_array($value['tmp_name'])) { 119 return self::normalizeNestedFileSpec($value); 120 } 121 122 return new UploadedFile( 123 $value['tmp_name'], 124 (int) $value['size'], 125 (int) $value['error'], 126 $value['name'], 127 $value['type'] 128 ); 129 } 130 131 /** 132 * Normalize an array of file specifications. 133 * 134 * Loops through all nested files and returns a normalized array of 135 * UploadedFileInterface instances. 136 * 137 * @param array $files 138 * @return UploadedFileInterface[] 139 */ 140 private static function normalizeNestedFileSpec(array $files = []) 141 { 142 $normalizedFiles = []; 143 144 foreach (array_keys($files['tmp_name']) as $key) { 145 $spec = [ 146 'tmp_name' => $files['tmp_name'][$key], 147 'size' => $files['size'][$key], 148 'error' => $files['error'][$key], 149 'name' => $files['name'][$key], 150 'type' => $files['type'][$key], 151 ]; 152 $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec); 153 } 154 155 return $normalizedFiles; 156 } 157 158 /** 159 * Return a ServerRequest populated with superglobals: 160 * $_GET 161 * $_POST 162 * $_COOKIE 163 * $_FILES 164 * $_SERVER 165 * 166 * @return ServerRequestInterface 167 */ 168 public static function fromGlobals() 169 { 170 $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET'; 171 $headers = getallheaders(); 172 $uri = self::getUriFromGlobals(); 173 $body = new CachingStream(new LazyOpenStream('php://input', 'r+')); 174 $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1'; 175 176 $serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER); 177 178 return $serverRequest 179 ->withCookieParams($_COOKIE) 180 ->withQueryParams($_GET) 181 ->withParsedBody($_POST) 182 ->withUploadedFiles(self::normalizeFiles($_FILES)); 183 } 184 185 private static function extractHostAndPortFromAuthority($authority) 186 { 187 $uri = 'http://'.$authority; 188 $parts = parse_url($uri); 189 if (false === $parts) { 190 return [null, null]; 191 } 192 193 $host = isset($parts['host']) ? $parts['host'] : null; 194 $port = isset($parts['port']) ? $parts['port'] : null; 195 196 return [$host, $port]; 197 } 198 199 /** 200 * Get a Uri populated with values from $_SERVER. 201 * 202 * @return UriInterface 203 */ 204 public static function getUriFromGlobals() 205 { 206 $uri = new Uri(''); 207 208 $uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http'); 209 210 $hasPort = false; 211 if (isset($_SERVER['HTTP_HOST'])) { 212 list($host, $port) = self::extractHostAndPortFromAuthority($_SERVER['HTTP_HOST']); 213 if ($host !== null) { 214 $uri = $uri->withHost($host); 215 } 216 217 if ($port !== null) { 218 $hasPort = true; 219 $uri = $uri->withPort($port); 220 } 221 } elseif (isset($_SERVER['SERVER_NAME'])) { 222 $uri = $uri->withHost($_SERVER['SERVER_NAME']); 223 } elseif (isset($_SERVER['SERVER_ADDR'])) { 224 $uri = $uri->withHost($_SERVER['SERVER_ADDR']); 225 } 226 227 if (!$hasPort && isset($_SERVER['SERVER_PORT'])) { 228 $uri = $uri->withPort($_SERVER['SERVER_PORT']); 229 } 230 231 $hasQuery = false; 232 if (isset($_SERVER['REQUEST_URI'])) { 233 $requestUriParts = explode('?', $_SERVER['REQUEST_URI'], 2); 234 $uri = $uri->withPath($requestUriParts[0]); 235 if (isset($requestUriParts[1])) { 236 $hasQuery = true; 237 $uri = $uri->withQuery($requestUriParts[1]); 238 } 239 } 240 241 if (!$hasQuery && isset($_SERVER['QUERY_STRING'])) { 242 $uri = $uri->withQuery($_SERVER['QUERY_STRING']); 243 } 244 245 return $uri; 246 } 247 248 249 /** 250 * {@inheritdoc} 251 */ 252 public function getServerParams() 253 { 254 return $this->serverParams; 255 } 256 257 /** 258 * {@inheritdoc} 259 */ 260 public function getUploadedFiles() 261 { 262 return $this->uploadedFiles; 263 } 264 265 /** 266 * {@inheritdoc} 267 */ 268 public function withUploadedFiles(array $uploadedFiles) 269 { 270 $new = clone $this; 271 $new->uploadedFiles = $uploadedFiles; 272 273 return $new; 274 } 275 276 /** 277 * {@inheritdoc} 278 */ 279 public function getCookieParams() 280 { 281 return $this->cookieParams; 282 } 283 284 /** 285 * {@inheritdoc} 286 */ 287 public function withCookieParams(array $cookies) 288 { 289 $new = clone $this; 290 $new->cookieParams = $cookies; 291 292 return $new; 293 } 294 295 /** 296 * {@inheritdoc} 297 */ 298 public function getQueryParams() 299 { 300 return $this->queryParams; 301 } 302 303 /** 304 * {@inheritdoc} 305 */ 306 public function withQueryParams(array $query) 307 { 308 $new = clone $this; 309 $new->queryParams = $query; 310 311 return $new; 312 } 313 314 /** 315 * {@inheritdoc} 316 */ 317 public function getParsedBody() 318 { 319 return $this->parsedBody; 320 } 321 322 /** 323 * {@inheritdoc} 324 */ 325 public function withParsedBody($data) 326 { 327 $new = clone $this; 328 $new->parsedBody = $data; 329 330 return $new; 331 } 332 333 /** 334 * {@inheritdoc} 335 */ 336 public function getAttributes() 337 { 338 return $this->attributes; 339 } 340 341 /** 342 * {@inheritdoc} 343 */ 344 public function getAttribute($attribute, $default = null) 345 { 346 if (false === array_key_exists($attribute, $this->attributes)) { 347 return $default; 348 } 349 350 return $this->attributes[$attribute]; 351 } 352 353 /** 354 * {@inheritdoc} 355 */ 356 public function withAttribute($attribute, $value) 357 { 358 $new = clone $this; 359 $new->attributes[$attribute] = $value; 360 361 return $new; 362 } 363 364 /** 365 * {@inheritdoc} 366 */ 367 public function withoutAttribute($attribute) 368 { 369 if (false === array_key_exists($attribute, $this->attributes)) { 370 return $this; 371 } 372 373 $new = clone $this; 374 unset($new->attributes[$attribute]); 375 376 return $new; 377 } 378} 379