Skip to content

Commit f8641c3

Browse files
xepozzvjik
andauthored
Trace for error (#96)
Co-authored-by: Sergei Predvoditelev <[email protected]>
1 parent 3882b7b commit f8641c3

File tree

7 files changed

+71
-17
lines changed

7 files changed

+71
-17
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## 3.1.1 under development
44

55
- New #98: Add ability to execute `getBody()` on response when `ExceptionResponder` middleware is processing (@vjik)
6+
- Enh #96: Trace PHP errors (@xepozz, @vjik)
67

78
## 3.1.0 January 07, 2024
89

src/ErrorHandler.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,8 @@ public function register(): void
128128
return true;
129129
}
130130

131-
throw new ErrorException($message, $severity, $severity, $file, $line);
131+
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
132+
throw new ErrorException($message, $severity, $severity, $file, $line, null, $backtrace);
132133
});
133134

134135
$this->enabled = true;
@@ -169,7 +170,16 @@ private function initializeOnce(): void
169170
$e = error_get_last();
170171

171172
if ($e !== null && ErrorException::isFatalError($e)) {
172-
$error = new ErrorException($e['message'], $e['type'], $e['type'], $e['file'], $e['line']);
173+
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
174+
$error = new ErrorException(
175+
$e['message'],
176+
$e['type'],
177+
$e['type'],
178+
$e['file'],
179+
$e['line'],
180+
null,
181+
$backtrace
182+
);
173183
$this->renderThrowableAndTerminate($error);
174184
}
175185
});

src/Exception/ErrorException.php

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
/**
1616
* `ErrorException` represents a PHP error.
17+
*
18+
* @psalm-type DebugBacktraceType = list<array{args?:list<mixed>,class?:class-string,file?:string,function:string,line?:int,object?:object,type?:string}>
1719
*/
1820
class ErrorException extends \ErrorException implements FriendlyExceptionInterface
1921
{
@@ -35,14 +37,11 @@ class ErrorException extends \ErrorException implements FriendlyExceptionInterfa
3537
E_USER_DEPRECATED => 'PHP User Deprecated Warning',
3638
];
3739

38-
public function __construct(
39-
string $message = '',
40-
int $code = 0,
41-
int $severity = 1,
42-
string $filename = __FILE__,
43-
int $line = __LINE__,
44-
Exception $previous = null
45-
) {
40+
/**
41+
* @psalm-param DebugBacktraceType $backtrace
42+
*/
43+
public function __construct(string $message = '', int $code = 0, int $severity = 1, string $filename = __FILE__, int $line = __LINE__, Exception $previous = null, private array $backtrace = [])
44+
{
4645
parent::__construct($message, $code, $severity, $filename, $line, $previous);
4746
$this->addXDebugTraceToFatalIfAvailable();
4847
}
@@ -76,6 +75,14 @@ public function getSolution(): ?string
7675
return null;
7776
}
7877

78+
/**
79+
* @psalm-return DebugBacktraceType
80+
*/
81+
public function getBacktrace(): array
82+
{
83+
return $this->backtrace;
84+
}
85+
7986
/**
8087
* Fatal errors normally do not provide any trace making it harder to debug. In case XDebug is installed, we
8188
* can get a trace using `xdebug_get_function_stack()`.

src/Renderer/HtmlRenderer.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Throwable;
1212
use Yiisoft\ErrorHandler\CompositeException;
1313
use Yiisoft\ErrorHandler\ErrorData;
14+
use Yiisoft\ErrorHandler\Exception\ErrorException;
1415
use Yiisoft\ErrorHandler\ThrowableRendererInterface;
1516
use Yiisoft\FriendlyException\FriendlyExceptionInterface;
1617

@@ -46,6 +47,8 @@
4647

4748
/**
4849
* Formats throwable into HTML string.
50+
*
51+
* @psalm-import-type DebugBacktraceType from ErrorException
4952
*/
5053
final class HtmlRenderer implements ThrowableRendererInterface
5154
{
@@ -237,13 +240,16 @@ public function renderPreviousExceptions(Throwable $t): string
237240
* @throws Throwable
238241
*
239242
* @return string HTML content of the rendered call stack.
243+
*
244+
* @psalm-param DebugBacktraceType $trace
240245
*/
241-
public function renderCallStack(Throwable $t): string
246+
public function renderCallStack(Throwable $t, array $trace = []): string
242247
{
243248
$application = $vendor = [];
244249
$application[1] = $this->renderCallStackItem($t->getFile(), $t->getLine(), null, null, [], 1, false);
245250

246-
for ($i = 0, $trace = $t->getTrace(), $length = count($trace); $i < $length; ++$i) {
251+
$length = count($trace);
252+
for ($i = 0; $i < $length; ++$i) {
247253
$file = !empty($trace[$i]['file']) ? $trace[$i]['file'] : null;
248254
$line = !empty($trace[$i]['line']) ? $trace[$i]['line'] : null;
249255
$class = !empty($trace[$i]['class']) ? $trace[$i]['class'] : null;

templates/development.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php
22

33
use Yiisoft\ErrorHandler\CompositeException;
4+
use Yiisoft\ErrorHandler\Exception\ErrorException;
45
use Yiisoft\FriendlyException\FriendlyExceptionInterface;
56

67
/**
@@ -97,7 +98,12 @@
9798

9899
<main>
99100
<div class="call-stack">
100-
<?= $this->renderCallStack($throwable) ?>
101+
<?= $this->renderCallStack(
102+
$throwable,
103+
$originalException === $throwable && $originalException instanceof ErrorException
104+
? $originalException->getBacktrace()
105+
: $throwable->getTrace()
106+
) ?>
101107
</div>
102108
<?php if ($request && ($requestInfo = $this->renderRequest($request)) !== ''): ?>
103109
<div class="request">

tests/ErrorHandlerTest.php

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,14 +99,17 @@ public function testHandleErrorWithCatching(): void
9999
$this->errorHandler->register();
100100
$array = ['type' => 'undefined'];
101101

102+
$exception = null;
102103
try {
103104
$array['undefined'];
104-
} catch (Throwable $e) {
105-
$this->assertInstanceOf(ErrorException::class, $e);
106-
$this->assertFalse($e::isFatalError($array));
107-
$this->assertNull($e->getSolution());
105+
} catch (Throwable $exception) {
108106
}
109107

108+
$this->assertInstanceOf(ErrorException::class, $exception);
109+
$this->assertFalse($exception::isFatalError($array));
110+
$this->assertNull($exception->getSolution());
111+
$this->assertNotEmpty($exception->getBacktrace());
112+
110113
$this->errorHandler->unregister();
111114
}
112115
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Yiisoft\ErrorHandler\Tests\Event;
6+
7+
use LogicException;
8+
use PHPUnit\Framework\TestCase;
9+
use Yiisoft\ErrorHandler\Event\ApplicationError;
10+
11+
final class ApplicationErrorTest extends TestCase
12+
{
13+
public function testBase(): void
14+
{
15+
$exception = new LogicException();
16+
17+
$error = new ApplicationError($exception);
18+
19+
$this->assertSame($exception, $error->getThrowable());
20+
}
21+
}

0 commit comments

Comments
 (0)