MultiCurl::addUrl()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
cc 2
nc 2
nop 2
1
<?php namespace JMathai\PhpMultiCurl;
2
/*if(!class_exists('MultiCurlManager'))
3
  include 'MultiCurlManager.php';
4
if(!class_exists('MultiCurlSequence'))
5
  include 'MultiCurlSequence.php';
6
if(!class_exists('MultiCurlException'))
7
  include 'MultiCurlException.php';*/
8
9
/**
10
 * MultiCurl multicurl http client
11
 *
12
 * @author Jaisen Mathai <[email protected]>
13
 */
14
class MultiCurl
15
{
16
  const timeout = 3;
17
  private static $inst = null;
18
  /* @TODO make this private and add a method to set it to 0 */
19
  public static $singleton = 0;
20
21
  private $mc;
22
  private $running;
23
  private $execStatus;
24
  private $sleepIncrement = 1.1;
25
  private $requests = array();
26
  private $responses = array();
27
  private $properties = array();
28
  private static $timers = array();
29
30
  public function __construct()
31
  {
32
    if(self::$singleton === 0)
33
    {
34
      throw new MultiCurlException('This class cannot be instantiated by the new keyword.  You must instantiate it using: $obj = MultiCurl::getInstance();');
35
    }
36
37
    $this->mc = curl_multi_init();
38
    $this->properties = array(
39
      'code'  => CURLINFO_HTTP_CODE,
40
      'time'  => CURLINFO_TOTAL_TIME,
41
      'length'=> CURLINFO_CONTENT_LENGTH_DOWNLOAD,
42
      'type'  => CURLINFO_CONTENT_TYPE,
43
      'url'   => CURLINFO_EFFECTIVE_URL
44
      );
45
  }
46
  
47
  public function reset(){
48
      $this->requests = array();
49
      $this->responses = array();
50
      self::$timers = array();
51
  }
52
53
  public function addUrl($url, $options = array())
54
  {
55
    $ch = curl_init($url);
56
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
57
    foreach($options as $option=>$value)
58
    {
59
        curl_setopt($ch, $option, $value);
60
    }
61
    return $this->addCurl($ch);
62
  }
63
64
  public function addCurl($ch)
65
  {
66
    if(gettype($ch) !== 'resource')
67
    {
68
      throw new MultiCurlInvalidParameterException('Parameter must be a valid curl handle');
69
    }
70
71
    $key = $this->getKey($ch);
72
    $this->requests[$key] = $ch;
73
    curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, 'headerCallback'));
74
75
    $code = curl_multi_add_handle($this->mc, $ch);
76
    $this->startTimer($key);
77
    
78
    // (1)
79
    if($code === CURLM_OK || $code === CURLM_CALL_MULTI_PERFORM)
80
    {
81
      do
82
      {
83
        $this->execStatus = curl_multi_exec($this->mc, $this->running);
84
      } while ($this->execStatus === CURLM_CALL_MULTI_PERFORM);
85
86
      return new MultiCurlManager($key);
87
    }
88
    else
89
    {
90
      return $code;
91
    }
92
  }
93
94
  public function getResult($key = null)
95
  {
96
    if($key != null)
97
    {
98
      if(isset($this->responses[$key]['code']))
99
      {
100
        return $this->responses[$key];
101
      }
102
103
      $innerSleepInt = $outerSleepInt = 1;
104
      while($this->running && ($this->execStatus == CURLM_OK || $this->execStatus == CURLM_CALL_MULTI_PERFORM))
105
      {
106
        usleep(intval($outerSleepInt));
107
        $outerSleepInt = intval(max(1, ($outerSleepInt*$this->sleepIncrement)));
108
        $ms=curl_multi_select($this->mc, 0);
109
110
        // bug in PHP 5.3.18+ where curl_multi_select can return -1
111
        // https://bugs.php.net/bug.php?id=63411
112
        if($ms === -1)
113
          usleep(100000);
114
115
        // see pull request https://github.com/jmathai/php-multi-curl/pull/17
116
        // details here http://curl.haxx.se/libcurl/c/libcurl-errors.html
117
        if($ms >= CURLM_CALL_MULTI_PERFORM)
118
        {
119
          do{
120
            $this->execStatus = curl_multi_exec($this->mc, $this->running);
121
            usleep(intval($innerSleepInt));
122
            $innerSleepInt = intval(max(1, ($innerSleepInt*$this->sleepIncrement)));
123
          }while($this->execStatus==CURLM_CALL_MULTI_PERFORM);
124
          $innerSleepInt = 1;
125
        }
126
        $this->storeResponses();
127
        if(isset($this->responses[$key]['data']))
128
        {
129
          return $this->responses[$key];
130
        }
131
      }
132
      return null;
133
    }
134
    return false;
135
  }
136
137
  public static function getSequence()
138
  {
139
    return new MultiCurlSequence(self::$timers);
140
  }
141
142
  public static function getTimers()
143
  {
144
    return self::$timers;
145
  }
146
147
  public function inject($key, $value)
148
  {
149
    $this->$key = $value;
150
  }
151
152
  private function getKey($ch)
153
  {
154
    return (string)$ch;
155
  }
156
157
  private function headerCallback($ch, $header)
158
  {
159
    $_header = trim($header);
160
    $colonPos= strpos($_header, ':');
161
    if($colonPos > 0)
162
    {
163
      $key = substr($_header, 0, $colonPos);
164
      $val = preg_replace('/^\W+/','',substr($_header, $colonPos));
165
      $this->responses[$this->getKey($ch)]['headers'][$key] = $val;
166
    }
167
    return strlen($header);
168
  }
169
170
  private function storeResponses()
171
  {
172
    while($done = curl_multi_info_read($this->mc))
173
    {
174
      $this->storeResponse($done);
175
    }
176
  }
177
178
  private function storeResponse($done, $isAsynchronous = true)
179
  {
180
    $key = $this->getKey($done['handle']);
181
    $this->stopTimer($key, $done);
182
    if($isAsynchronous)
183
      $this->responses[$key]['data'] = curl_multi_getcontent($done['handle']);
184
    else
185
      $this->responses[$key]['data'] = curl_exec($done['handle']);
186
187
    $this->responses[$key]['response'] = $this->responses[$key]['data'];
188
189
    foreach($this->properties as $name => $const)
190
    {
191
      $this->responses[$key][$name] = curl_getinfo($done['handle'], $const);
192
    }
193
    if($isAsynchronous)
194
      curl_multi_remove_handle($this->mc, $done['handle']);
195
    curl_close($done['handle']);
196
  }
197
198
  private function startTimer($key)
199
  {
200
    self::$timers[$key]['start'] = microtime(true);
201
  }
202
203
  private function stopTimer($key, $done)
204
  {
205
      self::$timers[$key]['end'] = microtime(true);
206
      self::$timers[$key]['api'] = curl_getinfo($done['handle'], CURLINFO_EFFECTIVE_URL);
207
      self::$timers[$key]['time'] = curl_getinfo($done['handle'], CURLINFO_TOTAL_TIME);
208
      self::$timers[$key]['code'] = curl_getinfo($done['handle'], CURLINFO_HTTP_CODE);
209
  }
210
211
  public static function getInstance()
212
  {
213
    if(self::$inst == null)
214
    {
215
      self::$singleton = 1;
216
      self::$inst = new MultiCurl();
217
    }
218
219
    return self::$inst;
220
  }
221
}
222
223
/*
224
 * Credits:
225
 *  - (1) Alistair pointed out that curl_multi_add_handle can return CURLM_CALL_MULTI_PERFORM on success.
226
 */
227