Skip to content

Commit

Permalink
call_user_func doesn't work in this context with private or protected…
Browse files Browse the repository at this point in the history
… functions. This should solve an issue encountered when using php-vcr with the PayPal SDK (paypal/PayPal-PHP-SDK#1057).
  • Loading branch information
alnorth committed Nov 22, 2020
1 parent d280fef commit 3f13b2b
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 2 deletions.
28 changes: 26 additions & 2 deletions src/VCR/Util/CurlHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public static function handleOutput(Response $response, array $curlOptions, $ch)
$headerList = array_merge($headerList, HttpUtil::formatHeadersForCurl($response->getHeaders()));
$headerList[] = '';
foreach ($headerList as $header) {
\call_user_func($curlOptions[CURLOPT_HEADERFUNCTION], $ch, $header);
self::callFunction($curlOptions[CURLOPT_HEADERFUNCTION], $ch, $header);
}
}

Expand All @@ -69,7 +69,7 @@ public static function handleOutput(Response $response, array $curlOptions, $ch)
}

if (isset($curlOptions[CURLOPT_WRITEFUNCTION])) {
\call_user_func($curlOptions[CURLOPT_WRITEFUNCTION], $ch, $body);
self::callFunction($curlOptions[CURLOPT_WRITEFUNCTION], $ch, $body);
} elseif (isset($curlOptions[CURLOPT_RETURNTRANSFER]) && true == $curlOptions[CURLOPT_RETURNTRANSFER]) {
return $body;
} elseif (isset($curlOptions[CURLOPT_FILE])) {
Expand Down Expand Up @@ -213,4 +213,28 @@ public static function validateCurlPOSTBody(Request $request, $curlHandle = null
$body = \call_user_func_array($readFunction, [$curlHandle, fopen('php://memory', 'r'), $bodySize]);
$request->setBody($body);
}

/**
* A wrapper around call_user_func that attempts to properly handle private
* and protected methods on objects.
*
* @param mixed $callback The callable to pass to call_user_func
* @param resource $curlHandle cURL handle associated with the request
* @param mixed $argument The third argument to pass to call_user_func
*
* @return mixed value returned by the callback function
*/
private static function callFunction($callback, $curlHandle, $argument)
{
if (!\is_callable($callback) && \is_array($callback) && 2 === \count($callback)) {
// This is probably a private or protected method on an object. Try and
// make it accessible.
$method = new \ReflectionMethod($callback[0], $callback[1]);
$method->setAccessible(true);

return $method->invoke($callback[0], $curlHandle, $argument);
} else {
return \call_user_func($callback, $curlHandle, $argument);
}
}
}
115 changes: 115 additions & 0 deletions tests/VCR/Util/CurlHelperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,81 @@ public function testHandleOutputHeaderFunction()
$this->assertEquals($expected, $actualHeaders);
}

public function testHandleOutputHeaderFunctionWithPublicFunction()
{
$this->headersFound = [];
$curlOptions = [
CURLOPT_HEADERFUNCTION => [$this, 'publicCurlHeaderFunction'],
];
$status = [
'code' => 200,
'message' => 'OK',
'http_version' => '1.1',
];
$headers = [
'Content-Length' => 0,
];
$response = new Response($status, $headers, 'example response');
CurlHelper::handleOutput($response, $curlOptions, curl_init());

$expected = [
'HTTP/1.1 200 OK',
'Content-Length: 0',
'',
];
$this->assertEquals($expected, $this->headersFound);
}

public function testHandleOutputHeaderFunctionWithProtectedFunction()
{
$this->headersFound = [];
$curlOptions = [
CURLOPT_HEADERFUNCTION => [$this, 'protectedCurlHeaderFunction'],
];
$status = [
'code' => 200,
'message' => 'OK',
'http_version' => '1.1',
];
$headers = [
'Content-Length' => 0,
];
$response = new Response($status, $headers, 'example response');
CurlHelper::handleOutput($response, $curlOptions, curl_init());

$expected = [
'HTTP/1.1 200 OK',
'Content-Length: 0',
'',
];
$this->assertEquals($expected, $this->headersFound);
}

public function testHandleOutputHeaderFunctionWithPrivateFunction()
{
$this->headersFound = [];
$curlOptions = [
CURLOPT_HEADERFUNCTION => [$this, 'privateCurlHeaderFunction'],
];
$status = [
'code' => 200,
'message' => 'OK',
'http_version' => '1.1',
];
$headers = [
'Content-Length' => 0,
];
$response = new Response($status, $headers, 'example response');
CurlHelper::handleOutput($response, $curlOptions, curl_init());

$expected = [
'HTTP/1.1 200 OK',
'Content-Length: 0',
'',
];
$this->assertEquals($expected, $this->headersFound);
}

public function testHandleResponseUsesWriteFunction()
{
$test = $this;
Expand All @@ -285,6 +360,19 @@ public function testHandleResponseUsesWriteFunction()
CurlHelper::handleOutput($response, $curlOptions, $expectedCh);
}

public function testHandleResponseUsesWriteFunctionWithPrivateFunction()
{
$test = $this;
$expectedCh = curl_init();
$expectedBody = 'example response';
$curlOptions = [
CURLOPT_WRITEFUNCTION => [$this, 'privateCurlWriteFunction'],
];
$response = new Response(200, [], $expectedBody);

CurlHelper::handleOutput($response, $curlOptions, $expectedCh);
}

public function testHandleResponseWritesFile()
{
vfsStream::setup('test');
Expand Down Expand Up @@ -441,4 +529,31 @@ public function testCurlCustomRequestAlwaysOverridesMethod()

$this->assertEquals('DELETE', $request->getMethod());
}

// Function used for testing CURLOPT_HEADERFUNCTION
public function publicCurlHeaderFunction($ch, $header)
{
$this->headersFound[] = $header;
}

// Function used for testing CURLOPT_HEADERFUNCTION
protected function protectedCurlHeaderFunction($ch, $header)
{
$this->headersFound[] = $header;
}

// Function used for testing CURLOPT_HEADERFUNCTION
private function privateCurlHeaderFunction($ch, $header)
{
$this->headersFound[] = $header;
}

// Function used for testing CURLOPT_WRITEFUNCTION
private function privateCurlWriteFunction($ch, $body)
{
$this->assertEquals('resource', \gettype($ch));
$this->assertEquals('example response', $body);

return \strlen($body);
}
}

0 comments on commit 3f13b2b

Please sign in to comment.