Passed
Push — master ( bf6fa9...817f88 )
by Nikolaos
06:58
created

RequestTrait::processMethod()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 25
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 17
nc 2
nop 1
dl 0
loc 25
rs 9.7
c 0
b 0
f 0
ccs 0
cts 19
cp 0
crap 20
1
<?php
2
3
/**
4
 * This file is part of the Phalcon Framework.
5
 *
6
 * (c) Phalcon Team <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE.txt
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Phalcon\Http\Message\Traits;
15
16
use Phalcon\Collection;
17
use Phalcon\Http\Message\Exception\InvalidArgumentException;
18
use Phalcon\Http\Message\Uri;
19
use Psr\Http\Message\UriInterface;
20
21
use function is_string;
22
use function preg_match;
23
24
/**
25
 * Representation of an outgoing, client-side request.
26
 *
27
 * Per the HTTP specification, this interface includes properties for
28
 * each of the following:
29
 *
30
 * - Protocol version
31
 * - HTTP method
32
 * - URI
33
 * - Headers
34
 * - Message body
35
 *
36
 * During construction, implementations MUST attempt to set the Host header from
37
 * a provided URI if no Host header is provided.
38
 *
39
 * Requests are considered immutable; all methods that might change state MUST
40
 * be implemented such that they retain the internal state of the current
41
 * message and return an instance that contains the changed state.
42
 *
43
 * @property Collection   $headers
44
 * @property string       $method
45
 * @property null|string  $requestTarget
46
 * @property UriInterface $uri
47
 */
48
trait RequestTrait
49
{
50
    /**
51
     * @var Collection
52
     */
53
    private $headers;
54
55
    /**
56
     * Retrieves the HTTP method of the request.
57
     *
58
     * @var string
59
     */
60
    private $method = 'GET';
61
62
    /**
63
     * The request-target, if it has been provided or calculated.
64
     *
65
     * @var null|string
66
     */
67
    private $requestTarget;
68
69
    /**
70
     * Retrieves the URI instance.
71
     *
72
     * This method MUST return a UriInterface instance.
73
     *
74
     * @see http://tools.ietf.org/html/rfc3986#section-4.3
75
     *
76
     * @var UriInterface
77
     */
78
    private $uri;
79
80
    /**
81
     * Return the current method
82
     *
83
     * @return string
84
     */
85
    public function getMethod(): string
86
    {
87
        return $this->method;
88
    }
89
90
    /**
91
     * Retrieves the message's request target.
92
     *
93
     * Retrieves the message's request-target either as it will appear (for
94
     * clients), as it appeared at request (for servers), or as it was
95
     * specified for the instance (see withRequestTarget()).
96
     *
97
     * In most cases, this will be the origin-form of the composed URI, unless a
98
     * value was provided to the concrete implementation (see
99
     * withRequestTarget() below).
100
     *
101
     * @return string
102
     */
103
    public function getRequestTarget(): string
104
    {
105
        $requestTarget = $this->requestTarget;
106
107
        if (null === $requestTarget) {
108
            $requestTarget = $this->uri->getPath();
109
110
            if (true !== empty($this->uri->getQuery())) {
111
                $requestTarget .= '?' . $this->uri->getQuery();
112
            }
113
114
            if (empty($requestTarget)) {
115
                $requestTarget = '/';
116
            }
117
        }
118
119
        return $requestTarget;
120
    }
121
122
    /**
123
     * Returns the current Uri
124
     *
125
     * @return UriInterface
126
     */
127
    public function getUri(): UriInterface
128
    {
129
        return $this->uri;
130
    }
131
132
    /**
133
     * Return an instance with the provided HTTP method.
134
     *
135
     * While HTTP method names are typically all uppercase characters, HTTP
136
     * method names are case-sensitive and thus implementations SHOULD NOT
137
     * modify the given string.
138
     *
139
     * This method MUST be implemented in such a way as to retain the
140
     * immutability of the message, and MUST return an instance that has the
141
     * changed request method.
142
     *
143
     * @param string $method
144
     *
145
     * @return self
146
     * @throws InvalidArgumentException for invalid HTTP methods.
147
     *
148
     */
149
    public function withMethod($method): self
150
    {
151
        $this->processMethod($method);
152
153
        return $this->cloneInstance($method, 'method');
154
    }
155
156
    /**
157
     * Return an instance with the specific request-target.
158
     *
159
     * If the request needs a non-origin-form request-target — e.g., for
160
     * specifying an absolute-form, authority-form, or asterisk-form —
161
     * this method may be used to create an instance with the specified
162
     * request-target, verbatim.
163
     *
164
     * This method MUST be implemented in such a way as to retain the
165
     * immutability of the message, and MUST return an instance that has the
166
     * changed request target.
167
     *
168
     * @see http://tools.ietf.org/html/rfc7230#section-5.3 (for the various
169
     *     request-target forms allowed in request messages)
170
     *
171
     * @param mixed $requestTarget
172
     *
173
     * @return self
174
     */
175
    public function withRequestTarget($requestTarget): self
176
    {
177
        if (preg_match('/\s/', $requestTarget)) {
178
            throw new InvalidArgumentException(
179
                'Invalid request target: cannot contain whitespace'
180
            );
181
        }
182
183
        return $this->cloneInstance($requestTarget, 'requestTarget');
184
    }
185
186
    /**
187
     * Returns an instance with the provided URI.
188
     *
189
     * This method MUST update the Host header of the returned request by
190
     * default if the URI contains a host component. If the URI does not
191
     * contain a host component, any pre-existing Host header MUST be carried
192
     * over to the returned request.
193
     *
194
     * You can opt-in to preserving the original state of the Host header by
195
     * setting `$preserveHost` to `true`. When `$preserveHost` is set to
196
     * `true`, this method interacts with the Host header in the following
197
     * ways:
198
     *
199
     * - If the Host header is missing or empty, and the new URI contains
200
     *   a host component, this method MUST update the Host header in the
201
     *   returned request.
202
     * - If the Host header is missing or empty, and the new URI does not
203
     * contain a host component, this method MUST NOT update the Host header in
204
     * the returned request.
205
     * - If a Host header is present and non-empty, this method MUST NOT update
206
     *   the Host header in the returned request.
207
     *
208
     * This method MUST be implemented in such a way as to retain the
209
     * immutability of the message, and MUST return an instance that has the
210
     * new UriInterface instance.
211
     *
212
     * @see http://tools.ietf.org/html/rfc3986#section-4.3
213
     *
214
     * @param UriInterface $uri
215
     * @param bool         $preserveHost
216
     *
217
     * @return self
218
     */
219
    public function withUri(UriInterface $uri, $preserveHost = false): self
220
    {
221
        $preserveHost     = (bool) $preserveHost;
222
        $headers          = clone $this->headers;
223
        $newInstance      = clone $this;
224
        $newInstance->uri = $uri;
225
226
        if (true !== $preserveHost) {
227
            $headers = $this->checkHeaderHost($headers);
228
229
            $newInstance->headers = $headers;
230
        }
231
232
        return $newInstance;
233
    }
234
235
    abstract protected function checkHeaderHost(Collection $collection): Collection;
236
237
    abstract protected function cloneInstance($element, string $property);
238
239
    /**
240
     * Check the method
241
     *
242
     * @param string $method
243
     *
244
     * @return string
245
     */
246
    private function processMethod($method = ''): string
247
    {
248
        $methods = [
249
            'GET'     => 1,
250
            'CONNECT' => 1,
251
            'DELETE'  => 1,
252
            'HEAD'    => 1,
253
            'OPTIONS' => 1,
254
            'PATCH'   => 1,
255
            'POST'    => 1,
256
            'PUT'     => 1,
257
            'TRACE'   => 1,
258
        ];
259
260
        if (
261
        !(!empty($method) &&
262
            is_string($method) &&
263
            isset($methods[$method]))
264
        ) {
265
            throw new InvalidArgumentException(
266
                'Invalid or unsupported method ' . $method
267
            );
268
        }
269
270
        return $method;
271
    }
272
273
    /**
274
     * Sets a valid Uri
275
     *
276
     * @param mixed $uri
277
     *
278
     * @return UriInterface
279
     */
280
    private function processUri($uri): UriInterface
281
    {
282
        if ($uri instanceof UriInterface) {
283
            return $uri;
284
        }
285
286
        if (is_string($uri)) {
287
            return new Uri($uri);
288
        }
289
290
        if (null === $uri) {
291
            return new Uri();
292
        }
293
294
        throw new InvalidArgumentException(
295
            'Invalid uri passed as a parameter'
296
        );
297
    }
298
}
299