Passed
Push — master ( 8a04f9...43fdea )
by Terry
01:45
created

src/Psr7/Request.php (1 issue)

Severity
1
<?php 
2
/*
3
 * This file is part of the Shieldon package.
4
 *
5
 * (c) Terry L. <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
declare(strict_types=1);
12
13
namespace Shieldon\Psr7;
14
15
use Psr\Http\Message\RequestInterface;
16
use Psr\Http\Message\StreamInterface;
17
use Psr\Http\Message\UriInterface;
18
use Shieldon\Psr7\Message;
19
use Shieldon\Psr7\Uri;
20
use InvalidArgumentException;
21
22
use function in_array;
23
use function is_string;
24
use function preg_match;
25
use function sprintf;
26
use function strtoupper;
27
28
/*
29
 * Representation of an outgoing, client-side request.
30
 */
31
class Request extends Message implements RequestInterface
32
{
33
    /**
34
     * The HTTP method of the outgoing request.
35
     *
36
     * @var string
37
     */
38
    protected $method;
39
40
    /**
41
     * The target URL of the outgoing request.
42
     *
43
     * @var string
44
     */
45
    protected $requestTarget;
46
47
    /**
48
     * A UriInterface object.
49
     *
50
     * @var UriInterface
51
     */
52
    protected $uri;
53
54
55
    /**
56
     * https://tools.ietf.org/html/rfc7231
57
     *
58
     * @var array
59
     */
60
    protected $validMethods = [
61
62
        // The HEAD method asks for a response identical to that of a GET
63
        // request, but without the response body.
64
        'HEAD',
65
66
        // The GET method requests a representation of the specified 
67
        // resource. Requests using GET should only retrieve data.
68
        'GET',
69
70
        // The POST method is used to submit an entity to the specified 
71
        // resource, often causing a change in state or side effects on the
72
        // server.
73
        'POST', 
74
        
75
        // The PUT method replaces all current representations of the target
76
        // resource with the request payload.
77
        'PUT', 
78
79
        // The DELETE method deletes the specified resource.
80
        'DELETE',
81
82
        // The PATCH method is used to apply partial modifications to a 
83
        // resource.
84
        'PATCH',
85
86
        // The CONNECT method establishes a tunnel to the server identified
87
        // by the target resource.
88
        'CONNECT',
89
90
        //The OPTIONS method is used to describe the communication options
91
        // for the target resource.
92
        'OPTIONS',
93
94
        // The TRACE method performs a message loop-back test along the
95
        // path to the target resource.
96
        'TRACE',
97
    ];
98
99
    /**
100
     * Request constructor.
101
     *
102
     * @param string                 $method  Request HTTP method
103
     * @param string|UriInterface    $uri     Request URI
104
     * @param string|StreamInterface $body    Request body - see setBody()
105
     * @param array                  $headers Request headers
106
     * @param string                 $version Request protocol version
107
     */
108
    public function __construct(
109
        string $method  = 'GET',
110
               $uri     = ''   ,
111
               $body    = ''   ,
112
        array  $headers = []   ,
113
        string $version = '1.1'
114
    ) {
115
        $this->method = $method;
116
117
        $this->assertMethod($method);
118
  
119
        $this->assertProtocolVersion($version);
120
        $this->protocolVersion = $version;
121
122
        if ($uri instanceof UriInterface) {
123
            $this->uri = $uri;
124
125
        } elseif (is_string($uri)) {
126
            $this->uri = new Uri($uri);
127
128
        } else {
129
            throw new InvalidArgumentException(
130
                sprintf(
131
                    'URI should be a string or an instance of UriInterface, but "%s" provided.',
132
                    gettype($uri)
133
                )
134
            );
135
        }
136
137
        $this->setBody($body);
138
        $this->setHeaders($headers);
139
    }
140
141
    /**
142
     * {@inheritdoc}
143
     */
144
    public function getRequestTarget(): string
145
    {
146
        if ($this->requestTarget) {
147
            return $this->requestTarget;
148
        }
149
150
        $path = $this->uri->getPath();
151
        $query = $this->uri->getQuery();
152
153
        if (empty($path)) {
154
            $path = '/';
155
        }
156
157
        if (!empty($query)) {
158
            $path .= '?' . $query;
159
        }
160
161
        return $path;
162
    }
163
164
    /**
165
     * {@inheritdoc}
166
     */
167
    public function withRequestTarget($requestTarget)
168
    {
169
        if (!is_string($requestTarget)) {
170
            throw new InvalidArgumentException(
171
                'A request target must be a string.'
172
            );
173
        }
174
175
        if (preg_match('/\s/', $requestTarget)) {
176
            throw new InvalidArgumentException(
177
                'A request target cannot contain any whitespace.'
178
            );
179
        }
180
181
        $clone = clone $this;
182
        $clone->requestTarget = $requestTarget;
183
184
        return $clone;
185
    }
186
187
    /**
188
     * {@inheritdoc}
189
     */
190
    public function getMethod()
191
    {
192
        return $this->method;
193
    }
194
195
    /**
196
     * {@inheritdoc}
197
     */
198
    public function withMethod($method)
199
    {
200
        $this->assertMethod($method);
201
202
        $clone = clone $this;
203
        $clone->method = $method;
204
205
        return $clone;
206
    }
207
208
    /**
209
     * {@inheritdoc}
210
     */
211
    public function getUri(): UriInterface
212
    {
213
        return $this->uri;
214
    }
215
216
    /**
217
     * {@inheritdoc}
218
     */
219
    public function withUri(UriInterface $uri, $preserveHost = false)
220
    {
221
        $host = $uri->getHost();
222
223
        $clone = clone $this;
224
        $clone->uri = $uri;
225
226
        if (
227
            // This method MUST update the Host header of the returned request by
228
            // default if the URI contains a host component.
229
            (!$preserveHost && $host !== '') ||
230
231
            // When `$preserveHost` is set to `true`.
232
            // If the Host header is missing or empty, and the new URI contains
233
            // a host component, this method MUST update the Host header in the returned
234
            // request.
235
            ($preserveHost && !$this->hasHeader('Host') && $host !== '')
236
        ) {
237
            $headers = $this->getHeaders();
238
            $headers['host'] = $host;
239
            $clone->setHeaders($headers);
240
        }
241
242
        return $clone;
243
    }
244
245
    /*
246
    |--------------------------------------------------------------------------
247
    | Non PSR-7 Methods.
248
    |--------------------------------------------------------------------------
249
    */
250
251
    /**
252
     * Check out whether a method defined in RFC 7231 request methods.
253
     *
254
     * @param string $method Http methods
255
     * 
256
     * @return void
257
     * 
258
     * @throws InvalidArgumentException
259
     */
260
    protected function assertMethod($method): void
261
    {
262
        if (!is_string($method)) {
0 ignored issues
show
The condition is_string($method) is always true.
Loading history...
263
            throw new InvalidArgumentException(
264
                sprintf(
265
                    'HTTP method must be a string.',
266
                    $method
267
                )
268
            );
269
        }
270
271
        if (!in_array(strtoupper($this->method), $this->validMethods)) {
272
            throw new InvalidArgumentException(
273
                sprintf(
274
                    'Unsupported HTTP method. It must be compatible with RFC-7231 request method, but "%s" provided.',
275
                    $method
276
                )
277
            );
278
        }
279
    }
280
}