spatie/backtrace
Spatie Backtrace makes PHP backtraces easier than debug_backtrace(): frames are aligned correctly and exposed as Frame objects with file, line, class, method, and optional arguments. Simple API to capture, inspect, and work with stack traces.
Install via Composer:
composer require spatie/backtrace
use Spatie\Backtrace\Backtrace;
// Get the current backtrace with application context
$backtrace = Backtrace::create()
->applicationPath(base_path())
->trimFilePaths();
$frames = $backtrace->frames();
// Inspect the first application frame
$firstAppFrame = $frames[0];
dump($firstAppFrame->method); // e.g., "handlePayment"
dump($firstAppFrame->file); // e.g., "app/Services/PaymentService.php"
Backtrace::create()->applicationPath(base_path())
->trimFilePaths()
frames() to explore structure).try {
// Risky operation
} catch (Throwable $e) {
$backtrace = Backtrace::createForThrowable($e)
->applicationPath(base_path())
->withArguments()
->reduceArguments();
$frames = $backtrace->frames();
// Log or display frames for debugging
}
public function handle($request, Closure $next) {
$backtrace = Backtrace::create()
->startingFromFrame(fn ($frame) => $frame->class === Middleware::class)
->limit(5);
// Log or inspect $backtrace->frames() for middleware flow
return $next($request);
}
// Skip vendor frames and limit to 3 app frames
$frames = Backtrace::create()
->applicationPath(base_path())
->startingFromFrame(fn ($frame) => $frame->applicationFrame)
->limit(3)
->frames();
$frame = $frames[0];
$snippet = $frame->getSnippetAsString(); // Returns code snippet around lineNumber
use Spatie\Backtrace\Arguments\ArgumentReducer;
use Spatie\Backtrace\Arguments\ReducedArgument;
use Spatie\Backtrace\Arguments\UnReducedArgument;
class UserIdReducer implements ArgumentReducer {
public function execute($argument): ReducedArgumentContract {
if ($argument instanceof UserId) {
return new ReducedArgument(
"User ID: {$argument->value()}",
UserId::class
);
}
return UnReducedArgument::create();
}
}
// Usage
$backtrace = Backtrace::create()
->withArguments()
->reduceArguments([
new UserIdReducer(),
// ... other reducers
]);
use Illuminate\Log\Logger;
use Spatie\Backtrace\Backtrace;
app(Logger::class)->error('Payment failed', [
'backtrace' => Backtrace::createForThrowable($e)
->applicationPath(base_path())
->withArguments()
->reduceArguments()
->getAsString(),
]);
// In App\Exceptions\Handler
public function render($request, Throwable $exception) {
$backtrace = Backtrace::createForThrowable($exception)
->applicationPath(base_path())
->trimFilePaths()
->limit(10);
return response()->view('errors.custom', [
'backtrace' => $backtrace->getAsString(),
]);
}
zend.exception_ignore_args INI Setting
1), createForThrowable() won’t include arguments.php.ini or .user.ini before throwing exceptions:
zend.exception_ignore_args=0
File Path Edge Cases
/app/) will crash without trimFilePaths().trimFilePaths() with applicationPath():
Backtrace::create()->applicationPath(base_path())->trimFilePaths();
Vendor Frame Misclassification
artisan and Statamic’s please are always marked as vendor frames (even in root).->startingFromFrame(fn ($frame) =>
$frame->applicationFrame &&
!str_contains($frame->file, ['artisan', 'please'])
)
Performance with withArguments()
$cachedBacktrace = Backtrace::create()
->withArguments()
->remember(); // Hypothetical; implement caching manually
Inspect Frame Properties Dump a frame to see all available data:
dd($frame->toArray()); // Includes: file, lineNumber, class, method, etc.
Check applicationFrame Logic
firstApplicationFrameIndex() to find the first app frame:
$index = $backtrace->firstApplicationFrameIndex();
$appFrames = array_slice($frames, $index);
Handle Non-Existent Files
open_basedir restricts file access, use a custom CodeSnippetProvider:
$backtrace->setCodeSnippetProvider(new CustomSnippetProvider());
Custom Argument Reducers
ArgumentReducer for domain-specific types (e.g., UUIDs, custom DTOs).Code Snippet Providers
CodeSnippetProvider to fetch code from external sources (e.g., GitHub).Frame Filtering
startingFromFrame() logic for project-specific needs:
->startingFromFrame(fn ($frame) =>
str_contains($frame->file, ['app/Http/Controllers'])
)
applicationPath() Must Precede trimFilePaths()
trimFilePaths() relies on applicationPath() for path normalization.withObject() Limitations
withObject() for non-throwable backtraces.PHP 8.4+ Compatibility
readonly properties). Test on target PHP version.Backtrace::create()
->applicationPath(base_path())
->withArguments()
->reduceArguments()
->getAsString(); // Log this for auditing
$this->assertEquals(
'App\\Services\\PaymentService',
Backtrace::create()->frames()[0]->class
);
whoops:
Integrate for enhanced error pages:
$whoops->addSingleFrameHandler(function (Whoops\Frame $frame) {
$backtrace = Backtrace::createForThrowable($frame->getThrowable())
->applicationPath(base_path())
->trimFilePaths();
return $backtrace->getAsString();
});
How can I help you explore Laravel packages today?