Passed
Push — master ( 4ac306...d2aa89 )
by Mihail
05:30
created

ClientRequest::setHost()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 2
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 5
ccs 3
cts 3
cp 1
crap 2
rs 10
1
<?php
2
3
/*
4
 * This file is part of the Koded package.
5
 *
6
 * (c) Mihail Binev <[email protected]>
7
 *
8
 * Please view the LICENSE distributed with this source code
9
 * for the full copyright and license information.
10
 *
11
 */
12
13
namespace Koded\Http;
14
15
use InvalidArgumentException;
16
use JsonSerializable;
17
use Koded\Http\Interfaces\{HttpStatus, Request};
18
use Psr\Http\Message\{RequestInterface, UriInterface};
19
use function Koded\Stdlib\json_serialize;
20
21
22
class ClientRequest implements RequestInterface, JsonSerializable
23
{
24
    use HeaderTrait, MessageTrait, JsonSerializeTrait;
25
26
    const E_INVALID_REQUEST_TARGET = 'The request target is invalid, it contains whitespaces';
27
    const E_SAFE_METHODS_WITH_BODY = 'failed to open stream: you should not set the message body with safe HTTP methods';
28
29
    /** @var UriInterface */
30
    protected $uri;
31
    protected $method        = Request::GET;
32
    protected $requestTarget = '';
33
34
    /**
35
     * ClientRequest constructor.
36
     *
37
     * If body is provided, the content internally is encoded in JSON
38
     * and stored in body Stream object.
39
     *
40
     * @param string                                                                   $method
41
     * @param UriInterface|string                                                      $uri
42
     * @param \Psr\Http\Message\StreamInterface|iterable|resource|callable|string|null $body    [optional]
43
     * @param array                                                                    $headers [optional]
44
     */
45 129
    public function __construct(string $method, $uri, $body = null, array $headers = [])
46
    {
47 129
        $this->uri    = $uri instanceof UriInterface ? $uri : new Uri($uri);
48 129
        $this->stream = create_stream($this->prepareBody($body));
49
50 129
        $this->setHost();
51 129
        $this->setMethod($method, $this);
52 129
        $this->setHeaders($headers);
53 129
    }
54
55 36
    public function getMethod(): string
56
    {
57 36
        return strtoupper($this->method);
58
    }
59
60 11
    public function withMethod($method): ClientRequest
61
    {
62 11
        return $this->setMethod($method, clone $this);
63
    }
64
65 30
    public function getUri(): UriInterface
66
    {
67 30
        return $this->uri;
68
    }
69
70 16
    public function withUri(UriInterface $uri, $preserveHost = false): ClientRequest
71
    {
72 16
        $instance = clone $this;
73
74 16
        if (true === $preserveHost) {
75 3
            $uri      = $uri->withHost($this->uri->getHost());
76 3
            $instance = $instance->withUri($uri);
77
        } else {
78 16
            $instance->uri = $uri;
79
        }
80
81 16
        if (empty($instance->getHeader('host')) && $host = $uri->getHost()) {
82 2
            return $instance->withHeader('Host', $host);
83
        }
84
85 15
        return $instance->withHeader('Host', [$uri->getHost()]);
86
    }
87
88 4
    public function getRequestTarget(): string
89
    {
90 4
        if ($this->requestTarget) {
91 2
            return $this->requestTarget;
92
        }
93
94 3
        $path = $this->uri->getPath();
95
96 3
        if (!$path && !$this->requestTarget) {
97 2
            return '/';
98
        }
99
100 1
        if ($query = $this->uri->getQuery()) {
101 1
            $path .= '?' . $query;
102
        }
103
104 1
        return $path;
105
    }
106
107 3
    public function withRequestTarget($requestTarget): ClientRequest
108
    {
109 3
        if (preg_match('/\s+/', $requestTarget)) {
110 1
            throw new InvalidArgumentException(self::E_INVALID_REQUEST_TARGET, HttpStatus::BAD_REQUEST);
111
        }
112
113 2
        $instance                = clone $this;
114 2
        $instance->requestTarget = $requestTarget;
115
116 2
        return $instance;
117
    }
118
119 2
    public function getPath(): string
120
    {
121 2
        return str_replace($_SERVER['SCRIPT_NAME'], '', $this->uri->getPath()) ?: '/';
122
    }
123
124 2
    public function getBaseUri(): string
125
    {
126 2
        if (false === empty($host = $this->getUri()->getHost())) {
127 1
            $port = $this->getUri()->getPort();
128 1
            $port && $port = ":{$port}";
0 ignored issues
show
Bug Best Practice introduced by
The expression $port of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
129
130 1
            return $this->getUri()->getScheme() . "://{$host}{$port}";
131
        }
132
133 1
        return '';
134
    }
135
136 1
    public function isSecure(): bool
137
    {
138 1
        return 'https' === $this->uri->getScheme();
139
    }
140
141 76
    public function isSafeMethod(): bool
142
    {
143 76
        return in_array($this->method, Request::SAFE_METHODS);
144
    }
145
146 129
    protected function setHost(): void
147
    {
148 129
        $this->headersMap['host'] = 'Host';
149
150 129
        $this->headers = ['Host' => $this->uri->getHost() ?: $_SERVER['HTTP_HOST'] ?? ''] + $this->headers;
151 129
    }
152
153
    /**
154
     * @param string           $method The HTTP method
155
     * @param RequestInterface $instance
156
     *
157
     * @return self
158
     */
159 129
    protected function setMethod(string $method, RequestInterface $instance): RequestInterface
160
    {
161 129
        $instance->method = strtoupper($method);
0 ignored issues
show
Bug introduced by
Accessing method on the interface Psr\Http\Message\RequestInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
162
163 129
        return $instance;
164
    }
165
166
    /**
167
     * Checks if body is non-empty if HTTP method is one of the *safe* methods.
168
     * The consuming code may disallow this and return the response object.
169
     *
170
     * @return ServerResponse|null
171
     */
172 28
    protected function assertSafeMethod(): ?ServerResponse
173
    {
174 28
        if ($this->isSafeMethod() && $this->getBody()->getSize() > 0) {
175 2
            return new ServerResponse(self::E_SAFE_METHODS_WITH_BODY, HttpStatus::BAD_REQUEST);
176
        }
177
178 26
        return null;
179
    }
180
181
    /**
182
     * @param mixed $body
183
     *
184
     * @return mixed If $body is iterable returns JSON stringified body, or whatever it is
185
     */
186 129
    protected function prepareBody($body)
187
    {
188 129
        if (false === is_iterable($body)) {
189 126
            return $body;
190
        }
191
192 5
        return json_serialize($body);
193
    }
194
}
195