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