bug #15159 [2.6] Towards 100% HHVM compat (nicolas-grekas)

This PR was merged into the 2.6 branch.

Discussion
----------

[2.6] Towards 100% HHVM compat

| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #9917, #11418
| License       | MIT
| Doc PR        | -

Failing components:
- [x] HttpFoundation
- [x] HttpKernel
- [x] VarDumper

Related HHVM issues:
- https://github.com/facebook/hhvm/issues/5563

Commits
-------

8a78255 [2.6] Towards 100% HHVM compat
This commit is contained in:
Fabien Potencier 2015-07-01 13:18:19 +02:00
commit a83487821f
9 changed files with 114 additions and 68 deletions

View File

@ -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();
}

View File

@ -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 {

View File

@ -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');
}
}
}

View File

@ -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()

View File

@ -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);
}

View File

@ -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(
<<<EOTXT
array:25 [
array:24 [
"number" => 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 [ <user%S> %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(
<<<EOTXT
Unknown resource @{$res}
EOTXT
,
$out
@ -114,6 +142,13 @@ EOTXT
$dumper = new CliDumper();
$dumper->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(
<<<EOTXT
stream resource {@{$ref}
wrapper_type: "PHP"
stream_type: "MEMORY"
mode: "w+b"
mode: "%s+b"
unread_bytes: 0
seekable: true
uri: "php://memory"
@ -141,11 +177,11 @@ stream resource {@{$ref}
blocked: true
eof: false
options: []
: Symfony\Component\VarDumper\Exception\ThrowingCasterException {#%d
: Symfony\Component\VarDumper\Exception\ThrowingCasterException {{$r}
#message: "Unexpected Exception thrown from a caster: Foobar"
trace: array:1 [
0 => 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(
<<<EOTXT
{#%d
{{$r}
+"foo": &1 "foo"
+"bar": &1 "foo"
}

View File

@ -13,8 +13,6 @@ $foo = new DumbFoo();
$foo->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);

View File

@ -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') ? '' : '<a class=sf-dump-ref>#%d</a>';
$this->assertStringMatchesFormat(
<<<EOTXT
<foo></foo><bar><span class=sf-dump-note>array:25</span> [<samp>
<foo></foo><bar><span class=sf-dump-note>array:24</span> [<samp>
"<span class=sf-dump-key>number</span>" => <span class=sf-dump-num>1</span>
<span class=sf-dump-key>0</span> => <a class=sf-dump-ref href=#{$dumpId}-ref01 title="2 occurrences">&amp;1</a> <span class=sf-dump-const>null</span>
"<span class=sf-dump-key>const</span>" => <span class=sf-dump-num>1.1</span>
@ -62,7 +62,7 @@ class HtmlDumperTest extends \PHPUnit_Framework_TestCase
"<span class=sf-dump-key>str</span>" => "<span class=sf-dump-str title="5 characters">d&#233;j&#224;</span>\\n"
<span class=sf-dump-key>7</span> => b"<span class=sf-dump-str title="2 binary or non-UTF-8 characters">&#233;</span>\\x00"
"<span class=sf-dump-key>[]</span>" => []
"<span class=sf-dump-key>res</span>" => <span class=sf-dump-note>stream resource</span> <a class=sf-dump-ref>@{$res1}</a><samp>
"<span class=sf-dump-key>res</span>" => <span class=sf-dump-note>stream resource</span> <a class=sf-dump-ref>@{$res}</a><samp>
<span class=sf-dump-meta>wrapper_type</span>: "<span class=sf-dump-str title="9 characters">plainfile</span>"
<span class=sf-dump-meta>stream_type</span>: "<span class=sf-dump-str title="5 characters">STDIO</span>"
<span class=sf-dump-meta>mode</span>: "<span class=sf-dump-str>r</span>"
@ -73,12 +73,11 @@ class HtmlDumperTest extends \PHPUnit_Framework_TestCase
<span class=sf-dump-meta>eof</span>: <span class=sf-dump-const>false</span>
<span class=sf-dump-meta>options</span>: []
</samp>}
<span class=sf-dump-key>8</span> => <span class=sf-dump-note>Unknown resource</span> <a class=sf-dump-ref>@{$res2}</a>
"<span class=sf-dump-key>obj</span>" => <abbr title="Symfony\Component\VarDumper\Tests\Fixture\DumbFoo" class=sf-dump-note>DumbFoo</abbr> {<a class=sf-dump-ref href=#{$dumpId}-ref2%d title="2 occurrences">#%d</a><samp id={$dumpId}-ref2%d>
+<span class=sf-dump-public title="Public property">foo</span>: "<span class=sf-dump-str title="3 characters">foo</span>"
+"<span class=sf-dump-public title="Runtime added dynamic property">bar</span>": "<span class=sf-dump-str title="3 characters">bar</span>"
</samp>}
"<span class=sf-dump-key>closure</span>" => <span class=sf-dump-note>Closure</span> {<a class=sf-dump-ref>#%d</a><samp>
"<span class=sf-dump-key>closure</span>" => <span class=sf-dump-note>Closure</span> {{$r}<samp>
<span class=sf-dump-meta>reflection</span>: """
<span class=sf-dump-str title="%d characters">Closure [ &lt;user%S&gt; %s Symfony\Component\VarDumper\Tests\Fixture\{closure} ] {</span>\\n
<span class=sf-dump-str title="%d characters"> @@ {$var['file']} {$var['line']} - {$var['line']}</span>\\n
@ -97,7 +96,7 @@ class HtmlDumperTest extends \PHPUnit_Framework_TestCase
"<span class=sf-dump-key>recurs</span>" => <a class=sf-dump-ref href=#{$dumpId}-ref04 title="2 occurrences">&amp;4</a> <span class=sf-dump-note>array:1</span> [<samp id={$dumpId}-ref04>
<span class=sf-dump-index>0</span> => <a class=sf-dump-ref href=#{$dumpId}-ref04 title="2 occurrences">&amp;4</a> <span class=sf-dump-note>array:1</span> [<a class=sf-dump-ref href=#{$dumpId}-ref04 title="2 occurrences">&amp;4</a>]
</samp>]
<span class=sf-dump-key>9</span> => <a class=sf-dump-ref href=#{$dumpId}-ref01 title="2 occurrences">&amp;1</a> <span class=sf-dump-const>null</span>
<span class=sf-dump-key>8</span> => <a class=sf-dump-ref href=#{$dumpId}-ref01 title="2 occurrences">&amp;1</a> <span class=sf-dump-const>null</span>
"<span class=sf-dump-key>sobj</span>" => <abbr title="Symfony\Component\VarDumper\Tests\Fixture\DumbFoo" class=sf-dump-note>DumbFoo</abbr> {<a class=sf-dump-ref href=#{$dumpId}-ref2%d title="2 occurrences">#%d</a>}
"<span class=sf-dump-key>snobj</span>" => <a class=sf-dump-ref href=#{$dumpId}-ref03 title="2 occurrences">&amp;3</a> {<a class=sf-dump-ref href=#{$dumpId}-ref2%d title="3 occurrences">#%d</a>}
"<span class=sf-dump-key>snobj2</span>" => {<a class=sf-dump-ref href=#{$dumpId}-ref2%d title="3 occurrences">#%d</a>}

View File

@ -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
)