<?php
namespace Spatie\LaravelIgnition\Views;
use Exception;
use Illuminate\Contracts\View\Engine;
use Illuminate\Foundation\Application;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\View\Engines\PhpEngine;
use Illuminate\View\ViewException;
use ReflectionClass;
use ReflectionProperty;
use Spatie\Ignition\Contracts\ProvidesSolution;
use Spatie\LaravelIgnition\Exceptions\ViewException as IgnitionViewException;
use Spatie\LaravelIgnition\Exceptions\ViewExceptionWithSolution;
use Throwable;
class ViewExceptionMapper
{
protected Engine $compilerEngine;
protected BladeSourceMapCompiler $bladeSourceMapCompiler;
protected array $knownPaths;
public function __construct(BladeSourceMapCompiler $bladeSourceMapCompiler)
{
$resolver = app('view.engine.resolver');
$this->compilerEngine = $resolver->resolve('blade');
$this->bladeSourceMapCompiler = $bladeSourceMapCompiler;
}
public function map(ViewException $viewException): IgnitionViewException
{
$baseException = $this->getRealException($viewException);
if ($baseException instanceof IgnitionViewException) {
return $baseException;
}
preg_match('/\(View: (?P<path>.*?)\)/', $viewException->getMessage(), $matches);
$compiledViewPath = $matches['path'];
$exception = $this->createException($baseException);
if ($baseException instanceof ProvidesSolution) {
/** @var ViewExceptionWithSolution $exception */
$exception->setSolution($baseException->getSolution());
}
$this->modifyViewsInTrace($exception);
$exception->setView($compiledViewPath);
$exception->setViewData($this->getViewData($exception));
return $exception;
}
protected function createException(Throwable $baseException): IgnitionViewException
{
$viewExceptionClass = $baseException instanceof ProvidesSolution
? ViewExceptionWithSolution::class
: IgnitionViewException::class;
$viewFile = $this->findCompiledView($baseException->getFile());
$file = $viewFile ?? $baseException->getFile();
$line = $viewFile ? $this->getBladeLineNumber($file, $baseException->getLine()) : $baseException->getLine();
return new $viewExceptionClass(
$baseException->getMessage(),
0,
1,
$file,
$line,
$baseException
);
}
protected function modifyViewsInTrace(IgnitionViewException $exception): void
{
$trace = Collection::make($exception->getPrevious()->getTrace())
->map(function ($trace) {
if ($originalPath = $this->findCompiledView(Arr::get($trace, 'file', ''))) {
$trace['file'] = $originalPath;
$trace['line'] = $this->getBladeLineNumber($trace['file'], $trace['line']);
}
return $trace;
})->toArray();
$traceProperty = new ReflectionProperty('Exception', 'trace');
$traceProperty->setAccessible(true);
$traceProperty->setValue($exception, $trace);
}
/**
* Look at the previous exceptions to find the original exception.
* This is usually the first Exception that is not a ViewException.
*/
protected function getRealException(Throwable $exception): Throwable
{
$rootException = $exception->getPrevious() ?? $exception;
while ($rootException instanceof ViewException && $rootException->getPrevious()) {
$rootException = $rootException->getPrevious();
}
return $rootException;
}
protected function findCompiledView(string $compiledPath): ?string
{
$this->knownPaths ??= $this->getKnownPaths();
return $this->knownPaths[$compiledPath] ?? null;
}
protected function getKnownPaths(): array
{
$compilerEngineReflection = new ReflectionClass($this->compilerEngine);
if (! $compilerEngineReflection->hasProperty('lastCompiled') && $compilerEngineReflection->hasProperty('engine')) {
$compilerEngine = $compilerEngineReflection->getProperty('engine');
$compilerEngine->setAccessible(true);
$compilerEngine = $compilerEngine->getValue($this->compilerEngine);
$lastCompiled = new ReflectionProperty($compilerEngine, 'lastCompiled');
$lastCompiled->setAccessible(true);
$lastCompiled = $lastCompiled->getValue($compilerEngine);
} else {
$lastCompiled = $compilerEngineReflection->getProperty('lastCompiled');
$lastCompiled->setAccessible(true);
$lastCompiled = $lastCompiled->getValue($this->compilerEngine);
}
$knownPaths = [];
foreach ($lastCompiled as $lastCompiledPath) {
$compiledPath = $this->compilerEngine->getCompiler()->getCompiledPath($lastCompiledPath);
$knownPaths[realpath($compiledPath ?? $lastCompiledPath)] = realpath($lastCompiledPath);
}
return $knownPaths;
}
protected function getBladeLineNumber(string $view, int $compiledLineNumber): int
{
return $this->bladeSourceMapCompiler->detectLineNumber($view, $compiledLineNumber);
}
protected function getViewData(Throwable $exception): array
{
foreach ($exception->getTrace() as $frame) {
if (Arr::get($frame, 'class') === PhpEngine::class) {
$data = Arr::get($frame, 'args.1', []);
return $this->filterViewData($data);
}
}
return [];
}
protected function filterViewData(array $data): array
{
// By default, Laravel views get two data keys:
// __env and app. We try to filter them out.
return array_filter($data, function ($value, $key) {
if ($key === 'app') {
return ! $value instanceof Application;
}
return $key !== '__env';
}, ARRAY_FILTER_USE_BOTH);
}
}
If you encounter any issues or need assistance, please reach out to our dedicated developer support team Contact Us
Thank you for choosing Kueue Pay Payment Gateway Solutions! We look forward to seeing your integration thrive and provide a seamless payment experience for your valued customers.