Skip to content

[ErrorHandler] Rewrite logic to dump exception properties and fix serializing FlattenException #49746

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;

/**
Expand Down Expand Up @@ -69,7 +69,7 @@ public function render(\Throwable $exception): FlattenException
$headers['X-Debug-Exception-File'] = rawurlencode($exception->getFile()).':'.$exception->getLine();
}

$exception = FlattenException::createFromThrowable($exception, null, $headers);
$exception = FlattenException::createWithDataRepresentation($exception, null, $headers);

return $exception->setAsString($this->renderException($exception));
}
Expand Down Expand Up @@ -149,21 +149,12 @@ private function renderException(FlattenException $exception, string $debugTempl
]);
}

private function dumpValue(mixed $value): string
private function dumpValue(Data $value): string
{
$cloner = new VarCloner();
$data = $cloner->cloneVar($value);

$dumper = new HtmlDumper();
$dumper->setTheme('light');
$dumper->setOutput($output = fopen('php://memory', 'r+'));
$dumper->dump($data);

$dump = stream_get_contents($output, -1, 0);
rewind($output);
ftruncate($output, 0);

return $dump;
return $dumper->dump($value, true);
}

private function formatArgs(array $args): string
Expand Down
60 changes: 47 additions & 13 deletions src/Symfony/Component/ErrorHandler/Exception/FlattenException.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\VarDumper\Caster\Caster;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Cloner\Stub;
use Symfony\Component\VarDumper\Cloner\VarCloner;

/**
* FlattenException wraps a PHP Error or Exception to be able to serialize it.
Expand All @@ -36,7 +40,7 @@ class FlattenException
private string $file;
private int $line;
private ?string $asString = null;
private array $properties = [];
private Data $dataRepresentation;

public static function create(\Exception $exception, int $statusCode = null, array $headers = []): static
{
Expand Down Expand Up @@ -78,14 +82,34 @@ public static function createFromThrowable(\Throwable $exception, int $statusCod
$e->setPrevious(static::createFromThrowable($previous));
}

if ((new \ReflectionClass($exception::class))->isUserDefined()) {
$getProperties = \Closure::bind(fn (\Throwable $e) => get_object_vars($e), null, $exception::class);
$properties = $getProperties($exception);
unset($properties['message'], $properties['code'], $properties['file'], $properties['line']);
$e->properties = $properties;
return $e;
}

public static function createWithDataRepresentation(\Throwable $throwable, int $statusCode = null, array $headers = [], VarCloner $cloner = null): static
{
$e = static::createFromThrowable($throwable, $statusCode, $headers);

static $defaultCloner;

if (!$cloner ??= $defaultCloner) {
$cloner = $defaultCloner = new VarCloner();
$cloner->addCasters([
\Throwable::class => function (\Throwable $e, array $a, Stub $s, bool $isNested): array {
if (!$isNested) {
unset($a[Caster::PREFIX_PROTECTED.'message']);
unset($a[Caster::PREFIX_PROTECTED.'code']);
unset($a[Caster::PREFIX_PROTECTED.'file']);
unset($a[Caster::PREFIX_PROTECTED.'line']);
unset($a["\0Error\0trace"], $a["\0Exception\0trace"]);
unset($a["\0Error\0previous"], $a["\0Exception\0previous"]);
}

return $a;
},
]);
}

return $e;
return $e->setDataRepresentation($cloner->cloneVar($throwable));
}

public function toArray(): array
Expand All @@ -96,7 +120,7 @@ public function toArray(): array
'message' => $exception->getMessage(),
'class' => $exception->getClass(),
'trace' => $exception->getTrace(),
'properties' => $exception->getProperties(),
'data' => $exception->getDataRepresentation(),
];
}

Expand Down Expand Up @@ -230,11 +254,6 @@ public function setCode(int|string $code): static
return $this;
}

public function getProperties(): array
{
return $this->properties;
}

public function getPrevious(): ?self
{
return $this->previous;
Expand Down Expand Up @@ -319,6 +338,21 @@ public function setTrace(array $trace, ?string $file, ?int $line): static
return $this;
}

public function getDataRepresentation(): ?Data
{
return $this->dataRepresentation ?? null;
}

/**
* @return $this
*/
public function setDataRepresentation(Data $data): static
{
$this->dataRepresentation = $data;

return $this;
}

private function flattenArgs(array $args, int $level = 0, int &$count = 0): array
{
$result = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@
<p class="break-long-words trace-message"><?= $this->escape($exception['message']); ?></p>
<?php } ?>
</div>
<?php if ($exception['properties']) { ?>
<?php if (\count($exception['data'] ?? [])) { ?>
<details class="exception-properties-wrapper">
<summary>Show exception properties</summary>
<div class="exception-properties">
<?= $this->dumpValue($exception['properties']) ?>
<?= $this->dumpValue($exception['data']) ?>
</div>
</details>
<?php } ?>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ public function testToArray(\Throwable $exception, string $expectedClass)
'namespace' => '', 'short_class' => '', 'class' => '', 'type' => '', 'function' => '', 'file' => 'foo.php', 'line' => 123,
'args' => [],
]],
'properties' => [],
'data' => null,
],
], $flattened->toArray());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public function collect(Request $request, Response $response, \Throwable $except
{
if (null !== $exception) {
$this->data = [
'exception' => FlattenException::createFromThrowable($exception),
'exception' => FlattenException::createWithDataRepresentation($exception),
];
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public function testCollect()
{
$e = new \Exception('foo', 500);
$c = new ExceptionDataCollector();
$flattened = FlattenException::createFromThrowable($e);
$flattened = FlattenException::createWithDataRepresentation($e);
$trace = $flattened->getTrace();

$this->assertFalse($c->hasException());
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Component/HttpKernel/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"require": {
"php": ">=8.1",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/error-handler": "^6.1",
"symfony/error-handler": "^6.3",
"symfony/event-dispatcher": "^5.4|^6.0",
"symfony/http-foundation": "^5.4.21|^6.2.7",
"symfony/polyfill-ctype": "^1.8",
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ private static function filterExceptionArray(string $xClass, array $a, string $x
if (empty($a[$xPrefix.'previous'])) {
unset($a[$xPrefix.'previous']);
}
unset($a[$xPrefix.'string'], $a[Caster::PREFIX_DYNAMIC.'xdebug_message'], $a[Caster::PREFIX_DYNAMIC.'__destructorException']);
unset($a[$xPrefix.'string'], $a[Caster::PREFIX_DYNAMIC.'xdebug_message']);

if (isset($a[Caster::PREFIX_PROTECTED.'message']) && str_contains($a[Caster::PREFIX_PROTECTED.'message'], "@anonymous\0")) {
$a[Caster::PREFIX_PROTECTED.'message'] = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', fn ($m) => class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0], $a[Caster::PREFIX_PROTECTED.'message']);
Expand Down
2 changes: 0 additions & 2 deletions src/Symfony/Component/VarDumper/Cloner/VarCloner.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
*/
class VarCloner extends AbstractCloner
{
private static string $gid;
private static array $arrayCache = [];

protected function doClone(mixed $var): array
Expand All @@ -41,7 +40,6 @@ protected function doClone(mixed $var): array
$stub = null; // Stub capturing the main properties of an original item value
// or null if the original value is used directly

$gid = self::$gid ??= hash('xxh128', random_bytes(6)); // Unique string used to detect the special $GLOBALS variable
$arrayStub = new Stub();
$arrayStub->type = Stub::TYPE_ARRAY;
$fromObjCast = false;
Expand Down