Completed
Pull Request — master (#37)
by
unknown
01:26
created

MultiCurl::_startTimer()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
1
<?php 
2
namespace JMathai\PhpMultiCurl;
3
4
use JMathai\PhpMultiCurl\Manager;
5
use JMathai\PhpMultiCurl\Sequence;
6
use JMathai\PhpMultiCurl\MultiException;
7
use JMathai\PhpMultiCurl\MultiInvalidParameterException;
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
            throw new MultiException('This class cannot be instantiated by the new keyword.  You must instantiate it using: $obj = MultiCurl::getInstance();');
34
        }
35
36
        $this->_mc = curl_multi_init();
37
        $this->_properties = array(
38
        'code'  => CURLINFO_HTTP_CODE,
39
        'time'  => CURLINFO_TOTAL_TIME,
40
        'length'=> CURLINFO_CONTENT_LENGTH_DOWNLOAD,
41
        'type'  => CURLINFO_CONTENT_TYPE,
42
        'url'   => CURLINFO_EFFECTIVE_URL
43
        );
44
    }
45
  
46
    public function reset()
47
    {
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
            curl_setopt($ch, $option, $value);
59
        }
60
        return $this->addCurl($ch);
61
    }
62
63
    public function addCurl($ch)
64
    {
65
        if (gettype($ch) !== 'resource') {
66
            throw new MultiInvalidParameterException('Parameter must be a valid curl handle');
67
        }
68
69
        $key = $this->_getKey($ch);
70
        $this->_requests[$key] = $ch;
71
        curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, '_headerCallback'));
72
73
        $code = curl_multi_add_handle($this->_mc, $ch);
74
        $this->_startTimer($key);
75
    
76
        // (1)
77
        if ($code === CURLM_OK || $code === CURLM_CALL_MULTI_PERFORM) {
78
            do {
79
                $this->_execStatus = curl_multi_exec($this->_mc, $this->_running);
80
            } while ($this->_execStatus === CURLM_CALL_MULTI_PERFORM);
81
82
            return new Manager($key);
83
        } else {
84
            return $code;
85
        }
86
    }
87
88
    public function getResult($key = null)
89
    {
90
        if ($key != null) {
91
            if (isset($this->_responses[$key]['code'])) {
92
                return $this->_responses[$key];
93
            }
94
95
            $innerSleepInt = $outerSleepInt = 1;
96
            while ($this->_running && ($this->_execStatus == CURLM_OK || $this->_execStatus == CURLM_CALL_MULTI_PERFORM)) {
97
                usleep(intval($outerSleepInt));
98
                $outerSleepInt = intval(max(1, ($outerSleepInt*$this->_sleepIncrement)));
99
                $ms=curl_multi_select($this->_mc, 0);
100
101
                // bug in PHP 5.3.18+ where curl_multi_select can return -1
102
                // https://bugs.php.net/bug.php?id=63411
103
                if ($ms === -1) {
104
                    usleep(100000);
105
                }
106
107
                // see pull request https://github.com/jmathai/php-multi-curl/pull/17
108
                // details here http://curl.haxx.se/libcurl/c/libcurl-errors.html
109
                if ($ms >= CURLM_CALL_MULTI_PERFORM) {
110
                    do {
111
                        $this->_execStatus = curl_multi_exec($this->_mc, $this->_running);
112
                        usleep(intval($innerSleepInt));
113
                        $innerSleepInt = intval(max(1, ($innerSleepInt*$this->_sleepIncrement)));
114
                    } while ($this->_execStatus==CURLM_CALL_MULTI_PERFORM);
115
                    $innerSleepInt = 1;
116
                }
117
                $this->_storeResponses();
118
                if (isset($this->_responses[$key]['data'])) {
119
                    return $this->_responses[$key];
120
                }
121
            }
122
            return null;
123
        }
124
        return false;
125
    }
126
127
    public static function getSequence()
128
    {
129
        return new Sequence(self::$_timers);
130
    }
131
132
    public static function getTimers()
133
    {
134
        return self::$_timers;
135
    }
136
137
    public function inject($key, $value)
138
    {
139
        $this->$key = $value;
140
    }
141
142
    private function _getKey($ch)
143
    {
144
        return (string)$ch;
145
    }
146
147
    private function _headerCallback($ch, $header)
148
    {
149
        $_header = trim($header);
150
        $colonPos= strpos($_header, ':');
151
        if ($colonPos > 0) {
152
            $key = substr($_header, 0, $colonPos);
153
            $val = preg_replace('/^\W+/', '', substr($_header, $colonPos));
154
            $this->_responses[$this->_getKey($ch)]['headers'][$key] = $val;
155
        }
156
        return strlen($header);
157
    }
158
159
    private function _storeResponses()
160
    {
161
        while ($done = curl_multi_info_read($this->_mc)) {
162
            $this->_storeResponse($done);
163
        }
164
    }
165
166
    private function _storeResponse($done, $isAsynchronous = true)
167
    {
168
        $key = $this->_getKey($done['handle']);
169
        $this->_stopTimer($key, $done);
170
        if ($isAsynchronous) {
171
            $this->_responses[$key]['data'] = curl_multi_getcontent($done['handle']);
172
        } else {
173
            $this->_responses[$key]['data'] = curl_exec($done['handle']);
174
        }
175
176
        $this->_responses[$key]['response'] = $this->_responses[$key]['data'];
177
178
        foreach ($this->_properties as $name => $const) {
179
            $this->_responses[$key][$name] = curl_getinfo($done['handle'], $const);
180
        }
181
182
        if ($isAsynchronous) {
183
            curl_multi_remove_handle($this->_mc, $done['handle']);
184
        }
185
        curl_close($done['handle']);
186
    }
187
188
    private function _startTimer($key)
189
    {
190
        self::$_timers[$key]['start'] = microtime(true);
191
    }
192
193
    private function _stopTimer($key, $done)
194
    {
195
        self::$_timers[$key]['end'] = microtime(true);
196
        self::$_timers[$key]['api'] = curl_getinfo($done['handle'], CURLINFO_EFFECTIVE_URL);
197
        self::$_timers[$key]['time'] = curl_getinfo($done['handle'], CURLINFO_TOTAL_TIME);
198
        self::$_timers[$key]['code'] = curl_getinfo($done['handle'], CURLINFO_HTTP_CODE);
199
    }
200
201
    public static function getInstance()
202
    {
203
        if (self::$_inst == null) {
204
            self::$singleton = 1;
205
            self::$_inst = new MultiCurl();
206
        }
207
        
208
        return self::$_inst;
209
    }
210
}
211
212
/*
213
 * Credits:
214
 *  - (1) Alistair pointed out that curl_multi_add_handle can return CURLM_CALL_MULTI_PERFORM on success.
215
 */
216