Skip to content

Commit 3f13b2b

Browse files
committed
call_user_func doesn't work in this context with private or protected functions. This should solve an issue encountered when using php-vcr with the PayPal SDK (paypal/PayPal-PHP-SDK#1057).
1 parent d280fef commit 3f13b2b

File tree

2 files changed

+141
-2
lines changed

2 files changed

+141
-2
lines changed

src/VCR/Util/CurlHelper.php

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public static function handleOutput(Response $response, array $curlOptions, $ch)
5858
$headerList = array_merge($headerList, HttpUtil::formatHeadersForCurl($response->getHeaders()));
5959
$headerList[] = '';
6060
foreach ($headerList as $header) {
61-
\call_user_func($curlOptions[CURLOPT_HEADERFUNCTION], $ch, $header);
61+
self::callFunction($curlOptions[CURLOPT_HEADERFUNCTION], $ch, $header);
6262
}
6363
}
6464

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

7171
if (isset($curlOptions[CURLOPT_WRITEFUNCTION])) {
72-
\call_user_func($curlOptions[CURLOPT_WRITEFUNCTION], $ch, $body);
72+
self::callFunction($curlOptions[CURLOPT_WRITEFUNCTION], $ch, $body);
7373
} elseif (isset($curlOptions[CURLOPT_RETURNTRANSFER]) && true == $curlOptions[CURLOPT_RETURNTRANSFER]) {
7474
return $body;
7575
} elseif (isset($curlOptions[CURLOPT_FILE])) {
@@ -213,4 +213,28 @@ public static function validateCurlPOSTBody(Request $request, $curlHandle = null
213213
$body = \call_user_func_array($readFunction, [$curlHandle, fopen('php://memory', 'r'), $bodySize]);
214214
$request->setBody($body);
215215
}
216+
217+
/**
218+
* A wrapper around call_user_func that attempts to properly handle private
219+
* and protected methods on objects.
220+
*
221+
* @param mixed $callback The callable to pass to call_user_func
222+
* @param resource $curlHandle cURL handle associated with the request
223+
* @param mixed $argument The third argument to pass to call_user_func
224+
*
225+
* @return mixed value returned by the callback function
226+
*/
227+
private static function callFunction($callback, $curlHandle, $argument)
228+
{
229+
if (!\is_callable($callback) && \is_array($callback) && 2 === \count($callback)) {
230+
// This is probably a private or protected method on an object. Try and
231+
// make it accessible.
232+
$method = new \ReflectionMethod($callback[0], $callback[1]);
233+
$method->setAccessible(true);
234+
235+
return $method->invoke($callback[0], $curlHandle, $argument);
236+
} else {
237+
return \call_user_func($callback, $curlHandle, $argument);
238+
}
239+
}
216240
}

tests/VCR/Util/CurlHelperTest.php

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,81 @@ public function testHandleOutputHeaderFunction()
267267
$this->assertEquals($expected, $actualHeaders);
268268
}
269269

270+
public function testHandleOutputHeaderFunctionWithPublicFunction()
271+
{
272+
$this->headersFound = [];
273+
$curlOptions = [
274+
CURLOPT_HEADERFUNCTION => [$this, 'publicCurlHeaderFunction'],
275+
];
276+
$status = [
277+
'code' => 200,
278+
'message' => 'OK',
279+
'http_version' => '1.1',
280+
];
281+
$headers = [
282+
'Content-Length' => 0,
283+
];
284+
$response = new Response($status, $headers, 'example response');
285+
CurlHelper::handleOutput($response, $curlOptions, curl_init());
286+
287+
$expected = [
288+
'HTTP/1.1 200 OK',
289+
'Content-Length: 0',
290+
'',
291+
];
292+
$this->assertEquals($expected, $this->headersFound);
293+
}
294+
295+
public function testHandleOutputHeaderFunctionWithProtectedFunction()
296+
{
297+
$this->headersFound = [];
298+
$curlOptions = [
299+
CURLOPT_HEADERFUNCTION => [$this, 'protectedCurlHeaderFunction'],
300+
];
301+
$status = [
302+
'code' => 200,
303+
'message' => 'OK',
304+
'http_version' => '1.1',
305+
];
306+
$headers = [
307+
'Content-Length' => 0,
308+
];
309+
$response = new Response($status, $headers, 'example response');
310+
CurlHelper::handleOutput($response, $curlOptions, curl_init());
311+
312+
$expected = [
313+
'HTTP/1.1 200 OK',
314+
'Content-Length: 0',
315+
'',
316+
];
317+
$this->assertEquals($expected, $this->headersFound);
318+
}
319+
320+
public function testHandleOutputHeaderFunctionWithPrivateFunction()
321+
{
322+
$this->headersFound = [];
323+
$curlOptions = [
324+
CURLOPT_HEADERFUNCTION => [$this, 'privateCurlHeaderFunction'],
325+
];
326+
$status = [
327+
'code' => 200,
328+
'message' => 'OK',
329+
'http_version' => '1.1',
330+
];
331+
$headers = [
332+
'Content-Length' => 0,
333+
];
334+
$response = new Response($status, $headers, 'example response');
335+
CurlHelper::handleOutput($response, $curlOptions, curl_init());
336+
337+
$expected = [
338+
'HTTP/1.1 200 OK',
339+
'Content-Length: 0',
340+
'',
341+
];
342+
$this->assertEquals($expected, $this->headersFound);
343+
}
344+
270345
public function testHandleResponseUsesWriteFunction()
271346
{
272347
$test = $this;
@@ -285,6 +360,19 @@ public function testHandleResponseUsesWriteFunction()
285360
CurlHelper::handleOutput($response, $curlOptions, $expectedCh);
286361
}
287362

363+
public function testHandleResponseUsesWriteFunctionWithPrivateFunction()
364+
{
365+
$test = $this;
366+
$expectedCh = curl_init();
367+
$expectedBody = 'example response';
368+
$curlOptions = [
369+
CURLOPT_WRITEFUNCTION => [$this, 'privateCurlWriteFunction'],
370+
];
371+
$response = new Response(200, [], $expectedBody);
372+
373+
CurlHelper::handleOutput($response, $curlOptions, $expectedCh);
374+
}
375+
288376
public function testHandleResponseWritesFile()
289377
{
290378
vfsStream::setup('test');
@@ -441,4 +529,31 @@ public function testCurlCustomRequestAlwaysOverridesMethod()
441529

442530
$this->assertEquals('DELETE', $request->getMethod());
443531
}
532+
533+
// Function used for testing CURLOPT_HEADERFUNCTION
534+
public function publicCurlHeaderFunction($ch, $header)
535+
{
536+
$this->headersFound[] = $header;
537+
}
538+
539+
// Function used for testing CURLOPT_HEADERFUNCTION
540+
protected function protectedCurlHeaderFunction($ch, $header)
541+
{
542+
$this->headersFound[] = $header;
543+
}
544+
545+
// Function used for testing CURLOPT_HEADERFUNCTION
546+
private function privateCurlHeaderFunction($ch, $header)
547+
{
548+
$this->headersFound[] = $header;
549+
}
550+
551+
// Function used for testing CURLOPT_WRITEFUNCTION
552+
private function privateCurlWriteFunction($ch, $body)
553+
{
554+
$this->assertEquals('resource', \gettype($ch));
555+
$this->assertEquals('example response', $body);
556+
557+
return \strlen($body);
558+
}
444559
}

0 commit comments

Comments
 (0)