Completed
Push — master ( 3f770a...ab575c )
by smiley
05:23
created

MultiRequest::fetch()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 23
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 8
Bugs 0 Features 4
Metric Value
c 8
b 0
f 4
dl 0
loc 23
rs 9.0856
cc 2
eloc 13
nc 2
nop 1
1
<?php
2
/**
3
 *
4
 * @filesource   MultiRequest.php
5
 * @created      15.02.2016
6
 * @package      chillerlan\TinyCurl
7
 * @author       Smiley <[email protected]>
8
 * @copyright    2016 Smiley
9
 * @license      MIT
10
 */
11
12
namespace chillerlan\TinyCurl;
13
14
use chillerlan\TinyCurl\Response\MultiResponse;
15
use chillerlan\TinyCurl\Response\MultiResponseHandlerInterface;
16
17
/**
18
 * Class MultiRequest
19
 *
20
 * @link http://www.onlineaspect.com/2009/01/26/how-to-use-curl_multi-without-blocking/
21
 */
22
class MultiRequest{
23
24
	/**
25
	 * the curl_multi master handle
26
	 *
27
	 * @var resource
28
	 */
29
	protected $curl_multi;
30
31
	/**
32
	 * cURL options for each handle
33
	 *
34
	 * @var array
35
	 */
36
	protected $curl_options = [];
37
38
	/**
39
	 * An array of the request URLs as \chillerlan\TinyCurl\URL object
40
	 *
41
	 * @var array
42
	 */
43
	protected $stack = [];
44
45
	/**
46
	 * The returned value from MultiResponseHandlerInterface::handleResponse() for each request
47
	 *
48
	 * @var array
49
	 */
50
	protected $responses = [];
51
52
	/**
53
	 * @var \chillerlan\TinyCurl\MultiRequestOptions
54
	 */
55
	protected $options;
56
57
	/**
58
	 * @var \chillerlan\TinyCurl\Response\MultiResponseHandlerInterface
59
	 */
60
	protected $multiResponseHandler;
61
62
	/**
63
	 * MultiRequest constructor.
64
	 *
65
	 * @param \chillerlan\TinyCurl\MultiRequestOptions $options
0 ignored issues
show
Documentation introduced by
Should the type for parameter $options not be null|MultiRequestOptions?

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...
66
	 */
67
	public function __construct(MultiRequestOptions $options = null){
68
69
		if(!$options){
70
			$options = new MultiRequestOptions;
71
		}
72
73
		$this->options = $options;
74
75
		if($this->options->handler){
76
			$this->setHandler();
77
		}
78
79
		$ca_info = is_file($this->options->ca_info) ? $this->options->ca_info : null;
80
		$this->curl_options = $this->options->curl_options + [
81
			CURLOPT_RETURNTRANSFER => true,
82
			CURLOPT_SSL_VERIFYPEER => (bool)$ca_info,
83
			CURLOPT_SSL_VERIFYHOST => 2, // Support for value 1 removed in cURL 7.28.1
84
			CURLOPT_CAINFO         => $ca_info,
85
			CURLOPT_HEADER         => true,
86
		];
87
88
	}
89
90
	/**
91
	 * closes the curl_multi instance
92
	 *
93
	 * @codeCoverageIgnore
94
	 */
95
	public function __destruct(){
96
		if($this->curl_multi){
97
			curl_multi_close($this->curl_multi);
98
		}
99
	}
100
101
	/**
102
	 * @param \chillerlan\TinyCurl\Response\MultiResponseHandlerInterface $handler
0 ignored issues
show
Documentation introduced by
Should the type for parameter $handler not be null|MultiResponseHandlerInterface?

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...
103
	 *
104
	 * @return $this
105
	 * @throws \chillerlan\TinyCurl\RequestException
106
	 */
107
	public function setHandler(MultiResponseHandlerInterface $handler = null){
108
109
		if(!$handler){
110
111
			if(!class_exists($this->options->handler)){
112
				throw new RequestException('!$this->options->handler');
113
			}
114
115
			$handler = new $this->options->handler($this);
116
117
			if(!is_a($handler, MultiResponseHandlerInterface::class)){
118
				throw new RequestException('!is_a($handler)');
119
			}
120
121
		}
122
123
		$this->multiResponseHandler = $handler;
124
125
		return $this;
126
	}
127
128
	/**
129
	 * @param array $urls array of \chillerlan\TinyCurl\URL objects
130
	 *
131
	 * @return $this
132
	 * @throws \chillerlan\TinyCurl\RequestException
133
	 */
134
	public function fetch(array $urls){
135
136
		if(empty($urls)){
137
			throw new RequestException('empty($urls)');
138
		}
139
140
		$this->stack      = $urls;
141
		$this->curl_multi = curl_multi_init();
142
143
		curl_multi_setopt($this->curl_multi, CURLMOPT_PIPELINING, 2);
144
		curl_multi_setopt($this->curl_multi, CURLMOPT_MAXCONNECTS, $this->options->window_size);
145
146
		// shoot out the first batch of requests
147
		array_map(function(){
148
			$this->createHandle();
149
			usleep(100);
150
		}, range(1, $this->options->window_size));
151
152
		/// ...and start processing the stack
153
		$this->processStack();
154
155
		return $this;
156
	}
157
158
	/**
159
	 * @param mixed $response
160
	 *
161
	 * @see \chillerlan\TinyCurl\Response\MultiResponseHandlerInterface
162
	 * @return $this
163
	 */
164
	public function addResponse($response){
165
		$this->responses[] = $response;
166
167
		return $this;
168
	}
169
170
	/**
171
	 * @return array
172
	 */
173
	public function getResponseData(){
174
		return $this->responses;
175
	}
176
177
	/**
178
	 * creates a new cURL handle
179
	 */
180
	protected function createHandle(){
181
182
		if(!empty($this->stack)){
183
			$url = array_shift($this->stack);
184
185
			if($url instanceof URL){
186
				$curl = curl_init($url);
187
				curl_setopt_array($curl, $this->curl_options);
188
				curl_multi_add_handle($this->curl_multi, $curl);
189
			}
190
			else{
191
				// retry on next if we don't get what we expect
192
				$this->createHandle();
193
			}
194
195
		}
196
197
	}
198
199
	/**
200
	 * processes the requests
201
	 */
202
	protected function processStack(){
203
204
		do{
205
206
			if(curl_multi_exec($this->curl_multi, $active) !== CURLM_OK){
207
				break; // @codeCoverageIgnore
208
			}
209
210
			// welcome to callback hell.
211
			while($state = curl_multi_info_read($this->curl_multi)){
212
				$url = $this->multiResponseHandler->handleResponse(new MultiResponse($state['handle']));
213
214
				if($url instanceof URL){
215
					$this->stack[] = $url;
216
				}
217
218
				$this->createHandle();
219
				curl_multi_remove_handle($this->curl_multi, $state['handle']);
220
			}
221
222
			if($active){
223
				curl_multi_select($this->curl_multi, $this->options->timeout);
224
			}
225
226
		}
227
		while($active);
228
229
	}
230
231
}
232