| 
<?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');
 }
 }
 }
 |