guzzle/guzzle — pull request #439 by adlawson (2013-10-15 22:47:56+00:00)
Title: Fix cURL progress callback in PHP 5.5

Using the async plugin triggers these warnings:
```
PHP Warning: curl_setopt() expects parameter 1 to be resource, integer given in /path/to/guzzle/src/Guzzle/Plugin/Async/AsyncPlugin.php on line 49
PHP Warning: curl_setopt() expects parameter 1 to be resource, integer given in /path/to/guzzle/src/Guzzle/Plugin/Async/AsyncPlugin.php on line 51
```

So I start digging: [`Guzzle/Plugin/Async/AsyncPlugin.php`][code-async]
```php
public function onCurlProgress(Event $event)
{
if ($event['handle'] &&
($event['downloaded'] || ($event['uploaded'] && $event['upload_size'] === $event['uploaded']))
) {
// Timeout after 1ms
curl_setopt($event['handle'], CURLOPT_TIMEOUT_MS, 1);
// Even if the response is quick, tell curl not to download the body
curl_setopt($event['handle'], CURLOPT_NOBODY, true);
}
}
```

Inspecting the `$event['handler']`, I see a value of `1357`, although oddly, `$event['download_size']` is a `resource`.

#### Hmm? That doesn't make sense.

Walking back through the stack, [`Guzzle/Http/Curl/RequestMediator.php`][code-mediator]
```php
public function progress($downloadSize, $downloaded, $uploadSize, $uploaded, $handle = null)
{
$this->request->dispatch('curl.callback.progress', array(
'request' => $this->request,
'handle' => $handle,
'download_size' => $downloadSize,
'downloaded' => $downloaded,
'upload_size' => $uploadSize,
'uploaded' => $uploaded
));
}
```

All of the arguments are dispatched correctly, but sure enough, `$downloadSize` is a `resource`.

#### Keep falling down the rabbit hole...

We end up at [`Guzzle/Http/Curl/CurlHandle.php`][code-handle] Line ~ 197
```php
if ($requestCurlOptions->get('progress')) {
// Wrap the function in a function that provides the curl handle to the mediator's progress function
// Using this rather than injecting the handle into the mediator prevents a circular reference
$curlOptions[CURLOPT_PROGRESSFUNCTION] = function () use ($mediator, $handle) {
$args = func_get_args();
$args[] = $handle;
call_user_func_array(array($mediator, 'progress'), $args);
};
$curlOptions[CURLOPT_NOPROGRESS] = false;
}
```

This is where the handle is pushed onto the array of event args and the event is dispatched. Inspecting `$args` just before `call_user_func_array` looks something like this:
```
[0] => Resource id #304 // cURL handle
[1] => 2 // download size
[2] => 2 // downloaded
[3] => 1357 // upload size
[4] => 1357 // uploaded
[5] => Resource id #304 // cURL handle
```

#### The handle is at the start *and* the end of the array!
The handle was already passed in, and doing the extra `$args[] = $handle` just added it again. Looking at the [docs at PHP.net][curl-opts], it makes complete sense

> CURLOPT_PROGRESSFUNCTION: A callback accepting five parameters. The first is the cURL resource...

This change was introduced in PHP 5.5, as seen just below the table

> 5.5.0 Added the cURL resource as the first argument to the CURLOPT_PROGRESSFUNCTION callback.

#### Damn you, progression!

My fix simply checks the first element in the array and removes it if it's a resource.

[code-async]: /guzzle/guzzle/blob/master/src/Guzzle/Plugin/Async/AsyncPlugin.php#L43-L53
[code-mediator]: /guzzle/guzzle/blob/master/src/Guzzle/Http/Curl/RequestMediator.php#L88-L98
[code-handle]: /guzzle/guzzle/blob/master/src/Guzzle/Http/Curl/CurlHandle.php#L197-L206
[curl-opts]: http://www.php.net/manual/en/function.curl-setopt.php