Completed
Push — master ( 651da6...722d0e )
by smiley
01:42
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\Settings\SettingsContainerInterface;
17
use Psr\Http\Message\{RequestFactoryInterface, RequestInterface, ResponseFactoryInterface, ResponseInterface, StreamFactoryInterface};
18
use Http\Client\Exception\{NetworkException, RequestException};
19
20
class CurlClient implements HTTPClientInterface{
21
22
	/**
23
	 * @var \chillerlan\HTTP\HTTPOptions
24
	 */
25
	protected $options;
26
27
	/**
28
	 * @var \Psr\Http\Message\RequestFactoryInterface
29
	 */
30
	protected $requestFactory;
31
32
	/**
33
	 * @var \Psr\Http\Message\ResponseFactoryInterface
34
	 */
35
	protected $responseFactory;
36
37
	/**
38
	 * @var \Psr\Http\Message\StreamFactoryInterface
39
	 */
40
	protected $streamFactory;
41
42
	/**
43
	 * CurlClient constructor.
44
	 *
45
	 * @param \chillerlan\Settings\SettingsContainerInterface|null $options
46
	 * @param \Psr\Http\Message\RequestFactoryInterface|null       $requestFactory
47
	 * @param \Psr\Http\Message\ResponseFactoryInterface|null      $responseFactory
48
	 * @param \Psr\Http\Message\StreamFactoryInterface|null        $streamFactory
49
	 */
50
	public function __construct(
51
		SettingsContainerInterface $options = null,
52
		RequestFactoryInterface $requestFactory = null,
53
		ResponseFactoryInterface $responseFactory = null,
54
		StreamFactoryInterface $streamFactory = null
55
	){
56
		$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...
57
		$this->requestFactory  = $requestFactory ?? new RequestFactory;
58
		$this->responseFactory = $responseFactory ?? new ResponseFactory;
59
		$this->streamFactory   = $streamFactory ?? new StreamFactory;
60
	}
61
62
	/**
63
	 * Sends a PSR-7 request.
64
	 *
65
	 * @param \Psr\Http\Message\RequestInterface $request
66
	 *
67
	 * @return \Psr\Http\Message\ResponseInterface
68
	 *
69
	 * @throws \Http\Client\Exception If an error happens during processing the request.
70
	 * @throws \Exception             If processing the request is impossible (eg. bad configuration).
71
	 */
72
	public function sendRequest(RequestInterface $request):ResponseInterface{
73
		$handle = new CurlHandle($request, $this->responseFactory->createResponse(), $this->options);
74
		$handle->init();
75
76
		curl_exec($handle->ch);
77
78
		$errno = curl_errno($handle->ch);
79
80
		if($errno !== CURLE_OK){
81
			$error = curl_error($handle->ch);
82
83
			$network_errors = [
84
				CURLE_COULDNT_RESOLVE_PROXY,
85
				CURLE_COULDNT_RESOLVE_HOST,
86
				CURLE_COULDNT_CONNECT,
87
				CURLE_OPERATION_TIMEOUTED,
88
				CURLE_SSL_CONNECT_ERROR,
89
				CURLE_GOT_NOTHING,
90
			];
91
92
			if(in_array($errno, $network_errors, true)){
93
				throw new NetworkException($error, $request);
94
			}
95
96
			throw new RequestException($error, $request);
97
		}
98
99
		$handle->close();
100
		$handle->response->getBody()->rewind();
101
102
		return $handle->response;
103
104
	}
105
106
	/**
107
	 * @todo: files, content-type
108
	 *
109
	 * @param string      $uri
110
	 * @param string|null $method
111
	 * @param array|null  $query
112
	 * @param mixed|null  $body
113
	 * @param array|null  $headers
114
	 *
115
	 * @return \Psr\Http\Message\ResponseInterface
116
	 */
117
	public function request(string $uri, string $method = null, array $query = null, $body = null, array $headers = null):ResponseInterface{
118
		$method    = strtoupper($method ?? 'GET');
119
		$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...
120
		$request   = $this->requestFactory->createRequest($method, Psr7\merge_query($uri, $query ?? []));
121
122
		if(in_array($method, ['DELETE', 'PATCH', 'POST', 'PUT'], true) && $body !== null){
123
124
			if(is_array($body) || is_object($body)){
125
126
				if(!isset($headers['Content-type'])){
127
					$headers['Content-type'] = 'application/x-www-form-urlencoded';
128
				}
129
130
				if($headers['Content-type'] === 'application/x-www-form-urlencoded'){
131
					$body = http_build_query($body, '', '&', PHP_QUERY_RFC1738);
132
				}
133
				elseif($headers['Content-type'] === 'application/json'){
134
					$body = json_encode($body);
135
				}
136
137
			}
138
139
			$request = $request->withBody($this->streamFactory->createStream((string)$body));
140
		}
141
142
		foreach($headers as $header => $value){
143
			$request = $request->withAddedHeader($header, $value);
144
		}
145
146
		return $this->sendRequest($request);
147
	}
148
149
}
150