<?php
/**
* MultiCurl class provides a convenient way to execute parallel HTTP(S)
* requests via PHP MULTI CURL extension with additional restrictions.
* For example: start 100 downloads with 2 parallel sessions, and get only
* first 100 Kb per session.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*/
abstract class MultiCurl {
/**
* Maximal number of CURL multi sessions. Default: 10 sessions.
*
* @var integer
*/
private $maxSessions = 10;
/**
* Maximal size of downloaded content. Default: 10 Mb (10 * 1024 * 1024).
*
* @var integer
*/
private $maxSize = 10485760;
/**
* Common CURL options (used for all requests).
*
* @var array
*/
private $curlOptions;
/**
* Current CURL multi sessions.
*
* @var array
*/
private $sessions = array();
/**
* Class constructor. Setup primary parameters.
*
* @param array $curlOptions Common CURL options.
*/
public function __construct($curlOptions = array()) {
$this->setCurlOptions($curlOptions);
}
/**
* Class destructor. Close opened sessions.
*/
public function __destruct() {
foreach ($this->sessions as $i => $sess) {
$this->destroySession($i);
}
}
/**
* Adds new URL to query.
*
* @param mixed $url URL for downloading.
* @param array $curlOptions CURL options for current request.
*/
public function addUrl($url, $curlOptions = array()) {
// Check URL
if (!$url) {
throw new Exception('URL is empty!');
}
// Check array of URLs
if (is_array($url)) {
foreach ($url as $s) {
$this->addUrl($s, $curlOptions);
}
return;
}
// Check query
while (count($this->sessions) == $this->maxSessions) {
$this->checkSessions();
}
// Init new CURL session
$ch = curl_init($url);
foreach ($this->curlOptions as $option => $value) {
curl_setopt($ch, $option, $value);
}
foreach ($curlOptions as $option => $value) {
curl_setopt($ch, $option, $value);
}
// Init new CURL multi session
$mh = curl_multi_init();
curl_multi_add_handle($mh, $ch);
$this->sessions[] = array($mh, $ch, $url);
$this->execSession(array_pop(array_keys($this->sessions)));
}
/**
* Waits CURL milti sessions.
*/
public function wait() {
while (count($this->sessions)) {
$this->checkSessions();
}
}
/**
* Executes all active CURL multi sessions.
*/
protected function checkSessions() {
foreach ($this->sessions as $i => $sess) {
if (curl_multi_select($sess[0]) != -1) {
$this->execSession($i);
}
}
}
/**
* Executes CURL multi session, check session status and downloaded size.
*
* @param integer $i A session id.
*/
protected function execSession($i) {
list($mh, $ch) = $this->sessions[$i];
while (($mrc = curl_multi_exec($mh, $act)) == CURLM_CALL_MULTI_PERFORM);
if (!$act || $mrc != CURLM_OK ||
curl_getinfo($ch, CURLINFO_SIZE_DOWNLOAD) >= $this->maxSize) {
$this->closeSession($i);
}
}
/**
* Closes session.
*
* @param integer $i A session id.
*/
protected function closeSession($i) {
list(, $ch, $url) = $this->sessions[$i];
$content = !curl_error($ch) ? curl_multi_getcontent($ch) : null;
$info = curl_getinfo($ch);
$this->destroySession($i);
$this->onLoad($url, $content, $info);
}
/**
* Destroys session.
*
* @param integer $i A session id.
*/
protected function destroySession($i) {
list($mh, $ch,) = $this->sessions[$i];
curl_multi_remove_handle($mh, $ch);
curl_close($ch);
curl_multi_close($mh);
unset($this->sessions[$i]);
}
/**
* Gets maximal number of CURL multi sessions.
*
* @return integer Maximal number of CURL multi sessions.
*/
public function getMaxSessions() {
return $this->maxSessions;
}
/**
* Sets maximal number of CURL multi sessions.
*
* @param integer $maxSessions Maximal number of CURL multi sessions.
*/
public function setMaxSessions($maxSessions) {
if ((int)$maxSessions <= 0) {
throw new Exception('Max sessions number must be bigger then zero!');
}
$this->maxSessions = (int)$maxSessions;
}
/**
* Gets maximal size limit for downloaded content.
*
* @return integer Maximal size limit for downloaded content.
*/
public function getMaxSize() {
return $this->maxSize;
}
/**
* Sets maximal size limit for downloaded content.
*
* @param integer $maxSize Maximal size limit for downloaded content.
*/
public function setMaxSize($maxSize) {
if ((int)$maxSize <= 0) {
throw new Exception('Max size limit must be bigger then zero!');
}
$this->maxSize = (int)$maxSize;
}
/**
* Gets CURL options for all requests.
*
* @return array CURL options.
*/
public function getCurlOptions() {
return $this->curlOptions;
}
/**
* Sets CURL options for all requests.
*
* @param array $curlOptions CURL options.
*/
public function setCurlOptions($curlOptions) {
if (!array_key_exists(CURLOPT_FOLLOWLOCATION, $curlOptions)) {
$curlOptions[CURLOPT_FOLLOWLOCATION] = 1;
}
$curlOptions[CURLOPT_RETURNTRANSFER] = 1;
$this->curlOptions = $curlOptions;
}
/**
* OnLoad callback event.
*
* @param string $url URL for downloading.
* @param string $content Downloaded content.
* @param array $info CURL session information.
*/
protected abstract function onLoad($url, $content, $info);
/**
* Checks CURL extension, etc.
*/
public static function checkEnvironment() {
if (!extension_loaded('curl')) {
throw new Exception('CURL extension not loaded');
}
}
}
|