Completed
Push — master ( 3c4fae...40d490 )
by smiley
01:25
created

CurlMultiClient::process()   B

Complexity

Conditions 11
Paths 14

Size

Total Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
nc 14
nop 0
dl 0
loc 47
rs 7.3166
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Class CurlMultiClient
4
 *
5
 * @filesource   CurlMultiClient.php
6
 * @created      30.08.2018
7
 * @package      chillerlan\HTTP\CurlUtils
8
 * @author       smiley <[email protected]>
9
 * @copyright    2018 smiley
10
 * @license      MIT
11
 */
12
13
namespace chillerlan\HTTP\CurlUtils;
14
15
use chillerlan\HTTP\{HTTPOptions, Psr17\ResponseFactory, Psr18\ClientException};
16
use chillerlan\Settings\SettingsContainerInterface;
17
use Psr\Http\Message\{RequestInterface, ResponseFactoryInterface};
18
use Psr\Log\{LoggerAwareInterface, LoggerAwareTrait, LoggerInterface, NullLogger};
19
20
use function array_shift, curl_close, curl_getinfo, curl_multi_add_handle, curl_multi_close, curl_multi_exec,
21
	curl_multi_info_read, curl_multi_init, curl_multi_remove_handle, curl_multi_select, curl_multi_setopt,
22
	is_array, is_resource, usleep;
23
24
use const CURLM_OK, CURLMOPT_MAXCONNECTS, CURLMOPT_PIPELINING, CURLPIPE_HTTP1, CURLPIPE_MULTIPLEX;
25
26
class CurlMultiClient implements LoggerAwareInterface{
27
	use LoggerAwareTrait;
28
29
	/** @var \chillerlan\HTTP\HTTPOptions */
30
	protected $options;
31
32
	/** @var \Psr\Http\Message\ResponseFactoryInterface */
33
	protected $responseFactory;
34
35
	/** @var \chillerlan\HTTP\CurlUtils\MultiResponseHandlerInterface */
36
	protected $multiResponseHandler;
37
38
	/**
39
	 * the curl_multi master handle
40
	 *
41
	 * @var resource
42
	 */
43
	protected $curl_multi;
44
45
	/**
46
	 * An array of RequestInterface to run
47
	 *
48
	 * @var \Psr\Http\Message\RequestInterface[]
49
	 */
50
	protected $requests = [];
51
52
	/**
53
	 * the stack of running handles
54
	 *
55
	 * @var \chillerlan\HTTP\CurlUtils\CurlHandle[]
56
	 */
57
	protected $handles = [];
58
59
	/**
60
	 * @var int
61
	 */
62
	protected $handleCounter = 0;
63
64
	/**
65
	 * CurlMultiClient constructor.
66
	 *
67
	 * @param \chillerlan\Settings\SettingsContainerInterface|null $options
68
	 * @param \Psr\Http\Message\ResponseFactoryInterface|null      $responseFactory
69
	 * @param \Psr\Log\LoggerInterface|null                        $logger
70
	 */
71
	public function __construct(
72
		SettingsContainerInterface $options = null,
73
		ResponseFactoryInterface $responseFactory = null,
74
		LoggerInterface $logger = null
75
	){
76
		$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...
77
		$this->responseFactory = $responseFactory ?? new ResponseFactory;
78
		$this->logger          = $logger ?? new NullLogger;
79
		$this->curl_multi      = curl_multi_init();
80
81
		curl_multi_setopt($this->curl_multi, CURLMOPT_PIPELINING, CURLPIPE_HTTP1 | CURLPIPE_MULTIPLEX);
82
		curl_multi_setopt($this->curl_multi, CURLMOPT_MAXCONNECTS, $this->options->windowSize);
83
84
		foreach($this->options->curl_multi_options as $k => $v){
85
			curl_multi_setopt($this->curl_multi, $k, $v);
86
		}
87
88
	}
89
90
	/**
91
	 * close an existing cURL multi handle on exit
92
	 */
93
	public function __destruct(){
94
		$this->close();
95
	}
96
97
	/**
98
	 * @return void
99
	 */
100
	public function close():void{
101
102
		if(is_resource($this->curl_multi)){
103
			curl_multi_close($this->curl_multi);
104
		}
105
106
	}
107
108
	/**
109
	 * @param \chillerlan\HTTP\CurlUtils\MultiResponseHandlerInterface $handler
110
	 *
111
	 * @return \chillerlan\HTTP\CurlUtils\CurlMultiClient
112
	 */
113
	public function setMultiResponseHandler(MultiResponseHandlerInterface $handler):CurlMultiClient{
114
		$this->multiResponseHandler = $handler;
115
116
		return $this;
117
	}
118
119
	/**
120
	 * @param \Psr\Http\Message\RequestInterface $request
121
	 *
122
	 * @return \chillerlan\HTTP\CurlUtils\CurlMultiClient
123
	 */
124
	public function addRequest(RequestInterface $request):CurlMultiClient{
125
		$this->requests[] = $request;
126
127
		return $this;
128
	}
129
130
	/**
131
	 * @param \Psr\Http\Message\RequestInterface[] $stack
132
	 *
133
	 * @return \chillerlan\HTTP\CurlUtils\CurlMultiClient
134
	 */
135
	public function addRequests(iterable $stack):CurlMultiClient{
136
137
		foreach($stack as $request){
138
139
			if($request instanceof RequestInterface){
140
				$this->requests[] = $request;
141
			}
142
143
		}
144
145
		return $this;
146
	}
147
148
	/**
149
	 * @throws \chillerlan\HTTP\Psr18\ClientException
150
	 */
151
	public function process():CurlMultiClient{
152
153
		if(empty($this->requests)){
154
			throw new ClientException('request stack is empty');
155
		}
156
157
		if(!$this->multiResponseHandler instanceof MultiResponseHandlerInterface){
158
			throw new ClientException('no response handler set');
159
		}
160
161
		// shoot out the first batch of requests
162
		for($i = 0; $i < $this->options->windowSize; $i++){
163
			$this->createHandle();
164
		}
165
166
		// ...and process the stack
167
		do{
168
			$status = curl_multi_exec($this->curl_multi, $active);
169
170
			if($active){
171
				curl_multi_select($this->curl_multi, $this->options->timeout);
172
			}
173
174
			while($state = curl_multi_info_read($this->curl_multi)){
175
				$id     = (int)$state['handle'];
176
				$handle = $this->handles[$id];
177
				$info   = curl_getinfo($handle->curl);
178
				$result = $this->multiResponseHandler->handleResponse($handle->response, $handle->request, $handle->id, (is_array($info) ? $info : []));
179
180
				curl_multi_remove_handle($this->curl_multi, $state['handle']);
181
				curl_close($state['handle']);
182
				unset($this->handles[$id]);
183
184
				if($result instanceof RequestInterface && $handle->retries < $this->options->retries){
185
					$this->createHandle($result, $handle->id, ++$handle->retries);
186
187
					continue;
188
				}
189
190
				$this->createHandle();
191
			}
192
193
		}
194
		while($active && $status === CURLM_OK);
195
196
		return $this;
197
	}
198
199
	/**
200
	 * @param \Psr\Http\Message\RequestInterface $request
0 ignored issues
show
Documentation introduced by
Should the type for parameter $request not be null|\Psr\Http\Message\RequestInterface?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
201
	 * @param int|null                           $id
202
	 * @param int|null                           $retries
203
	 *
204
	 * @return void
205
	 */
206
	protected function createHandle(RequestInterface $request = null, int $id = null, int $retries = null):void{
207
208
		if($request === null){
209
210
			if(empty($this->requests)){
211
				return;
212
			}
213
214
			$request = array_shift($this->requests);
215
		}
216
217
		/** @var \chillerlan\HTTP\CurlUtils\CurlHandle $handle */
218
		$handle          = new $this->options->curlHandle($request, $this->responseFactory->createResponse(), $this->options);
219
		$handle->id      = $id ?? $this->handleCounter++;
220
		$handle->retries = $retries ?? 1;
221
222
		$handle->init();
223
		curl_multi_add_handle($this->curl_multi, $handle->curl);
224
225
		$this->handles[(int)$handle->curl] = $handle;
226
227
		if($this->options->sleep > 0){
228
			usleep($this->options->sleep);
229
		}
230
231
	}
232
233
}
234