1<?php 2 3declare(strict_types=1); 4 5namespace GuzzleHttp\Psr7; 6 7use InvalidArgumentException; 8use Psr\Http\Message\RequestInterface; 9use Psr\Http\Message\StreamInterface; 10use Psr\Http\Message\UriInterface; 11 12/** 13 * PSR-7 request implementation. 14 */ 15class Request implements RequestInterface 16{ 17 use MessageTrait; 18 19 /** @var string */ 20 private $method; 21 22 /** @var string|null */ 23 private $requestTarget; 24 25 /** @var UriInterface */ 26 private $uri; 27 28 /** 29 * @param string $method HTTP method 30 * @param string|UriInterface $uri URI 31 * @param (string|string[])[] $headers Request headers 32 * @param string|resource|StreamInterface|null $body Request body 33 * @param string $version Protocol version 34 */ 35 public function __construct( 36 string $method, 37 $uri, 38 array $headers = [], 39 $body = null, 40 string $version = '1.1' 41 ) { 42 $this->assertMethod($method); 43 if (!($uri instanceof UriInterface)) { 44 $uri = new Uri($uri); 45 } 46 47 $this->method = strtoupper($method); 48 $this->uri = $uri; 49 $this->setHeaders($headers); 50 $this->protocol = $version; 51 52 if (!isset($this->headerNames['host'])) { 53 $this->updateHostFromUri(); 54 } 55 56 if ($body !== '' && $body !== null) { 57 $this->stream = Utils::streamFor($body); 58 } 59 } 60 61 public function getRequestTarget(): string 62 { 63 if ($this->requestTarget !== null) { 64 return $this->requestTarget; 65 } 66 67 $target = $this->uri->getPath(); 68 if ($target === '') { 69 $target = '/'; 70 } 71 if ($this->uri->getQuery() != '') { 72 $target .= '?'.$this->uri->getQuery(); 73 } 74 75 return $target; 76 } 77 78 public function withRequestTarget($requestTarget): RequestInterface 79 { 80 if (preg_match('#\s#', $requestTarget)) { 81 throw new InvalidArgumentException( 82 'Invalid request target provided; cannot contain whitespace' 83 ); 84 } 85 86 $new = clone $this; 87 $new->requestTarget = $requestTarget; 88 89 return $new; 90 } 91 92 public function getMethod(): string 93 { 94 return $this->method; 95 } 96 97 public function withMethod($method): RequestInterface 98 { 99 $this->assertMethod($method); 100 $new = clone $this; 101 $new->method = strtoupper($method); 102 103 return $new; 104 } 105 106 public function getUri(): UriInterface 107 { 108 return $this->uri; 109 } 110 111 public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface 112 { 113 if ($uri === $this->uri) { 114 return $this; 115 } 116 117 $new = clone $this; 118 $new->uri = $uri; 119 120 if (!$preserveHost || !isset($this->headerNames['host'])) { 121 $new->updateHostFromUri(); 122 } 123 124 return $new; 125 } 126 127 private function updateHostFromUri(): void 128 { 129 $host = $this->uri->getHost(); 130 131 if ($host == '') { 132 return; 133 } 134 135 if (($port = $this->uri->getPort()) !== null) { 136 $host .= ':'.$port; 137 } 138 139 if (isset($this->headerNames['host'])) { 140 $header = $this->headerNames['host']; 141 } else { 142 $header = 'Host'; 143 $this->headerNames['host'] = 'Host'; 144 } 145 // Ensure Host is the first header. 146 // See: https://datatracker.ietf.org/doc/html/rfc7230#section-5.4 147 $this->headers = [$header => [$host]] + $this->headers; 148 } 149 150 /** 151 * @param mixed $method 152 */ 153 private function assertMethod($method): void 154 { 155 if (!is_string($method) || $method === '') { 156 throw new InvalidArgumentException('Method must be a non-empty string.'); 157 } 158 } 159} 160