/
CurlRunkit.php
214 lines (179 loc) · 6.19 KB
/
CurlRunkit.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
<?php
namespace VCR\LibraryHooks;
use VCR\Request;
use VCR\Response;
use VCR\Assertion;
use VCR\Util\CurlHelper;
/**
* Library hook for curl functions.
*/
class CurlRunkit implements LibraryHookInterface
{
/**
* @var \Closure Callback which will be executed when a request is intercepted.
*/
protected static $handleRequestCallback;
/**
* @var string Current status of this hook, either enabled or disabled.
*/
protected static $status = self::DISABLED;
/**
* @var Request[] All requests which have been intercepted.
*/
protected static $requests = array();
/**
* @var Response[] All responses which have been intercepted.
*/
protected static $responses = array();
/**
* @var array[] Additinal curl options, which are not stored within a request.
*/
protected static $curlOptions = array();
/**
* All curl handles which belong to curl_multi handles.
*/
protected static $multiHandles = array();
protected static $multiExecLastCh;
/**
* @var array Defines curl functions to overwrite with method calls to this class.
*/
protected static $overwriteFunctions = array(
'curl_init' => array('$url = null', 'init($url)'),
'curl_exec' => array('$resource', 'exec($resource)'),
'curl_getinfo' => array('$resource, $option = 0', 'getInfo($resource, $option)'),
'curl_setopt' => array('$ch, $option, $value', 'setOpt($ch, $option, $value)'),
'curl_setopt_array' => array('$ch, $options', 'setOptArray($ch, $options)'),
'curl_multi_add_handle' => array('$mh, $ch', 'multiAddHandle($mh, $ch)'),
'curl_multi_remove_handle' => array('$mh, $ch', 'multiRemoveHandle($mh, $ch)'),
'curl_multi_exec' => array('$mh, &$still_running', 'multiExec($mh, $still_running)'),
'curl_multi_info_read' => array('$mh', 'multiInfoRead($mh)')
);
/**
* Initializes a new curl libraryhook using ext-runkit.
*
* @throws \BadMethodCallException When runkit is not available.
*/
public function __construct()
{
if (!function_exists('runkit_function_redefine')) {
throw new \BadMethodCallException('For curl support you need to install runkit extension.');
}
self::$handleRequestCallback = null;
}
/**
* @inheritdoc
*/
public function enable(\Closure $handleRequestCallback)
{
Assertion::isCallable($handleRequestCallback, 'No valid callback for handling requests defined.');
self::$handleRequestCallback = $handleRequestCallback;
if (self::$status == self::ENABLED) {
return;
}
foreach (self::$overwriteFunctions as $functionName => $mapping) {
runkit_function_rename($functionName, $functionName . '_original');
if (function_exists($functionName . '_temp')) {
runkit_function_rename($functionName . '_temp', $functionName);
} else {
runkit_function_add($functionName, $mapping[0], 'return ' . __CLASS__ . '::' . $mapping[1] . ';');
}
}
self::$status = self::ENABLED;
}
/**
* @inheritdoc
*/
public function disable()
{
if (self::$status == self::DISABLED) {
return;
}
foreach (self::$overwriteFunctions as $functionName => $mapping) {
runkit_function_rename($functionName, $functionName . '_temp');
runkit_function_rename($functionName . '_original', $functionName);
}
self::$status = self::DISABLED;
self::$handleRequestCallback = null;
}
public static function init($url = null)
{
$ch = \curl_init_original($url);
self::$requests[(int) $ch] = new Request('GET', $url);
return $ch;
}
public static function multiAddHandle($mh, $ch)
{
if (isset(self::$multiHandles[(int) $mh])) {
self::$multiHandles[(int) $mh][] = (int) $ch;
} else {
self::$multiHandles[(int) $mh] = array((int) $ch);
}
}
public static function multiRemoveHandle($mh, $ch)
{
if (isset(self::$multiHandles[(int) $mh][(int) $ch])) {
unset(self::$multiHandles[(int) $mh][(int) $ch]);
}
}
public static function multiExec($mh, &$still_running)
{
if (isset(self::$multiHandles[(int) $mh])) {
foreach (self::$multiHandles[(int) $mh] as $ch) {
if (!isset(self::$responses[(int) $ch])) {
self::$multiExecLastCh = $ch;
self::exec($ch);
}
}
}
return CURLM_OK;
}
public static function multiInfoRead($mh)
{
if (self::$multiExecLastCh) {
$info = array(
'msg' => CURLMSG_DONE,
'handle' => self::$multiExecLastCh,
'result' => CURLE_OK
);
self::$multiExecLastCh = null;
return $info;
}
return false;
}
public static function exec($ch)
{
$handleRequestCallback = self::$handleRequestCallback;
self::$responses[(int) $ch] = $handleRequestCallback(self::$requests[(int) $ch]);
return CurlHelper::handleOutput(
self::$responses[(int) $ch],
self::$curlOptions[(int) $ch],
$ch
);
}
public static function getInfo($ch, $option = 0)
{
return CurlHelper::getCurlOptionFromResponse(
self::$responses[(int) $ch],
$option
);
}
public static function setOpt($ch, $option, $value)
{
CurlHelper::setCurlOptionOnRequest(self::$requests[(int) $ch], $option, $value);
if (!isset(static::$curlOptions[(int) $ch])) {
static::$curlOptions[(int) $ch] = array();
}
static::$curlOptions[(int) $ch][$option] = $value;
\curl_setopt_original($ch, $option, $value);
}
public static function setOptArray($ch, $options)
{
foreach ($options as $option => $value) {
static::setOpt($ch, $option, $value);
}
}
public function __destruct()
{
self::$handleRequestCallback = null;
}
}