From 8a782556eb02dec7c9d8482556199faa854c2a27 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 30 Jun 2015 19:32:40 +0200 Subject: [PATCH] [2.6] Towards 100% HHVM compat --- .../Component/HttpFoundation/JsonResponse.php | 66 +++++++++++++------ .../Component/HttpFoundation/Response.php | 10 +-- .../HttpFoundation/Tests/JsonResponseTest.php | 9 +-- .../Tests/ResponseHeaderBagTest.php | 4 +- .../VarDumper/Tests/Caster/PdoCasterTest.php | 6 +- .../VarDumper/Tests/CliDumperTest.php | 61 +++++++++++++---- .../VarDumper/Tests/Fixtures/dumb-var.php | 5 +- .../VarDumper/Tests/HtmlDumperTest.php | 15 ++--- .../VarDumper/Tests/VarClonerTest.php | 6 +- 9 files changed, 114 insertions(+), 68 deletions(-) diff --git a/src/Symfony/Component/HttpFoundation/JsonResponse.php b/src/Symfony/Component/HttpFoundation/JsonResponse.php index b941b46e84..5399d1b1f1 100644 --- a/src/Symfony/Component/HttpFoundation/JsonResponse.php +++ b/src/Symfony/Component/HttpFoundation/JsonResponse.php @@ -95,34 +95,58 @@ class JsonResponse extends Response */ public function setData($data = array()) { - $errorHandler = null; - $errorHandler = set_error_handler(function () use (&$errorHandler) { - if (JSON_ERROR_NONE !== json_last_error()) { - return; + if (defined('HHVM_VERSION')) { + // HHVM does not trigger any warnings and let exceptions + // thrown from a JsonSerializable object pass through. + // If only PHP did the same... + $data = json_encode($data, $this->encodingOptions); + } else { + try { + if (PHP_VERSION_ID < 50400) { + // PHP 5.3 triggers annoying warnings for some + // types that can't be serialized as JSON (INF, resources, etc.) + // but doesn't provide the JsonSerializable interface. + set_error_handler('var_dump', 0); + $data = @json_encode($data, $this->encodingOptions); + } else { + // PHP 5.4 and up wrap exceptions thrown by JsonSerializable + // objects in a new exception that needs to be removed. + // Fortunately, PHP 5.5 and up do not trigger any warning anymore. + if (PHP_VERSION_ID < 50500) { + // Clear json_last_error() + json_encode(null); + $errorHandler = set_error_handler('var_dump'); + restore_error_handler(); + set_error_handler(function () use ($errorHandler) { + if (JSON_ERROR_NONE === json_last_error()) { + return $errorHandler && false !== call_user_func_array($errorHandler, func_get_args()); + } + }); + } + + $data = json_encode($data, $this->encodingOptions); + } + + if (PHP_VERSION_ID < 50500) { + restore_error_handler(); + } + } catch (\Exception $e) { + if (PHP_VERSION_ID < 50500) { + restore_error_handler(); + } + if (PHP_VERSION_ID >= 50400 && 'Exception' === get_class($e) && 0 === strpos($e->getMessage(), 'Failed calling ')) { + throw $e->getPrevious() ?: $e; + } + throw $e; } - - if ($errorHandler) { - call_user_func_array($errorHandler, func_get_args()); - } - }); - - try { - // Clear json_last_error() - json_encode(null); - - $this->data = json_encode($data, $this->encodingOptions); - - restore_error_handler(); - } catch (\Exception $exception) { - restore_error_handler(); - - throw $exception; } if (JSON_ERROR_NONE !== json_last_error()) { throw new \InvalidArgumentException($this->transformJsonError()); } + $this->data = $data; + return $this->update(); } diff --git a/src/Symfony/Component/HttpFoundation/Response.php b/src/Symfony/Component/HttpFoundation/Response.php index 6641e51023..dc7203a8d1 100644 --- a/src/Symfony/Component/HttpFoundation/Response.php +++ b/src/Symfony/Component/HttpFoundation/Response.php @@ -1242,15 +1242,9 @@ class Response { $status = ob_get_status(true); $level = count($status); + $flags = PHP_VERSION_ID >= 50400 ? PHP_OUTPUT_HANDLER_REMOVABLE | ($flush ? PHP_OUTPUT_HANDLER_FLUSHABLE : PHP_OUTPUT_HANDLER_CLEANABLE) : -1; - while ($level-- > $targetLevel - && (!empty($status[$level]['del']) - || (isset($status[$level]['flags']) - && ($status[$level]['flags'] & PHP_OUTPUT_HANDLER_REMOVABLE) - && ($status[$level]['flags'] & ($flush ? PHP_OUTPUT_HANDLER_FLUSHABLE : PHP_OUTPUT_HANDLER_CLEANABLE)) - ) - ) - ) { + while ($level-- > $targetLevel && ($s = $status[$level]) && (!isset($s['del']) ? !isset($s['flags']) || $flags === ($s['flags'] & $flags) : $s['del'])) { if ($flush) { ob_end_flush(); } else { diff --git a/src/Symfony/Component/HttpFoundation/Tests/JsonResponseTest.php b/src/Symfony/Component/HttpFoundation/Tests/JsonResponseTest.php index 48b86038a7..f1ea565700 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/JsonResponseTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/JsonResponseTest.php @@ -203,9 +203,8 @@ class JsonResponseTest extends \PHPUnit_Framework_TestCase } /** - * @expectedException Exception - * @expectedExceptionMessage Failed calling Symfony\Component\HttpFoundation\Tests\JsonSerializableObject::jsonSerialize() - * @link http://php.net/manual/en/jsonserializable.jsonserialize.php#114688 + * @expectedException \Exception + * @expectedExceptionMessage This error is expected */ public function testSetContentJsonSerializeError() { @@ -224,9 +223,7 @@ if (interface_exists('JsonSerializable')) { { public function jsonSerialize() { - trigger_error('This error is expected', E_USER_WARNING); - - return array(); + throw new \Exception('This error is expected'); } } } diff --git a/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php b/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php index ec739b592b..d5596f746c 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php @@ -118,7 +118,7 @@ class ResponseHeaderBagTest extends \PHPUnit_Framework_TestCase $bag->clearCookie('foo'); - $this->assertContains('Set-Cookie: foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; path=/; httponly', explode("\r\n", $bag->__toString())); + $this->assertRegExp('#^Set-Cookie: foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; path=/; httponly#m', $bag->__toString()); } public function testClearCookieSecureNotHttpOnly() @@ -127,7 +127,7 @@ class ResponseHeaderBagTest extends \PHPUnit_Framework_TestCase $bag->clearCookie('foo', '/', null, true, false); - $this->assertContains("Set-Cookie: foo=deleted; expires=".gmdate("D, d-M-Y H:i:s T", time() - 31536001)."; path=/; secure", explode("\r\n", $bag->__toString())); + $this->assertRegExp('#^Set-Cookie: foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; path=/; secure#m', $bag->__toString()); } public function testReplace() diff --git a/src/Symfony/Component/VarDumper/Tests/Caster/PdoCasterTest.php b/src/Symfony/Component/VarDumper/Tests/Caster/PdoCasterTest.php index eaed9dbb16..445b712ec9 100644 --- a/src/Symfony/Component/VarDumper/Tests/Caster/PdoCasterTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Caster/PdoCasterTest.php @@ -45,13 +45,11 @@ class PdoCasterTest extends \PHPUnit_Framework_TestCase 'ORACLE_NULLS' => $attr['ORACLE_NULLS'], 'CLIENT_VERSION' => $pdo->getAttribute(\PDO::ATTR_CLIENT_VERSION), 'SERVER_VERSION' => $pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), - 'STATEMENT_CLASS' => array( - 'PDOStatement', - array($pdo), - ), + 'STATEMENT_CLASS' => array('PDOStatement'), 'DEFAULT_FETCH_MODE' => $attr['DEFAULT_FETCH_MODE'], ), ); + unset($cast["\0~\0attributes"]['STATEMENT_CLASS'][1]); $this->assertSame($xCast, $cast); } diff --git a/src/Symfony/Component/VarDumper/Tests/CliDumperTest.php b/src/Symfony/Component/VarDumper/Tests/CliDumperTest.php index 2b860fdc97..899b2fb09e 100644 --- a/src/Symfony/Component/VarDumper/Tests/CliDumperTest.php +++ b/src/Symfony/Component/VarDumper/Tests/CliDumperTest.php @@ -28,7 +28,7 @@ class CliDumperTest extends \PHPUnit_Framework_TestCase $cloner = new VarCloner(); $cloner->addCasters(array( ':stream' => function ($res, $a) { - unset($a['uri']); + unset($a['uri'], $a['wrapper_data']); return $a; }, @@ -40,12 +40,12 @@ class CliDumperTest extends \PHPUnit_Framework_TestCase $out = ob_get_clean(); $out = preg_replace('/[ \t]+$/m', '', $out); $intMax = PHP_INT_MAX; - $res1 = (int) $var['res']; - $res2 = (int) $var[8]; + $res = (int) $var['res']; + $r = defined('HHVM_VERSION') ? '' : '#%d'; $this->assertStringMatchesFormat( << 1 0 => &1 null "const" => 1.1 @@ -58,7 +58,7 @@ array:25 [ "str" => "déjà\\n" 7 => b"é\\x00" "[]" => [] - "res" => stream resource {@{$res1} + "res" => stream resource {@{$res} wrapper_type: "plainfile" stream_type: "STDIO" mode: "r" @@ -69,12 +69,11 @@ array:25 [ eof: false options: [] } - 8 => Unknown resource @{$res2} "obj" => Symfony\Component\VarDumper\Tests\Fixture\DumbFoo {#%d +foo: "foo" +"bar": "bar" } - "closure" => Closure {#%d + "closure" => Closure {{$r} reflection: """ Closure [ %s Symfony\Component\VarDumper\Tests\Fixture\{closure} ] {\\n @@ {$var['file']} {$var['line']} - {$var['line']}\\n @@ -93,7 +92,7 @@ array:25 [ "recurs" => &4 array:1 [ 0 => &4 array:1 [&4] ] - 9 => &1 null + 8 => &1 null "sobj" => Symfony\Component\VarDumper\Tests\Fixture\DumbFoo {#%d} "snobj" => &3 {#%d} "snobj2" => {#%d} @@ -101,6 +100,35 @@ array:25 [ b"bin-key-é" => "" ] +EOTXT + , + $out + ); + } + + public function testClosedResource() + { + if (defined('HHVM_VERSION') && HHVM_VERSION_ID < 30600) { + $this->markTestSkipped(); + } + + $var = fopen(__FILE__, 'r'); + fclose($var); + + $dumper = new CliDumper('php://output'); + $dumper->setColors(false); + $cloner = new VarCloner(); + $data = $cloner->cloneVar($var); + + ob_start(); + $dumper->dump($data); + $out = ob_get_clean(); + $res = (int) $var; + + $this->assertStringMatchesFormat( + <<setColors(false); $cloner = new VarCloner(); + $cloner->addCasters(array( + ':stream' => function ($res, $a) { + unset($a['wrapper_data']); + + return $a; + }, + )); $cloner->addCasters(array( ':stream' => function () { throw new \Exception('Foobar'); @@ -128,12 +163,13 @@ EOTXT rewind($out); $out = stream_get_contents($out); + $r = defined('HHVM_VERSION') ? '' : '#%d'; $this->assertStringMatchesFormat( << array:2 [ - "call" => "%s{closure}()" + "call" => "%slosure%s()" "file" => "{$file}:{$line}" ] ] @@ -173,9 +209,10 @@ EOTXT rewind($out); $out = stream_get_contents($out); + $r = defined('HHVM_VERSION') ? '' : '#%d'; $this->assertStringMatchesFormat( <<bar = 'bar'; $g = fopen(__FILE__, 'r'); -$h = fopen(__FILE__, 'r'); -fclose($h); $var = array( 'number' => 1, null, @@ -22,7 +20,6 @@ $var = array( 'str' => "déjà\n", "\xE9\x00", '[]' => array(), 'res' => $g, - $h, 'obj' => $foo, 'closure' => function ($a, \PDO &$b = null) {}, 'line' => __LINE__ - 1, @@ -40,4 +37,4 @@ $var['snobj2'] = $var['nobj'][0]; $var['file'] = __FILE__; $var["bin-key-\xE9"] = ''; -unset($g, $h, $r); +unset($g, $r); diff --git a/src/Symfony/Component/VarDumper/Tests/HtmlDumperTest.php b/src/Symfony/Component/VarDumper/Tests/HtmlDumperTest.php index b0e21ffe31..16dd4dc06a 100644 --- a/src/Symfony/Component/VarDumper/Tests/HtmlDumperTest.php +++ b/src/Symfony/Component/VarDumper/Tests/HtmlDumperTest.php @@ -29,7 +29,7 @@ class HtmlDumperTest extends \PHPUnit_Framework_TestCase $cloner = new VarCloner(); $cloner->addCasters(array( ':stream' => function ($res, $a) { - unset($a['uri']); + unset($a['uri'], $a['wrapper_data']); return $a; }, @@ -44,12 +44,12 @@ class HtmlDumperTest extends \PHPUnit_Framework_TestCase $intMax = PHP_INT_MAX; preg_match('/sf-dump-\d+/', $out, $dumpId); $dumpId = $dumpId[0]; - $res1 = (int) $var['res']; - $res2 = (int) $var[8]; + $res = (int) $var['res']; + $r = defined('HHVM_VERSION') ? '' : '#%d'; $this->assertStringMatchesFormat( <<array:25 [ +array:24 [ "number" => 1 0 => &1 null "const" => 1.1 @@ -62,7 +62,7 @@ class HtmlDumperTest extends \PHPUnit_Framework_TestCase "str" => "déjà\\n" 7 => b"é\\x00" "[]" => [] - "res" => stream resource @{$res1} + "res" => stream resource @{$res} wrapper_type: "plainfile" stream_type: "STDIO" mode: "r" @@ -73,12 +73,11 @@ class HtmlDumperTest extends \PHPUnit_Framework_TestCase eof: false options: [] } - 8 => Unknown resource @{$res2} "obj" => DumbFoo {#%d +foo: "foo" +"bar": "bar" } - "closure" => Closure {#%d + "closure" => Closure {{$r} reflection: """ Closure [ <user%S> %s Symfony\Component\VarDumper\Tests\Fixture\{closure} ] {\\n @@ {$var['file']} {$var['line']} - {$var['line']}\\n @@ -97,7 +96,7 @@ class HtmlDumperTest extends \PHPUnit_Framework_TestCase "recurs" => &4 array:1 [ 0 => &4 array:1 [&4] ] - 9 => &1 null + 8 => &1 null "sobj" => DumbFoo {#%d} "snobj" => &3 {#%d} "snobj2" => {#%d} diff --git a/src/Symfony/Component/VarDumper/Tests/VarClonerTest.php b/src/Symfony/Component/VarDumper/Tests/VarClonerTest.php index 1bb9410036..c086c9a7cb 100644 --- a/src/Symfony/Component/VarDumper/Tests/VarClonerTest.php +++ b/src/Symfony/Component/VarDumper/Tests/VarClonerTest.php @@ -81,7 +81,7 @@ Symfony\Component\VarDumper\Cloner\Data Object [class] => stdClass [value] => [cut] => 0 - [handle] => %d + [handle] => %i [refCount] => 0 [position] => 1 ) @@ -96,7 +96,7 @@ Symfony\Component\VarDumper\Cloner\Data Object [class] => stdClass [value] => [cut] => 0 - [handle] => %d + [handle] => %i [refCount] => 0 [position] => 2 ) @@ -107,7 +107,7 @@ Symfony\Component\VarDumper\Cloner\Data Object [class] => stdClass [value] => [cut] => 0 - [handle] => %d + [handle] => %i [refCount] => 0 [position] => 3 )