Passed
Branch master (abb9ab)
by
unknown
02:22
created

src/MultiRequest.php (5 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Class MultiRequest
4
 *
5
 * @filesource   MultiRequest.php
6
 * @created      15.02.2016
7
 * @package      chillerlan\TinyCurl
8
 * @author       Smiley <[email protected]>
9
 * @copyright    2016 Smiley
10
 * @license      MIT
11
 */
12
13
namespace chillerlan\TinyCurl;
14
15
use chillerlan\Traits\ContainerInterface;
16
17
/**
18
 * @link http://www.onlineaspect.com/2009/01/26/how-to-use-curl_multi-without-blocking/
19
 * @link https://github.com/joshfraser/rolling-curl
20
 *
21
 * (there are countless implementations around, just google for "php rolling curl")
22
 */
23
class MultiRequest{
24
25
	/**
26
	 * the curl_multi master handle
27
	 *
28
	 * @var resource
29
	 */
30
	protected $curl_multi;
31
32
	/**
33
	 * cURL options for each handle
34
	 *
35
	 * @var array
36
	 */
37
	protected $curl_options = [];
38
39
	/**
40
	 * An array of the request URLs
41
	 *
42
	 * @var array<\chillerlan\TinyCurl\URL>
43
	 */
44
	protected $stack = [];
45
46
	/**
47
	 * The returned value from MultiResponseHandlerInterface::handleResponse() for each request
48
	 *
49
	 * @var array
50
	 */
51
	protected $responses = [];
52
53
	/**
54
	 * @var \chillerlan\TinyCurl\MultiRequestOptions
55
	 */
56
	protected $options;
57
58
	/**
59
	 * @var \chillerlan\TinyCurl\MultiResponseHandlerInterface
60
	 */
61
	protected $multiResponseHandler;
62
63
	/**
64
	 * MultiRequest constructor.
65
	 *
66
	 * @param \chillerlan\Traits\ContainerInterface|null $options
67
	 */
68
	public function __construct(ContainerInterface $options = null){
69
		$this->setOptions($options ?: new MultiRequestOptions);
70
	}
71
72
	/**
73
	 * closes the curl_multi instance
74
	 *
75
	 * @codeCoverageIgnore
76
	 */
77
	public function __destruct(){
78
		if($this->curl_multi){
79
			curl_multi_close($this->curl_multi);
80
		}
81
	}
82
83
	/**
84
	 * @param \chillerlan\Traits\ContainerInterface $options
85
	 *
86
	 * @return \chillerlan\TinyCurl\MultiRequest
87
	 */
88
	public function setOptions(ContainerInterface $options):MultiRequest {
89
		$this->options = $options;
0 ignored issues
show
Documentation Bug introduced by
$options is of type object<chillerlan\Traits\ContainerInterface>, but the property $options was declared to be of type object<chillerlan\TinyCurl\MultiRequestOptions>. 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...
90
91
		if($this->options->handler){
0 ignored issues
show
Accessing handler on the interface chillerlan\Traits\ContainerInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
92
			$this->setHandler();
93
		}
94
95
		$this->curl_options = $this->options->curl_options + [
0 ignored issues
show
Accessing curl_options on the interface chillerlan\Traits\ContainerInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
96
			CURLOPT_HEADER         => true,
97
			CURLOPT_RETURNTRANSFER => true,
98
			CURLOPT_USERAGENT      => $this->options->user_agent,
0 ignored issues
show
Accessing user_agent on the interface chillerlan\Traits\ContainerInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
99
			CURLOPT_PROTOCOLS      => CURLPROTO_HTTP|CURLPROTO_HTTPS,
100
			CURLOPT_SSL_VERIFYPEER => true,
101
			CURLOPT_SSL_VERIFYHOST => 2, // Support for value 1 removed in cURL 7.28.1
102
			CURLOPT_CAINFO         => is_file($this->options->ca_info) ? $this->options->ca_info : null,
0 ignored issues
show
Accessing ca_info on the interface chillerlan\Traits\ContainerInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

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