One of Livewire v4's features is the ability to create single file components. Instead of splitting your logic across a PHP class and a Blade view, you can now write everything in a single .php file.
It's a fantastic developer experience.
Until something breaks.
When an error occurs inside a single file component, the stack trace points to a compiled file with a hashed name like ab1234cd.php. That's not very helpful when you're trying to figure out what went wrong. We wanted Flare to show you the actual source file at the actual line number, just like it does for regular Livewire components.
Here's how we made that happen.
The problem with compiled files
Traditional Livewire components are straightforward for Flare to handle. There's a PHP class and a Blade view. We know how to map both back to their source files.
Single file components are different. Livewire compiles them into anonymous classes with hashed filenames and stores them in the cache directory. When an exception is thrown, the stack trace looks something like this:
/storage/framework/cache/livewire/ab1234cd.php:42
That path tells you nothing about which component failed. And line 42 in the compiled output won't correspond to line 42 in your source file either.
Mapping hashes back to source files
During the compilation phase, Livewire creates a cache mapping component names to their compiled classes.
Flare reads that cache using reflection:
$factory = $this->app->make('livewire.factory');
$reflection = new ReflectionProperty($factory, 'resolvedComponentCache');
$resolvedComponents = $reflection->getValue($factory);
The map looks roughly like this:
[
'counter' => "class@anonymous\x00/storage/framework/cache/livewire/ab1234cd.php:12$0",
]
We then loop over each resolved component. An single file component's class string contains a null byte followed by the compiled file path:
foreach ($resolvedComponents as $name => $class) {
$compiledClassPath = preg_replace('/:\d+\$.*$/', '', substr($class, strpos($class, "\x00") + 1));
}
At this point we have the hash from the compiled path, but only the component name like counter. We don't know where the source file lives yet. So, we ask Livewire's Finder to resolve that:
$sourcePath = $this->livewireComponentFinder->findSingleFileComponentFile($name);
if (preg_match('/[\/\\\\]([a-f0-9]+)\.php$/', $compiledClassPath, $matches)) {
$this->compiledPathMap[$matches[1]] = $sourcePath;
}
The end result is a simple reverse map:
"ab1234cd" => "/resources/views/livewire/counter.php"
When Flare later encounters a hashed path in a stack trace, it checks this map and swaps in the real file path. The map is built lazily, only when an single file component is present in the current request. If you're not using single file components, none of this code runs.
Fixing stack traces
With the reverse map in place, we need to hook it into Flare's stack trace processing. The LaravelStacktraceMapper walks every frame in a stack trace and checks if it points to a compiled view:
public function map(array $frames, ?Throwable $throwable): array
{
$frames = array_map(function (Frame $frame) {
if ($originalPath = $this->viewFrameMapper->findCompiledView($frame->file)) {
$frame->file = $originalPath;
$frame->lineNumber = $this->viewFrameMapper->getBladeLineNumber(
$frame->file,
$frame->lineNumber
);
}
return $frame;
}, $frames);
return parent::map($frames, $throwable);
}
If a frame's file path matches a hash in our reverse map, we replace it with the real source path.
Next up is fixing the line number, which at this point is still the line number of the compiled Blade component.
Flare already knows how to map compiled Blade line numbers back to source line numbers. We use the same getBladeLineNumber logic that we've always used for regular Blade views. Since an single file component is just a Blade view under the hood, no new line number mapping was needed. We just had to get to the real file path first.
Tagging traces correctly
Flare doesn't just fix stack traces. It also records performance traces of your application. A trace is a timeline of everything that happens during a request, broken down into individual spans. Each Livewire component gets its own span, showing when it mounted, hydrated, and rendered.
When a component starts rendering, the LivewireRecorder checks whether it's a single file component and tags the span accordingly:
$isSingleFileComponent = $this->livewireComponentFinder->isSingleFileComponent($component);
$attributes = [
'livewire.component.name' => $component,
'livewire.component.single_file_component' => $isSingleFileComponent,
];
if (! $isSingleFileComponent) {
$attributes['livewire.component.class'] = $class;
}
For class-based components, Flare records the class name. For single file components, there is no meaningful class name, so we resolve the source file path from the component name and record that instead:
if (
$componentState->isSingleFileComponent
&& ($viewFile = $this->livewireComponentFinder->findSingleFileComponentFile($component->getName()))
) {
$componentState->span->addAttribute('view.file', $viewFile);
}
The same happens for view spans. When the ViewRecorder detects that the current view belongs to a single file component, it replaces the compiled file path with the real source file and tags the span:
$singleFileComponentFile = $this->livewireComponentFinder->findCurrentSingleFileComponentFile();
if ($singleFileComponentFile) {
$file = $singleFileComponentFile;
$attributes['view.is_livewire_single_file_component'] = true;
}
This way, every span in your traces links back to the right file, whether it's a class-based component or an single file component.
Built for Laravel
Flare is the error tracker and performance monitoring platform for Laravel. Every feature we build, including single file component support, is designed to work the way Laravel developers expect.
As the framework evolves, so does Flare. We're committed to being deeply integrated with the Laravel ecosystem, so you never have to wrestle with your tools to understand your application.
Continue reading
Track frontend errors back to the exact commit
Swap the default random UUID for your git commit hash and trace every JavaScript error straight back to the deploy that caused it.
Dries
A unified error debug timeline
We've reworked the error debug timeline to show all events in chronological order and added support for HTTP requests, Redis commands, filesystem operations, and caching events.
Ruben
Subscribe to Backtrace, our quarterly Flare newsletter
No spam, just news & product updates