Skip to content

Commit 979ecb0

Browse files
committed
Merge branch 'psr-7'
# Conflicts: # README.md # composer.json # src/CurlClient.php # src/GuzzleClient.php # src/HTTPClientAbstract.php # src/HTTPResponse.php # src/StreamClient.php # tests/HTTPClientTestAbstract.php
2 parents 78d4e70 + cd6e7b3 commit 979ecb0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+5756
-1067
lines changed

README.md

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# chillerlan/php-httpinterface
22

3-
A [http client wrapper](https://github.com/chillerlan/php-oauth/tree/afeb3efa7fb31710c7fd3d2909772e6177c8196a/src/HTTP) for PHP 7.2+.
3+
A http client wrapper/PSR-7/PSR-17 implementation for PHP 7.2+.
44

55
[![version][packagist-badge]][packagist]
66
[![license][license-badge]][license]
@@ -25,26 +25,3 @@ A [http client wrapper](https://github.com/chillerlan/php-oauth/tree/afeb3efa7fb
2525
[donate-badge]: https://img.shields.io/badge/donate-paypal-ff33aa.svg?style=flat-square
2626
[donate]: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=WLYUNAT9ZTJZ4
2727

28-
### `HTTPClientInterface`
29-
method | return
30-
------ | ------
31-
`request(string $url, array $params = null, string $method = null, $body = null, array $headers = null)` | `HTTPResponseInterface`
32-
`normalizeRequestHeaders(array $headers)` | array
33-
`buildQuery(array $params, bool $urlencode = null, string $delimiter = null, string $enclosure = null)` | string
34-
`checkQueryParams(array $params, bool $booleans_as_string = null)` | array
35-
36-
### `HTTPClientTrait`
37-
The `HTTPClientTrait` provides several (protected) shortcut methods for the `HTTPClientInterface`.
38-
39-
method | return
40-
------ | ------
41-
`setHTTPClient(HTTPClientInterface $http)` | `$this`
42-
`httpRequest(string $url, array $params = null, string $method = null, $body = null, array $headers = null)` | `HTTPResponseInterface`
43-
`httpDELETE(string $url, array $params = null, array $headers = null)` | `HTTPResponseInterface`
44-
`httpGET(string $url, array $params = null, array $headers = null)` | `HTTPResponseInterface`
45-
`httpPATCH(string $url, array $params = null, $body = null, array $headers = null)` | `HTTPResponseInterface`
46-
`httpPOST(string $url, array $params = null, $body = null, array $headers = null)` | `HTTPResponseInterface`
47-
`httpPUT(string $url, array $params = null, $body = null, array $headers = null)` | `HTTPResponseInterface`
48-
`normalizeRequestHeaders(array $headers)` | array
49-
`checkQueryParams($params, bool $booleans_as_string = null)` | mixed
50-
`httpBuildQuery(array $params, bool $urlencode = null, string $delimiter = null, string $enclosure = null)` | string

composer.json

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"name": "chillerlan/php-httpinterface",
3-
"description": "A http client interface for PHP7+",
3+
"description": "A http PSR-7/17 client/interface for PHP7.2+",
44
"license": "MIT",
55
"type": "library",
66
"keywords": [
7-
"http", "client", "wrapper"
7+
"http", "client", "psr-7", "psr-17"
88
],
99
"minimum-stability": "stable",
1010
"authors": [
@@ -18,16 +18,28 @@
1818
"issues": "https://github.com/chillerlan/php-httpinterface/issues",
1919
"source": "https://github.com/chillerlan/php-httpinterface"
2020
},
21+
"provide": {
22+
"chillerlan/php-httpinterface": "2.0",
23+
"psr/http-message-implementation": "1.0",
24+
"psr/http-factory-implementation": "1.0"
25+
},
2126
"require": {
22-
"php": "^7.2.0",
23-
"chillerlan/php-traits": "^2.0",
27+
"php": "^7.2",
28+
"ext-curl":"*",
29+
"chillerlan/php-settings-container":"^1.0",
30+
"fig/http-message-util":"^1.1",
31+
"php-http/httplug":"^1.1",
32+
"psr/http-message": "^1.0",
33+
"psr/http-factory":"^1.0",
2434
"psr/log": "^1.0"
2535
},
2636
"require-dev": {
27-
"guzzlehttp/guzzle": "^6.3",
2837
"phpunit/phpunit": "^7.3"
2938
},
3039
"autoload": {
40+
"files": [
41+
"src/includes.php"
42+
],
3143
"psr-4": {
3244
"chillerlan\\HTTP\\": "src/"
3345
}

src/CurlClient.php

Lines changed: 101 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,142 +1,148 @@
11
<?php
22
/**
3-
* Class CurlClient
3+
* Class HTTPClient
44
*
5-
* @filesource CurlClient.php
6-
* @created 21.10.2017
5+
* @filesource HTTPClient.php
6+
* @created 27.08.2018
77
* @package chillerlan\HTTP
8-
* @author Smiley <smiley@chillerlan.net>
9-
* @copyright 2017 Smiley
8+
* @author smiley <smiley@chillerlan.net>
9+
* @copyright 2018 smiley
1010
* @license MIT
1111
*/
1212

1313
namespace chillerlan\HTTP;
1414

15-
use chillerlan\Traits\ImmutableSettingsInterface;
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};
1620

17-
/**
18-
* @property resource $http
19-
*/
20-
class CurlClient extends HTTPClientAbstract{
21+
class CurlClient implements HTTPClientInterface{
2122

2223
/**
23-
* @var \stdClass
24+
* @var \chillerlan\HTTP\HTTPOptions
2425
*/
25-
protected $responseHeaders;
26+
protected $options;
2627

2728
/**
28-
* CurlClient constructor.
29-
*
30-
* @param \chillerlan\Traits\ImmutableSettingsInterface $options
31-
*
32-
* @throws \chillerlan\HTTP\HTTPClientException
29+
* @var \Psr\Http\Message\RequestFactoryInterface
3330
*/
34-
public function __construct(ImmutableSettingsInterface $options){
35-
parent::__construct($options);
31+
protected $requestFactory;
3632

37-
if(!isset($this->options->ca_info) || !is_file($this->options->ca_info)){
38-
throw new HTTPClientException('invalid CA file');
39-
}
33+
/**
34+
* @var \Psr\Http\Message\ResponseFactoryInterface
35+
*/
36+
protected $responseFactory;
4037

41-
}
38+
/**
39+
* @var \Psr\Http\Message\StreamFactoryInterface
40+
*/
41+
protected $streamFactory;
4242

4343
/**
44-
* @return void
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
4550
*/
46-
protected function initCurl(){
47-
$this->http = curl_init();
48-
49-
curl_setopt_array($this->http, [
50-
CURLOPT_HEADER => false,
51-
CURLOPT_RETURNTRANSFER => true,
52-
CURLOPT_PROTOCOLS => CURLPROTO_HTTP|CURLPROTO_HTTPS,
53-
CURLOPT_CAINFO => $this->options->ca_info,
54-
CURLOPT_SSL_VERIFYPEER => true,
55-
CURLOPT_SSL_VERIFYHOST => 2,
56-
CURLOPT_TIMEOUT => 5,
57-
CURLOPT_USERAGENT => $this->options->user_agent,
58-
]);
59-
60-
curl_setopt_array($this->http, $this->options->curl_options);
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;
58+
$this->requestFactory = $requestFactory ?? new RequestFactory;
59+
$this->responseFactory = $responseFactory ?? new ResponseFactory;
60+
$this->streamFactory = $streamFactory ?? new StreamFactory;
6161
}
6262

63-
/** @inheritdoc */
64-
protected function getResponse():HTTPResponseInterface{
65-
$this->responseHeaders = new \stdClass;
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();
6676

67-
$headers = $this->normalizeRequestHeaders($this->requestHeaders);
77+
curl_exec($handle->ch);
6878

69-
if(in_array($this->requestMethod, ['PATCH', 'POST', 'PUT', 'DELETE'])){
79+
$errno = curl_errno($handle->ch);
7080

71-
$options = in_array($this->requestMethod, ['PATCH', 'PUT', 'DELETE'])
72-
? [CURLOPT_CUSTOMREQUEST => $this->requestMethod]
73-
: [CURLOPT_POST => true];
81+
if($errno !== CURLE_OK){
82+
$error = curl_error($handle->ch);
7483

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+
];
7592

76-
if(!isset($headers['Content-type']) && $this->requestMethod === 'POST' && is_array($this->requestBody)){
77-
$headers += ['Content-type: application/x-www-form-urlencoded'];
78-
$this->requestBody = http_build_query($this->requestBody, '', '&', PHP_QUERY_RFC1738);
93+
if(in_array($errno, $network_errors, true)){
94+
throw new NetworkException($error, $request);
7995
}
8096

81-
$options += [CURLOPT_POSTFIELDS => $this->requestBody];
82-
}
83-
else{
84-
$options = [CURLOPT_CUSTOMREQUEST => $this->requestMethod];
97+
throw new RequestException($error, $request);
8598
}
8699

87-
$headers += [
88-
'Host: '.$this->parsedURL['host'],
89-
'Connection: close',
90-
];
91-
92-
parse_str($this->parsedURL['query'] ?? '', $parsedquery);
93-
$params = array_merge($parsedquery, $this->requestParams);
94-
95-
$url = $this->requestURL.(!empty($params) ? '?'.$this->buildQuery($params) : '');
96-
97-
$options += [
98-
CURLOPT_URL => $url,
99-
CURLOPT_HTTPHEADER => $headers,
100-
CURLOPT_HEADERFUNCTION => [$this, 'headerLine'],
101-
];
102-
103-
$this->initCurl();
104-
curl_setopt_array($this->http, $options);
100+
$handle->close();
101+
$handle->response->getBody()->rewind();
105102

106-
$response = curl_exec($this->http);
107-
$curl_info = curl_getinfo($this->http);
103+
return $handle->response;
108104

109-
return new HTTPResponse([
110-
'url' => $url,
111-
'curl_info' => $curl_info,
112-
'headers' => $this->responseHeaders,
113-
'body' => $response,
114-
]);
115105
}
116106

117107
/**
118-
* @param resource $curl
119-
* @param string $header_line
108+
* @param string $uri
109+
* @param string|null $method
110+
* @param array|null $query
111+
* @param mixed|null $body
112+
* @param array|null $headers
120113
*
121-
* @return int
122-
*
123-
* @link http://php.net/manual/function.curl-setopt.php CURLOPT_HEADERFUNCTION
114+
* @return \Psr\Http\Message\ResponseInterface
124115
*/
125-
protected function headerLine($curl, $header_line){
126-
$header = explode(':', $header_line, 2);
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);
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+
}
127137

128-
if(count($header) === 2){
129-
$this->responseHeaders->{trim(strtolower($header[0]))} = trim($header[1]);
138+
$request = $request->withBody($this->streamFactory->createStream((string)$body));
130139
}
131-
elseif(substr($header_line, 0, 4) === 'HTTP'){
132-
$status = explode(' ', $header_line, 3);
133140

134-
$this->responseHeaders->httpversion = explode('/', $status[0], 2)[1];
135-
$this->responseHeaders->statuscode = intval($status[1]);
136-
$this->responseHeaders->statustext = trim($status[2]);
141+
foreach($headers as $header => $value){
142+
$request = $request->withAddedHeader($header, $value);
137143
}
138144

139-
return strlen($header_line);
145+
return $this->sendRequest($request);
140146
}
141147

142148
}

0 commit comments

Comments
 (0)