Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Spatie\LaravelIgnition\Views;
4
 
5
use Exception;
6
use Illuminate\Contracts\View\Engine;
7
use Illuminate\Foundation\Application;
8
use Illuminate\Support\Arr;
9
use Illuminate\Support\Collection;
10
use Illuminate\View\Engines\PhpEngine;
11
use Illuminate\View\ViewException;
12
use ReflectionClass;
13
use ReflectionProperty;
14
use Spatie\Ignition\Contracts\ProvidesSolution;
15
use Spatie\LaravelIgnition\Exceptions\ViewException as IgnitionViewException;
16
use Spatie\LaravelIgnition\Exceptions\ViewExceptionWithSolution;
17
use Throwable;
18
 
19
class ViewExceptionMapper
20
{
21
    protected Engine $compilerEngine;
22
    protected BladeSourceMapCompiler $bladeSourceMapCompiler;
23
    protected array $knownPaths;
24
 
25
    public function __construct(BladeSourceMapCompiler $bladeSourceMapCompiler)
26
    {
27
        $resolver = app('view.engine.resolver');
28
 
29
        $this->compilerEngine = $resolver->resolve('blade');
30
 
31
        $this->bladeSourceMapCompiler = $bladeSourceMapCompiler;
32
    }
33
 
34
    public function map(ViewException $viewException): IgnitionViewException
35
    {
36
        $baseException = $this->getRealException($viewException);
37
 
38
        if ($baseException instanceof IgnitionViewException) {
39
            return $baseException;
40
        }
41
 
42
        preg_match('/\(View: (?P<path>.*?)\)/', $viewException->getMessage(), $matches);
43
 
44
        $compiledViewPath = $matches['path'];
45
 
46
        $exception = $this->createException($baseException);
47
 
48
        if ($baseException instanceof ProvidesSolution) {
49
            /** @var ViewExceptionWithSolution $exception */
50
            $exception->setSolution($baseException->getSolution());
51
        }
52
 
53
        $this->modifyViewsInTrace($exception);
54
 
55
        $exception->setView($compiledViewPath);
56
        $exception->setViewData($this->getViewData($exception));
57
 
58
        return $exception;
59
    }
60
 
61
    protected function createException(Throwable $baseException): IgnitionViewException
62
    {
63
        $viewExceptionClass = $baseException instanceof ProvidesSolution
64
            ? ViewExceptionWithSolution::class
65
            : IgnitionViewException::class;
66
 
67
        $viewFile = $this->findCompiledView($baseException->getFile());
68
        $file = $viewFile ?? $baseException->getFile();
69
        $line = $viewFile ? $this->getBladeLineNumber($file, $baseException->getLine()) : $baseException->getLine();
70
 
71
        return new $viewExceptionClass(
72
            $baseException->getMessage(),
73
            0,
74
            1,
75
            $file,
76
            $line,
77
            $baseException
78
        );
79
    }
80
 
81
    protected function modifyViewsInTrace(IgnitionViewException $exception): void
82
    {
83
        $trace = Collection::make($exception->getPrevious()->getTrace())
84
            ->map(function ($trace) {
85
                if ($originalPath = $this->findCompiledView(Arr::get($trace, 'file', ''))) {
86
                    $trace['file'] = $originalPath;
87
                    $trace['line'] = $this->getBladeLineNumber($trace['file'], $trace['line']);
88
                }
89
 
90
                return $trace;
91
            })->toArray();
92
 
93
        $traceProperty = new ReflectionProperty('Exception', 'trace');
94
        $traceProperty->setAccessible(true);
95
        $traceProperty->setValue($exception, $trace);
96
    }
97
 
98
    /**
99
     * Look at the previous exceptions to find the original exception.
100
     * This is usually the first Exception that is not a ViewException.
101
     */
102
    protected function getRealException(Throwable $exception): Throwable
103
    {
104
        $rootException = $exception->getPrevious() ?? $exception;
105
 
106
        while ($rootException instanceof ViewException && $rootException->getPrevious()) {
107
            $rootException = $rootException->getPrevious();
108
        }
109
 
110
        return $rootException;
111
    }
112
 
113
    protected function findCompiledView(string $compiledPath): ?string
114
    {
115
        $this->knownPaths ??= $this->getKnownPaths();
116
 
117
        return $this->knownPaths[$compiledPath] ?? null;
118
    }
119
 
120
    protected function getKnownPaths(): array
121
    {
122
        $compilerEngineReflection = new ReflectionClass($this->compilerEngine);
123
 
124
        if (! $compilerEngineReflection->hasProperty('lastCompiled') && $compilerEngineReflection->hasProperty('engine')) {
125
            $compilerEngine = $compilerEngineReflection->getProperty('engine');
126
            $compilerEngine->setAccessible(true);
127
            $compilerEngine = $compilerEngine->getValue($this->compilerEngine);
128
            $lastCompiled = new ReflectionProperty($compilerEngine, 'lastCompiled');
129
            $lastCompiled->setAccessible(true);
130
            $lastCompiled = $lastCompiled->getValue($compilerEngine);
131
        } else {
132
            $lastCompiled = $compilerEngineReflection->getProperty('lastCompiled');
133
            $lastCompiled->setAccessible(true);
134
            $lastCompiled = $lastCompiled->getValue($this->compilerEngine);
135
        }
136
 
137
        $knownPaths = [];
138
        foreach ($lastCompiled as $lastCompiledPath) {
139
            $compiledPath = $this->compilerEngine->getCompiler()->getCompiledPath($lastCompiledPath);
140
 
141
            $knownPaths[realpath($compiledPath ?? $lastCompiledPath)] = realpath($lastCompiledPath);
142
        }
143
 
144
        return $knownPaths;
145
    }
146
 
147
    protected function getBladeLineNumber(string $view, int $compiledLineNumber): int
148
    {
149
        return $this->bladeSourceMapCompiler->detectLineNumber($view, $compiledLineNumber);
150
    }
151
 
152
    protected function getViewData(Throwable $exception): array
153
    {
154
        foreach ($exception->getTrace() as $frame) {
155
            if (Arr::get($frame, 'class') === PhpEngine::class) {
156
                $data = Arr::get($frame, 'args.1', []);
157
 
158
                return $this->filterViewData($data);
159
            }
160
        }
161
 
162
        return [];
163
    }
164
 
165
    protected function filterViewData(array $data): array
166
    {
167
        // By default, Laravel views get two data keys:
168
        // __env and app. We try to filter them out.
169
        return array_filter($data, function ($value, $key) {
170
            if ($key === 'app') {
171
                return ! $value instanceof Application;
172
            }
173
 
174
            return $key !== '__env';
175
        }, ARRAY_FILTER_USE_BOTH);
176
    }
177
}