Completed
Push — master ( 9a8fc2...1b152a )
by Jeroen van
13s queued 11s
created

CurlHook::curlError()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 0
cts 0
cp 0
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 6
1
<?php
2
3
namespace VCR\LibraryHooks;
4
5
use VCR\CodeTransform\AbstractCodeTransform;
6
use VCR\Request;
7
use VCR\Response;
8
use VCR\Util\Assertion;
9
use VCR\Util\CurlException;
10
use VCR\Util\CurlHelper;
11
use VCR\Util\StreamProcessor;
12
use VCR\Util\TextUtil;
13
14
/**
15
 * Library hook for curl functions using include-overwrite.
16
 */
17
class CurlHook implements LibraryHook
18
{
19
    /**
20
     * @var \Closure|null callback which will be executed when a request is intercepted
21
     */
22
    protected static $requestCallback;
23
24
    /**
25
     * @var string current status of this hook, either enabled or disabled
26
     */
27
    protected static $status = self::DISABLED;
28
29
    /**
30
     * @var Request[] all requests which have been intercepted
31
     */
32
    protected static $requests = [];
33
34
    /**
35
     * @var Response[] all responses which have been intercepted
36
     */
37
    protected static $responses = [];
38
39
    /**
40
     * @var array<int,mixed> additinal curl options, which are not stored within a request
41
     */
42
    protected static $curlOptions = [];
43
44
    /**
45
     * @var array<int, array> all curl handles which belong to curl_multi handles
46
     */
47
    protected static $multiHandles = [];
48
49
    /**
50
     * @var array<int, array> last active curl_multi_exec() handles
51
     */
52
    protected static $multiExecLastChs = [];
53
54
    /**
55
     * @var CurlException[] last cURL error, as a CurlException
56
     */
57
    protected static $lastErrors = [];
58
59
    /**
60
     * @var AbstractCodeTransform
61
     */
62
    private $codeTransformer;
63
64
    /**
65
     * @var StreamProcessor
66
     */
67
    private $processor;
68
69
    /**
70
     * Creates a new cURL hook instance.
71 119
     *
72
     * @throws \BadMethodCallException in case the cURL extension is not installed
73 119
     */
74
    public function __construct(AbstractCodeTransform $codeTransformer, StreamProcessor $processor)
75
    {
76
        if (!\function_exists('curl_version')) {
77
            // @codeCoverageIgnoreStart
78
            throw new \BadMethodCallException('cURL extension not installed, please disable the cURL library hook');
79
            // @codeCoverageIgnoreEnd
80 119
        }
81 119
        $this->processor = $processor;
82 119
        $this->codeTransformer = $codeTransformer;
83
    }
84
85
    /**
86
     * {@inheritdoc}
87 126
     */
88
    public function enable(\Closure $requestCallback): void
89 126
    {
90
        Assertion::isCallable($requestCallback, 'No valid callback for handling requests defined.');
91 126
92 28
        if (self::ENABLED == static::$status) {
93
            return;
94
        }
95 126
96 126
        $this->codeTransformer->register();
97 126
        $this->processor->appendCodeTransformer($this->codeTransformer);
98
        $this->processor->intercept();
99 126
100
        self::$requestCallback = $requestCallback;
101 126
102 126
        static::$status = self::ENABLED;
103
    }
104
105
    /**
106
     * {@inheritdoc}
107 133
     */
108
    public function disable(): void
109 133
    {
110 7
        if (self::DISABLED == static::$status) {
111
            return;
112
        }
113 126
114
        self::$requestCallback = null;
115 126
116 126
        static::$status = self::DISABLED;
117
    }
118
119
    /**
120
     * {@inheritdoc}
121 7
     */
122
    public function isEnabled(): bool
123 7
    {
124
        return self::ENABLED == self::$status;
125
    }
126
127
    /**
128
     * Calls the intercepted curl method if library hook is disabled, otherwise the real one.
129
     *
130
     * @param callable&string  $method cURL method to call, example: curl_info()
0 ignored issues
show
Documentation introduced by
The doc-type callable&string could not be parsed: Unknown type name "callable&string" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
131
     * @param array<int,mixed> $args   cURL arguments for this function
132
     *
133
     * @return mixed cURL function return type
134 182
     */
135
    public static function __callStatic($method, array $args)
136
    {
137 182
        // Call original when disabled
138 84
        if (self::DISABLED == static::$status) {
139
            if ('curl_multi_exec' === $method) {
140
                // curl_multi_exec expects to be called with args by reference
141 7
                // which call_user_func_array doesn't do.
142
                return curl_multi_exec($args[0], $args[1]);
143
            }
144 84
145
            return \call_user_func_array($method, $args);
146
        }
147 98
148
        if ('curl_multi_exec' === $method) {
149
            // curl_multi_exec expects to be called with args by reference
150 7
            // which call_user_func_array doesn't do.
151
            return self::curlMultiExec($args[0], $args[1]);
152
        }
153 98
154 98
        $localMethod = TextUtil::underscoreToLowerCamelcase($method);
155
156
        $callable = [__CLASS__, $localMethod];
157
158
        Assertion::isCallable($callable);
159
160
        return \call_user_func_array($callable, $args);
161
    }
162
163
    /**
164
     * Initialize a cURL session.
165 98
     *
166
     * @see http://www.php.net/manual/en/function.curl-init.php
167 98
     *
168 98
     * @param string|null $url (Optional) url
169 98
     *
170
     * @return resource|false cURL handle
171 98
     */
172
    public static function curlInit(?string $url = null)
173
    {
174
        $curlHandle = curl_init($url);
175
        if (false !== $curlHandle) {
176
            self::$requests[(int) $curlHandle] = new Request('GET', $url);
177
            self::$curlOptions[(int) $curlHandle] = [];
178
        }
179
180 7
        return $curlHandle;
181
    }
182 7
183 7
    /**
184 7
     * Reset a cURL session.
185 7
     *
186 7
     * @see http://www.php.net/manual/en/function.curl-reset.php
187
     *
188
     * @param resource $curlHandle a cURL handle returned by curl_init()
189
     */
190
    public static function curlReset($curlHandle): void
191
    {
192
        curl_reset($curlHandle);
193
        self::$requests[(int) $curlHandle] = new Request('GET', null);
194
        self::$curlOptions[(int) $curlHandle] = [];
195
        unset(self::$responses[(int) $curlHandle]);
196
    }
197
198 98
    /**
199
     * Perform a cURL session.
200 98
     *
201 98
     * @see http://www.php.net/manual/en/function.curl-exec.php
202
     *
203 98
     * @param resource $curlHandle a cURL handle returned by curl_init()
204 98
     *
205
     * @return mixed Returns TRUE on success or FALSE on failure.
206 98
     *               However, if the CURLOPT_RETURNTRANSFER option is set, it will return the
207 98
     *               result on success, FALSE on failure.
208 98
     */
209 70
    public static function curlExec($curlHandle)
210 28
    {
211
        try {
212
            $request = self::$requests[(int) $curlHandle];
213
            CurlHelper::validateCurlPOSTBody($request, $curlHandle);
214
215
            $requestCallback = self::$requestCallback;
216
            Assertion::isCallable($requestCallback);
217
            self::$responses[(int) $curlHandle] = $requestCallback($request);
218
219
            return CurlHelper::handleOutput(
220 7
                self::$responses[(int) $curlHandle],
221
                self::$curlOptions[(int) $curlHandle],
222 7
                $curlHandle
223 7
            );
224 2
        } catch (CurlException $e) {
225
            self::$lastErrors[(int) $curlHandle] = $e;
226 7
227 7
            return false;
228
        }
229
    }
230
231
    /**
232
     * Add a normal cURL handle to a cURL multi handle.
233
     *
234
     * @see http://www.php.net/manual/en/function.curl-multi-add-handle.php
235
     *
236 7
     * @param resource $multiHandle a cURL multi handle returned by curl_multi_init()
237
     * @param resource $curlHandle  a cURL handle returned by curl_init()
238 7
     */
239 7
    public static function curlMultiAddHandle($multiHandle, $curlHandle): void
240 2
    {
241 7
        if (!isset(self::$multiHandles[(int) $multiHandle])) {
242
            self::$multiHandles[(int) $multiHandle] = [];
243
        }
244
245
        self::$multiHandles[(int) $multiHandle][(int) $curlHandle] = $curlHandle;
246
    }
247
248
    /**
249
     * Remove a multi handle from a set of cURL handles.
250
     *
251
     * @see http://www.php.net/manual/en/function.curl-multi-remove-handle.php
252 7
     *
253
     * @param resource $multiHandle a cURL multi handle returned by curl_multi_init()
254 7
     * @param resource $curlHandle  a cURL handle returned by curl_init()
255 7
     */
256 7
    public static function curlMultiRemoveHandle($multiHandle, $curlHandle): void
257 7
    {
258 7
        if (isset(self::$multiHandles[(int) $multiHandle][(int) $curlHandle])) {
259 2
            unset(self::$multiHandles[(int) $multiHandle][(int) $curlHandle]);
260 2
        }
261 2
    }
262
263 7
    /**
264
     * Run the sub-connections of the current cURL handle.
265
     *
266
     * @see http://www.php.net/manual/en/function.curl-multi-exec.php
267
     *
268
     * @param resource $multiHandle  a cURL multi handle returned by curl_multi_init()
269
     * @param int      $stillRunning a reference to a flag to tell whether the operations are still running
270
     *
271
     * @return int a cURL code defined in the cURL Predefined Constants
272
     */
273 7
    public static function curlMultiExec($multiHandle, ?int &$stillRunning): int
274
    {
275 7
        if (isset(self::$multiHandles[(int) $multiHandle])) {
276
            foreach (self::$multiHandles[(int) $multiHandle] as $curlHandle) {
277 7
                if (!isset(self::$responses[(int) $curlHandle])) {
278 7
                    self::$multiExecLastChs[] = $curlHandle;
279 5
                    self::curlExec($curlHandle);
280 2
                }
281
            }
282 7
        }
283
284
        return CURLM_OK;
285 7
    }
286
287
    /**
288
     * Get information about the current transfers.
289
     *
290
     * @see http://www.php.net/manual/en/function.curl-multi-info-read.php
291
     *
292
     * @return array<string,mixed>|bool on success, returns an associative array for the message, FALSE on failure
293
     */
294
    public static function curlMultiInfoRead()
295
    {
296
        if (!empty(self::$multiExecLastChs)) {
297 28
            $info = [
298
                'msg' => CURLMSG_DONE,
299 28
                'handle' => array_pop(self::$multiExecLastChs),
300 28
                'result' => CURLE_OK,
301 20
            ];
302 8
303
            return $info;
304
        }
305
306
        return false;
307
    }
308
309
    /**
310
     * Get information regarding a specific transfer.
311
     *
312
     * @see http://www.php.net/manual/en/function.curl-getinfo.php
313
     *
314
     * @param resource $curlHandle a cURL handle returned by curl_init()
315 91
     * @param int      $option     a cURL option defined in the cURL Predefined Constants
316
     *
317 91
     * @return mixed
318
     */
319 91
    public static function curlGetinfo($curlHandle, int $option = 0)
320
    {
321 91
        if (isset(self::$responses[(int) $curlHandle])) {
322
            return CurlHelper::getCurlOptionFromResponse(
323
                self::$responses[(int) $curlHandle],
324
                $option
325
            );
326
        } elseif (isset(self::$lastErrors[(int) $curlHandle])) {
327
            return self::$lastErrors[(int) $curlHandle]->getInfo();
328
        } else {
329
            throw new \RuntimeException('Unexpected error, could not find curl_getinfo in response or errors');
330
        }
331 7
    }
332
333 7
    /**
334 7
     * Set an option for a cURL transfer.
335 7
     *
336 2
     * @see http://www.php.net/manual/en/function.curl-setopt.php
337 2
     *
338 7
     * @param resource $curlHandle a cURL handle returned by curl_init()
339
     * @param int      $option     the CURLOPT_XXX option to set
340
     * @param mixed    $value      the value to be set on option
341
     *
342
     * @return bool returns TRUE on success or FALSE on failure
343
     */
344
    public static function curlSetopt($curlHandle, int $option, $value): bool
345
    {
346
        CurlHelper::setCurlOptionOnRequest(self::$requests[(int) $curlHandle], $option, $value, $curlHandle);
347
348
        static::$curlOptions[(int) $curlHandle][$option] = $value;
349
350
        return curl_setopt($curlHandle, $option, $value);
351
    }
352
353
    /**
354
     * Set multiple options for a cURL transfer.
355
     *
356
     * @see http://www.php.net/manual/en/function.curl-setopt-array.php
357
     *
358
     * @param resource          $curlHandle a cURL handle returned by curl_init()
359
     * @param array<int, mixed> $options    an array specifying which options to set and their values
360
     */
361
    public static function curlSetoptArray($curlHandle, array $options): void
362
    {
363
        if (\is_array($options)) {
364
            foreach ($options as $option => $value) {
365
                static::curlSetopt($curlHandle, $option, $value);
366
            }
367
        }
368
    }
369
370
    /**
371
     * Return a string containing the last error for the current session.
372
     *
373
     * @see https://php.net/manual/en/function.curl-error.php
374
     *
375
     * @param resource $curlHandle
376
     *
377
     * @return string the error message or '' (the empty string) if no
378
     *                error occurred
379
     */
380
    public static function curlError($curlHandle): string
381
    {
382
        if (isset(self::$lastErrors[(int) $curlHandle])) {
383
            return self::$lastErrors[(int) $curlHandle]->getMessage();
384
        }
385
386
        return '';
387
    }
388
389
    /**
390
     * Return the last error number.
391
     *
392
     * @see https://php.net/manual/en/function.curl-errno.php
393
     *
394
     * @param resource $curlHandle
395
     *
396
     * @return int the error number or 0 (zero) if no error
397
     *             occurred
398
     */
399
    public static function curlErrno($curlHandle): int
400
    {
401
        if (isset(self::$lastErrors[(int) $curlHandle])) {
402
            return self::$lastErrors[(int) $curlHandle]->getCode();
403
        }
404
405
        return 0;
406
    }
407
}
408