Introduction
This story is for those people for whom network call becomes the bottleneck (I know for sure that there are plenty, so comes the solution). But before we get to the solution, allow me to describe the problem formally. When you have multiple network calls to be made simultaneously in PHP you either end up in reading about
Curl Multi exec or end up reading about
PHP Threads to have a work around and trying to make those requests simultaneously. So, the solution below will help you if you are facing any/all the issues like:
- Multiple urls needs to be hit without any dependency on each other.
- All the responses from the requests need to be handled from a single place.
- If you are not going to use it directly with client inputs (error handling is not done properly, yet!)
Solution
The solution I am talking about actually uses
Curl Multi exec but in a more cleaner way. We can call it a
wrapper for ease of use. Now lets make our eyes dirty and go through the code. PS: Please read the comments => Super necessary
Code Section
class MultiCurl
{
//curl handler info storing variables
private $multiCurlHandler;
private $noOfHandlers;
private $handlers;
public function __construct()
{
$this->multiCurlHandler = curl_multi_init();
$this->noOfHandlers = 0;
$this->handlers = [];
}
/**
* @param $ch curl_init() resource variable
* @param $id any unique identification for the handler (default is the number (i-1), for ith handler)
*/
public function addHandler($ch, $id = -1)
{
if ( $id !== -1 ) {
curl_setopt($ch, CURLOPT_PRIVATE, $id);
} else {
curl_setopt($ch, CURLOPT_PRIVATE, $this->noOfHandlers);
}
curl_multi_add_handle($this->multiCurlHandler, $ch);
$this->noOfHandlers++;
}
/**
* @param callable|NULL $callback non-mandatory callback (sort of) function, called with param mentioned next
* @param int $sleep milliseconds to wait in the loops to prevent high CPU usage (defaults to 100 ms)
* @return array associative array containing unique id to response/error mapping.
*/
public function exec(callable $callback = NULL, int $sleep = 100)
{
$active = 0;
do {
//Initialise multi exec resources until all are done
$execResult = curl_multi_exec($this->multiCurlHandler, $active);
usleep($sleep*1000);
} while ( $active > 0 );
while ( $active && $execResult == CURLM_OK ) {
// We have more than one connections awaiting for response and initialisations are done properly
//if the socket has any data
if ( curl_multi_select($this->multiCurlHandler) != -1 ) {
do {
//Fetch more data as long as system tells us to fetch
$execResult = curl_multi_exec($this->multiCurlHandler, $active);
usleep($sleep*500);
} while ( $active );
}
usleep($sleep*500);
}
//Rest below are easily understandable stuff
$response = [];
while ( $done = curl_multi_info_read($this->multiCurlHandler) ) {
$info = curl_getinfo($done['handle']);
$uniqueId = curl_getinfo($done['handle'], CURLINFO_PRIVATE);
if ( $info['http_code'] == 200 ) {
$output = curl_multi_getcontent($done['handle']);
if ( $callback === NULL ) {
$response[$uniqueId] = [
'error' => NULL,
'response' => $output
];
} else {
$callback($uniqueId, NULL, $output);
}
} else {
$error = curl_error($done['handle']);
if ( $callback === NULL ) {
$response[$uniqueId] = [
'error' => $error,
'response' => NULL
];
} else {
$callback($uniqueId, $error);
}
}
}
if ( $callback === NULL ) {
return $response;
}
}
/**
* Closes all the connections
*/
public function close()
{
foreach ( $this->handlers as $handler ) {
curl_multi_remove_handle($this->multiCurlHandler, $handler);
curl_close($handler);
}
curl_multi_close($this->multiCurlHandler);
$this->multiCurlHandler = NULL;
$this->handlers = [];
$this->noOfHandlers = 0;
}
public function __destruct()
{
if ( $this->multiCurlHandler !== NULL && $this->noOfHandlers > 0 ) {
$this->close();
}
}
}
Here's how you would use it
$obj = new MultiCurl();
$url = "http://jsonplaceholder.typicode.com/comments/";
for ( $i = 1; $i < 10; $i++ ) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url . $i);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$obj->addHandler($ch, "handle" . $i);
}
$res = $obj->exec();
/*
* Response can be obtained by using exec in callback form too as shown below:
* $obj->exec(function($uniqueId, $error, $response) {
* //if $error === NULL, $response is obtained
* //otherwise $response is not passed
* });
*/
$obj->close(); //Optional to call as __destruct() makes sure resources are freed at the end
Wordpress versionReferences
Matt Butcher, TechnoSophos |
Example, php.net
No comments:
Post a Comment