Completed
Push — master ( 78d4e7...979ecb )
by smiley
02:57
created

CurlClient::request()   B

Complexity

Conditions 9
Paths 16

Size

Total Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
nc 16
nop 5
dl 0
loc 31
rs 8.0555
c 0
b 0
f 0
1
<?php
2
/**
3
 * Class HTTPClient
4
 *
5
 * @filesource   HTTPClient.php
6
 * @created      27.08.2018
7
 * @package      chillerlan\HTTP
8
 * @author       smiley <[email protected]>
9
 * @copyright    2018 smiley
10
 * @license      MIT
11
 */
12
13
namespace chillerlan\HTTP;
14
15
use chillerlan\HTTP\Psr17\{RequestFactory, ResponseFactory, StreamFactory};
16
use chillerlan\HTTP\Psr7;
17
use chillerlan\Settings\SettingsContainerInterface;
18
use Psr\Http\Message\{RequestFactoryInterface, RequestInterface, ResponseFactoryInterface, ResponseInterface, StreamFactoryInterface};
19
use Http\Client\Exception\{NetworkException, RequestException};
20
21
class CurlClient implements HTTPClientInterface{
22
23
	/**
24
	 * @var \chillerlan\HTTP\HTTPOptions
25
	 */
26
	protected $options;
27
28
	/**
29
	 * @var \Psr\Http\Message\RequestFactoryInterface
30
	 */
31
	protected $requestFactory;
32
33
	/**
34
	 * @var \Psr\Http\Message\ResponseFactoryInterface
35
	 */
36
	protected $responseFactory;
37
38
	/**
39
	 * @var \Psr\Http\Message\StreamFactoryInterface
40
	 */
41
	protected $streamFactory;
42
43
	/**
44
	 * CurlClient constructor.
45
	 *
46
	 * @param \chillerlan\Settings\SettingsContainerInterface|null $options
47
	 * @param \Psr\Http\Message\RequestFactoryInterface|null       $requestFactory
48
	 * @param \Psr\Http\Message\ResponseFactoryInterface|null      $responseFactory
49
	 * @param \Psr\Http\Message\StreamFactoryInterface|null        $streamFactory
50
	 */
51
	public function __construct(
52
		SettingsContainerInterface $options = null,
53
		RequestFactoryInterface $requestFactory = null,
54
		ResponseFactoryInterface $responseFactory = null,
55
		StreamFactoryInterface $streamFactory = null
56
	){
57
		$this->options         = $options ?? new HTTPOptions;
0 ignored issues
show
Documentation Bug introduced by
$options ?? new \chillerlan\HTTP\HTTPOptions() is of type object<chillerlan\Settin...ingsContainerInterface>, but the property $options was declared to be of type object<chillerlan\HTTP\HTTPOptions>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
58
		$this->requestFactory  = $requestFactory ?? new RequestFactory;
59
		$this->responseFactory = $responseFactory ?? new ResponseFactory;
60
		$this->streamFactory   = $streamFactory ?? new StreamFactory;
61
	}
62
63
	/**
64
	 * Sends a PSR-7 request.
65
	 *
66
	 * @param \Psr\Http\Message\RequestInterface $request
67
	 *
68
	 * @return \Psr\Http\Message\ResponseInterface
69
	 *
70
	 * @throws \Http\Client\Exception If an error happens during processing the request.
71
	 * @throws \Exception             If processing the request is impossible (eg. bad configuration).
72
	 */
73
	public function sendRequest(RequestInterface $request):ResponseInterface{
74
		$handle = new CurlHandle($request, $this->responseFactory->createResponse(), $this->options);
75
		$handle->init();
76
77
		curl_exec($handle->ch);
78
79
		$errno = curl_errno($handle->ch);
80
81
		if($errno !== CURLE_OK){
82
			$error = curl_error($handle->ch);
83
84
			$network_errors = [
85
				CURLE_COULDNT_RESOLVE_PROXY,
86
				CURLE_COULDNT_RESOLVE_HOST,
87
				CURLE_COULDNT_CONNECT,
88
				CURLE_OPERATION_TIMEOUTED,
89
				CURLE_SSL_CONNECT_ERROR,
90
				CURLE_GOT_NOTHING,
91
			];
92
93
			if(in_array($errno, $network_errors, true)){
94
				throw new NetworkException($error, $request);
95
			}
96
97
			throw new RequestException($error, $request);
98
		}
99
100
		$handle->close();
101
		$handle->response->getBody()->rewind();
102
103
		return $handle->response;
104
105
	}
106
107
	/**
108
	 * @param string      $uri
109
	 * @param string|null $method
110
	 * @param array|null  $query
111
	 * @param mixed|null  $body
112
	 * @param array|null  $headers
113
	 *
114
	 * @return \Psr\Http\Message\ResponseInterface
115
	 */
116
	public function request(string $uri, string $method = null, array $query = null, $body = null, array $headers = null):ResponseInterface{
117
		$method    = strtoupper($method ?? 'GET');
118
		$headers   = Psr7\normalize_request_headers($headers);
0 ignored issues
show
Bug introduced by
It seems like $headers can also be of type null; however, chillerlan\HTTP\Psr7\normalize_request_headers() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
119
		$request   = $this->requestFactory->createRequest($method, Psr7\merge_query($uri, $query ?? []));
120
121
		if(in_array($method, ['DELETE', 'PATCH', 'POST', 'PUT'], true) && $body !== null){
122
123
			if(is_array($body) || is_object($body)){
124
125
				if(!isset($headers['Content-type'])){
126
					$headers['Content-type'] = 'application/x-www-form-urlencoded';
127
				}
128
129
				if($headers['Content-type'] === 'application/x-www-form-urlencoded'){
130
					$body = http_build_query($body, '', '&', PHP_QUERY_RFC1738);
131
				}
132
				elseif($headers['Content-type'] === 'application/json'){
133
					$body = json_encode($body);
134
				}
135
136
			}
137
138
			$request = $request->withBody($this->streamFactory->createStream((string)$body));
139
		}
140
141
		foreach($headers as $header => $value){
142
			$request = $request->withAddedHeader($header, $value);
143
		}
144
145
		return $this->sendRequest($request);
146
	}
147
148
}
149