Passed
Push — master ( 9cee35...fab5c8 )
by Anatoly
01:42
created

Client::extendCurlOptions()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 11
nc 3
nop 1
dl 0
loc 18
ccs 12
cts 12
cp 1
crap 3
rs 9.9
c 0
b 0
f 0
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_exec;
31
use function curl_getinfo;
32
use function curl_init;
33
use function curl_setopt_array;
34
use function explode;
35
use function sprintf;
36
use function strpos;
37
use function substr;
38
use function trim;
39
40
/**
41
 * Import constants
42
 */
43
use const CURLINFO_HEADER_SIZE;
44
use const CURLINFO_RESPONSE_CODE;
45
use const CURLOPT_CUSTOMREQUEST;
46
use const CURLOPT_HEADER;
47
use const CURLOPT_HTTPHEADER;
48
use const CURLOPT_POSTFIELDS;
49
use const CURLOPT_RETURNTRANSFER;
50
use const CURLOPT_URL;
51
52
/**
53
 * HTTP Client based on CURL
54
 *
55
 * @link http://php.net/manual/en/intro.curl.php
56
 * @link https://curl.haxx.se/libcurl/c/libcurl-errors.html
57
 * @link https://www.php-fig.org/psr/psr-2/
58
 * @link https://www.php-fig.org/psr/psr-7/
59
 * @link https://www.php-fig.org/psr/psr-17/
60
 * @link https://www.php-fig.org/psr/psr-18/
61
 */
62
class Client implements ClientInterface
63
{
64
65
    /**
66
     * Response Factory
67
     *
68
     * @var ResponseFactoryInterface
69
     */
70
    protected $responseFactory;
71
72
    /**
73
     * Stream Factory
74
     *
75
     * @var StreamFactoryInterface
76
     */
77
    protected $streamFactory;
78
79
    /**
80
     * CURL options
81
     *
82
     * @var array
83
     */
84
    protected $curlOptions;
85
86
    /**
87
     * Constructor of the class
88
     *
89
     * @param ResponseFactoryInterface $responseFactory
90
     * @param StreamFactoryInterface $streamFactory
91
     * @param array $curlOptions
92
     */
93 3
    public function __construct(
94
        ResponseFactoryInterface $responseFactory,
95
        StreamFactoryInterface $streamFactory,
96
        array $curlOptions = []
97
    ) {
98 3
        $this->responseFactory = $responseFactory;
99 3
        $this->streamFactory = $streamFactory;
100 3
        $this->curlOptions = $curlOptions;
101 3
    }
102
103
    /**
104
     * {@inheritDoc}
105
     *
106
     * @throws ClientException
107
     * @throws NetworkException
108
     */
109 2
    public function sendRequest(RequestInterface $request) : ResponseInterface
110
    {
111 2
        $handle = curl_init();
112 2
        if (false === $handle) {
113
            throw new ClientException('Unable to initialize a cURL session');
114
        }
115
116 2
        $options = $this->extendCurlOptions($request);
117 2
        if (false === curl_setopt_array($handle, $options)) {
118
            throw new ClientException('Unable to configure a cURL session');
119
        }
120
121 2
        $result = curl_exec($handle);
122 2
        if (false === $result) {
123 1
            throw new NetworkException($request, curl_error($handle), curl_errno($handle));
124
        }
125
126 1
        $responseStatusCode = curl_getinfo($handle, CURLINFO_RESPONSE_CODE);
127 1
        $responseHeadersPartSize = curl_getinfo($handle, CURLINFO_HEADER_SIZE);
128 1
        $responseHeadersPart = substr($result, 0, $responseHeadersPartSize);
129 1
        $responseBodyPart = substr($result, $responseHeadersPartSize);
130
131 1
        $response = $this->responseFactory->createResponse($responseStatusCode)
132 1
        ->withBody($this->streamFactory->createStream($responseBodyPart));
133
134 1
        foreach (explode("\n", $responseHeadersPart) as $header) {
135 1
            $colonPosition = strpos($header, ':');
136
137 1
            if (false === $colonPosition) { // Status Line
138 1
                continue;
139 1
            } elseif (0 === $colonPosition) { // HTTP/2 Field
140
                continue;
141
            }
142
143 1
            list($name, $value) = explode(':', $header, 2);
144 1
            $response = $response->withAddedHeader(trim($name), trim($value));
145
        }
146
147 1
        curl_close($handle);
148 1
        return $response;
149
    }
150
151
    /**
152
     * Supplements options for a cURL session from the given request message
153
     *
154
     * @param RequestInterface $request
155
     *
156
     * @return array
157
     */
158 2
    protected function extendCurlOptions(RequestInterface $request) : array
159
    {
160 2
        $options = $this->curlOptions;
161
162 2
        $options[CURLOPT_RETURNTRANSFER] = true;
163 2
        $options[CURLOPT_HEADER]         = true;
164 2
        $options[CURLOPT_CUSTOMREQUEST]  = $request->getMethod();
165 2
        $options[CURLOPT_URL]            = (string) $request->getUri();
166 2
        $options[CURLOPT_POSTFIELDS]     = (string) $request->getBody();
167
168 2
        foreach ($request->getHeaders() as $name => $values) {
169 1
            foreach ($values as $value) {
170 1
                $header = sprintf('%s: %s', $name, $value);
171 1
                $options[CURLOPT_HTTPHEADER][] = $header;
172
            }
173
        }
174
175 2
        return $options;
176
    }
177
}
178