Passed
Pull Request — master (#3)
by lee
01:38
created

Client   A

Complexity

Total Complexity 14

Size/Duplication

Total Lines 139
Duplicated Lines 0 %

Test Coverage

Coverage 94%

Importance

Changes 0
Metric Value
wmc 14
eloc 46
dl 0
loc 139
rs 10
c 0
b 0
f 0
ccs 47
cts 50
cp 0.94

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
B sendRequest() 0 40 7
A padCurlOptions() 0 21 4
A getCurlOptions() 0 3 1
A setCurlOptions() 0 3 1
1
<?php declare(strict_types=1);
2
3
/**
4
 * It's free open-source software released under the MIT License.
5
 *
6
 * @author Anatoly Fenric <[email protected]>
7
 * @copyright Copyright (c) 2018, Anatoly Fenric
8
 * @license https://github.com/sunrise-php/http-client-curl/blob/master/LICENSE
9
 * @link https://github.com/sunrise-php/http-client-curl
10
 */
11
12
namespace Sunrise\Http\Client\Curl;
13
14
/**
15
 * Import classes
16
 */
17
use Psr\Http\Client\ClientInterface;
18
use Psr\Http\Message\RequestInterface;
19
use Psr\Http\Message\ResponseInterface;
20
use Psr\Http\Message\ResponseFactoryInterface;
21
use Psr\Http\Message\StreamFactoryInterface;
22
use Sunrise\Http\Client\Curl\Exception\ClientException;
23
use Sunrise\Http\Client\Curl\Exception\NetworkException;
24
25
/**
26
 * Import functions
27
 */
28
use function curl_close;
29
use function curl_errno;
30
use function curl_error;
31
use function curl_exec;
32
use function curl_getinfo;
33
use function curl_init;
34
use function curl_setopt_array;
35
use function explode;
36
use function sprintf;
37
use function strpos;
38
use function substr;
39
use function trim;
40
41
/**
42
 * Import constants
43
 */
44
use const CURLINFO_HEADER_SIZE;
45
use const CURLINFO_RESPONSE_CODE;
46
use const CURLOPT_CUSTOMREQUEST;
47
use const CURLOPT_HEADER;
48
use const CURLOPT_HTTPHEADER;
49
use const CURLOPT_POSTFIELDS;
50
use const CURLOPT_RETURNTRANSFER;
51
use const CURLOPT_URL;
52
53
/**
54
 * HTTP Client based on CURL
55
 *
56
 * @link http://php.net/manual/en/intro.curl.php
57
 * @link https://curl.haxx.se/libcurl/c/libcurl-errors.html
58
 * @link https://www.php-fig.org/psr/psr-2/
59
 * @link https://www.php-fig.org/psr/psr-7/
60
 * @link https://www.php-fig.org/psr/psr-17/
61
 * @link https://www.php-fig.org/psr/psr-18/
62
 */
63
class Client implements ClientInterface
64
{
65
66
    /**
67
     * Response Factory
68
     *
69
     * @var ResponseFactoryInterface
70
     */
71
    protected $responseFactory;
72
73
    /**
74
     * Stream Factory
75
     *
76
     * @var StreamFactoryInterface
77
     */
78
    protected $streamFactory;
79
80
    /**
81
     * CURL options
82
     *
83
     * @var array
84
     */
85
    protected $curlOptions;
86
87
    /**
88
     * Constructor of the class
89
     *
90
     * @param ResponseFactoryInterface $responseFactory
91
     * @param StreamFactoryInterface $streamFactory
92
     * @param array $curlOptions
93
     */
94 5
    public function __construct(
95
        ResponseFactoryInterface $responseFactory,
96
        StreamFactoryInterface $streamFactory,
97
        array $curlOptions = []
98
    ) {
99 5
        $this->responseFactory = $responseFactory;
100 5
        $this->streamFactory = $streamFactory;
101 5
        $this->curlOptions = $curlOptions;
102 5
    }
103
104
    /**
105
     * {@inheritDoc}
106
     *
107
     * @throws ClientException
108
     * @throws NetworkException
109
     */
110 2
    public function sendRequest(RequestInterface $request) : ResponseInterface
111
    {
112 2
        $handle = curl_init();
113 2
        if (false === $handle) {
114
            throw new ClientException('Unable to initialize a cURL session');
115
        }
116
117 2
        $options = $this->padCurlOptions($request);
118 2
        if (false === curl_setopt_array($handle, $options)) {
119
            throw new ClientException('Unable to configure a cURL session');
120
        }
121
122 2
        $result = curl_exec($handle);
123 2
        if (false === $result) {
124 1
            throw new NetworkException($request, curl_error($handle), curl_errno($handle));
125
        }
126
127 1
        $responseStatusCode = curl_getinfo($handle, CURLINFO_RESPONSE_CODE);
128 1
        $responseHeadersPartSize = curl_getinfo($handle, CURLINFO_HEADER_SIZE);
129 1
        $responseHeadersPart = substr($result, 0, $responseHeadersPartSize);
130 1
        $responseBodyPart = substr($result, $responseHeadersPartSize);
131
132 1
        $response = $this->responseFactory->createResponse($responseStatusCode)
133 1
        ->withBody($this->streamFactory->createStream($responseBodyPart));
134
135 1
        foreach (explode("\n", $responseHeadersPart) as $header) {
136 1
            $colonPosition = strpos($header, ':');
137
138 1
            if (false === $colonPosition) { // Status Line
139 1
                continue;
140 1
            } elseif (0 === $colonPosition) { // HTTP/2 Field
141
                continue;
142
            }
143
144 1
            list($name, $value) = explode(':', $header, 2);
145 1
            $response = $response->withAddedHeader(trim($name), trim($value));
146
        }
147
148 1
        curl_close($handle);
149 1
        return $response;
150
    }
151
152
    /**
153
     * Manipulate $curlOptions array
154
     *
155
     * @param array $curlOptions
156
     *
157
     * @return void
158
     */
159 1
    public function setCurlOptions(array $curlOptions) : void
160
    {
161 1
        $this->curlOptions = $this->curlOptions + $curlOptions;
162 1
    }
163
164
    /**
165
     * Get protected $curlOptions array
166
     *
167
     * @return array
168
     */
169 2
    public function getCurlOptions() : array
170
    {
171 2
        return $this->curlOptions;
172
    }
173
174
    /**
175
     * Supplements options for a cURL session from the given request message
176
     *
177
     * @param RequestInterface $request
178
     *
179
     * @return array
180
     */
181 2
    protected function padCurlOptions(RequestInterface $request) : array
182
    {
183 2
        $options = $this->curlOptions;
184
185 2
        $options[CURLOPT_RETURNTRANSFER] = $options[CURLOPT_RETURNTRANSFER] ?? true;
186 2
        $options[CURLOPT_HEADER]         = $options[CURLOPT_HEADER] ?? true;
187 2
        $options[CURLOPT_CUSTOMREQUEST]  = $options[CURLOPT_CUSTOMREQUEST] ?? $request->getMethod();
188 2
        $options[CURLOPT_URL]            = $options[CURLOPT_URL] ?? (string) $request->getUri();
189 2
        $options[CURLOPT_POSTFIELDS]     = $options[CURLOPT_POSTFIELDS] ?? (string) $request->getBody();
190 2
        $options[CURLOPT_HTTPHEADER]     = $options[CURLOPT_HTTPHEADER] ?? [];
191
192 2
        foreach ($request->getHeaders() as $name => $values) {
193 1
            foreach ($values as $value) {
194 1
                $header = sprintf('%s: %s', $name, $value);
195 1
                if (!in_array($header, $options[CURLOPT_HTTPHEADER])) {
196 1
                    $options[CURLOPT_HTTPHEADER][] = $header;
197
                }
198
            }
199
        }
200
201 2
        return $options;
202
    }
203
}
204