Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
declare(strict_types=1);
4
 
5
namespace NunoMaduro\Collision;
6
 
7
use NunoMaduro\Collision\Contracts\ArgumentFormatter as ArgumentFormatterContract;
8
use NunoMaduro\Collision\Contracts\Highlighter as HighlighterContract;
9
use NunoMaduro\Collision\Contracts\RenderlessEditor;
10
use NunoMaduro\Collision\Contracts\RenderlessTrace;
11
use NunoMaduro\Collision\Contracts\SolutionsRepository;
12
use NunoMaduro\Collision\Contracts\Writer as WriterContract;
13
use NunoMaduro\Collision\SolutionsRepositories\NullSolutionsRepository;
14
use Symfony\Component\Console\Output\ConsoleOutput;
15
use Symfony\Component\Console\Output\OutputInterface;
16
use Whoops\Exception\Frame;
17
use Whoops\Exception\Inspector;
18
 
19
/**
20
 * @internal
21
 *
22
 * @see \Tests\Unit\WriterTest
23
 */
24
final class Writer implements WriterContract
25
{
26
    /**
27
     * The number of frames if no verbosity is specified.
28
     */
29
    public const VERBOSITY_NORMAL_FRAMES = 1;
30
 
31
    /**
32
     * Holds an instance of the solutions repository.
33
     *
34
     * @var \NunoMaduro\Collision\Contracts\SolutionsRepository
35
     */
36
    private $solutionsRepository;
37
 
38
    /**
39
     * Holds an instance of the Output.
40
     *
41
     * @var \Symfony\Component\Console\Output\OutputInterface
42
     */
43
    protected $output;
44
 
45
    /**
46
     * Holds an instance of the Argument Formatter.
47
     *
48
     * @var \NunoMaduro\Collision\Contracts\ArgumentFormatter
49
     */
50
    protected $argumentFormatter;
51
 
52
    /**
53
     * Holds an instance of the Highlighter.
54
     *
55
     * @var \NunoMaduro\Collision\Contracts\Highlighter
56
     */
57
    protected $highlighter;
58
 
59
    /**
60
     * Ignores traces where the file string matches one
61
     * of the provided regex expressions.
62
     *
63
     * @var string[]
64
     */
65
    protected $ignore = [];
66
 
67
    /**
68
     * Declares whether or not the trace should appear.
69
     *
70
     * @var bool
71
     */
72
    protected $showTrace = true;
73
 
74
    /**
75
     * Declares whether or not the title should appear.
76
     *
77
     * @var bool
78
     */
79
    protected $showTitle = true;
80
 
81
    /**
82
     * Declares whether or not the editor should appear.
83
     *
84
     * @var bool
85
     */
86
    protected $showEditor = true;
87
 
88
    /**
89
     * Creates an instance of the writer.
90
     */
91
    public function __construct(
92
        SolutionsRepository $solutionsRepository = null,
93
        OutputInterface $output = null,
94
        ArgumentFormatterContract $argumentFormatter = null,
95
        HighlighterContract $highlighter = null
96
    ) {
97
        $this->solutionsRepository = $solutionsRepository ?: new NullSolutionsRepository();
98
        $this->output = $output ?: new ConsoleOutput();
99
        $this->argumentFormatter = $argumentFormatter ?: new ArgumentFormatter();
100
        $this->highlighter = $highlighter ?: new Highlighter();
101
    }
102
 
103
    /**
104
     * {@inheritdoc}
105
     */
106
    public function write(Inspector $inspector): void
107
    {
108
        $this->renderTitleAndDescription($inspector);
109
 
110
        $frames = $this->getFrames($inspector);
111
 
112
        $editorFrame = array_shift($frames);
113
 
114
        $exception = $inspector->getException();
115
 
116
        if ($this->showEditor
117
            && $editorFrame !== null
118
            && ! $exception instanceof RenderlessEditor
119
        ) {
120
            $this->renderEditor($editorFrame);
121
        }
122
 
123
        $this->renderSolution($inspector);
124
 
125
        if ($this->showTrace && ! empty($frames) && ! $exception instanceof RenderlessTrace) {
126
            $this->renderTrace($frames);
127
        } elseif (! $exception instanceof RenderlessEditor) {
128
            $this->output->writeln('');
129
        }
130
    }
131
 
132
    /**
133
     * {@inheritdoc}
134
     */
135
    public function ignoreFilesIn(array $ignore): WriterContract
136
    {
137
        $this->ignore = $ignore;
138
 
139
        return $this;
140
    }
141
 
142
    /**
143
     * {@inheritdoc}
144
     */
145
    public function showTrace(bool $show): WriterContract
146
    {
147
        $this->showTrace = $show;
148
 
149
        return $this;
150
    }
151
 
152
    /**
153
     * {@inheritdoc}
154
     */
155
    public function showTitle(bool $show): WriterContract
156
    {
157
        $this->showTitle = $show;
158
 
159
        return $this;
160
    }
161
 
162
    /**
163
     * {@inheritdoc}
164
     */
165
    public function showEditor(bool $show): WriterContract
166
    {
167
        $this->showEditor = $show;
168
 
169
        return $this;
170
    }
171
 
172
    /**
173
     * {@inheritdoc}
174
     */
175
    public function setOutput(OutputInterface $output): WriterContract
176
    {
177
        $this->output = $output;
178
 
179
        return $this;
180
    }
181
 
182
    /**
183
     * {@inheritdoc}
184
     */
185
    public function getOutput(): OutputInterface
186
    {
187
        return $this->output;
188
    }
189
 
190
    /**
191
     * Returns pertinent frames.
192
     */
193
    protected function getFrames(Inspector $inspector): array
194
    {
195
        return $inspector->getFrames()
196
            ->filter(
197
                function ($frame) {
198
                    // If we are in verbose mode, we always
199
                    // display the full stack trace.
200
                    if ($this->output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
201
                        return true;
202
                    }
203
 
204
                    foreach ($this->ignore as $ignore) {
205
                        // Ensure paths are linux-style (like the ones on $this->ignore)
206
                        // @phpstan-ignore-next-line
207
                        $sanitizedPath = (string) str_replace('\\', '/', $frame->getFile());
208
                        if (preg_match($ignore, $sanitizedPath)) {
209
                            return false;
210
                        }
211
                    }
212
 
213
                    return true;
214
                }
215
            )
216
            ->getArray();
217
    }
218
 
219
    /**
220
     * Renders the title of the exception.
221
     */
222
    protected function renderTitleAndDescription(Inspector $inspector): WriterContract
223
    {
224
        $exception = $inspector->getException();
225
        $message = rtrim($exception->getMessage());
226
        $class = $inspector->getExceptionName();
227
 
228
        if ($this->showTitle) {
229
            $this->render("<bg=red;options=bold> $class </>");
230
            $this->output->writeln('');
231
        }
232
 
233
        $this->output->writeln("<fg=default;options=bold>  $message</>");
234
 
235
        return $this;
236
    }
237
 
238
    /**
239
     * Renders the solution of the exception, if any.
240
     */
241
    protected function renderSolution(Inspector $inspector): WriterContract
242
    {
243
        $throwable = $inspector->getException();
244
        $solutions = $this->solutionsRepository->getFromThrowable($throwable);
245
 
246
        foreach ($solutions as $solution) {
247
            /** @var \Spatie\Ignition\Contracts\Solution $solution */
248
            $title = $solution->getSolutionTitle();
249
            $description = $solution->getSolutionDescription();
250
            $links = $solution->getDocumentationLinks();
251
 
252
            $description = trim((string) preg_replace("/\n/", "\n    ", $description));
253
 
254
            $this->render(sprintf(
255
                '<fg=cyan;options=bold>i</>   <fg=default;options=bold>%s</>: %s %s',
256
                rtrim($title, '.'),
257
                $description,
258
                implode(', ', array_map(function (string $link) {
259
                    return sprintf("\n      <fg=gray>%s</>", $link);
260
                }, $links))
261
            ));
262
        }
263
 
264
        return $this;
265
    }
266
 
267
    /**
268
     * Renders the editor containing the code that was the
269
     * origin of the exception.
270
     */
271
    protected function renderEditor(Frame $frame): WriterContract
272
    {
273
        if ($frame->getFile() !== 'Unknown') {
274
            $file = $this->getFileRelativePath((string) $frame->getFile());
275
 
276
            // getLine() might return null so cast to int to get 0 instead
277
            $line = (int) $frame->getLine();
278
            $this->render('at <fg=green>'.$file.'</>'.':<fg=green>'.$line.'</>');
279
 
280
            $content = $this->highlighter->highlight((string) $frame->getFileContents(), (int) $frame->getLine());
281
 
282
            $this->output->writeln($content);
283
        }
284
 
285
        return $this;
286
    }
287
 
288
    /**
289
     * Renders the trace of the exception.
290
     */
291
    protected function renderTrace(array $frames): WriterContract
292
    {
293
        $vendorFrames = 0;
294
        $userFrames = 0;
295
        foreach ($frames as $i => $frame) {
296
            if ($this->output->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE && strpos($frame->getFile(), '/vendor/') !== false) {
297
                $vendorFrames++;
298
 
299
                continue;
300
            }
301
 
302
            if ($userFrames > static::VERBOSITY_NORMAL_FRAMES && $this->output->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) {
303
                break;
304
            }
305
 
306
            $userFrames++;
307
 
308
            $file = $this->getFileRelativePath($frame->getFile());
309
            $line = $frame->getLine();
310
            $class = empty($frame->getClass()) ? '' : $frame->getClass().'::';
311
            $function = $frame->getFunction();
312
            $args = $this->argumentFormatter->format($frame->getArgs());
313
            $pos = str_pad((string) ((int) $i + 1), 4, ' ');
314
 
315
            if ($vendorFrames > 0) {
316
                $this->output->write(
317
                    sprintf("\n      \e[2m+%s vendor frames \e[22m", $vendorFrames)
318
                );
319
                $vendorFrames = 0;
320
            }
321
 
322
            $this->render("<fg=yellow>$pos</><fg=default;options=bold>$file</>:<fg=default;options=bold>$line</>");
323
            $this->render("<fg=gray>    $class$function($args)</>", false);
324
        }
325
 
326
        return $this;
327
    }
328
 
329
    /**
330
     * Renders an message into the console.
331
     *
332
     * @return $this
333
     */
334
    protected function render(string $message, bool $break = true): WriterContract
335
    {
336
        if ($break) {
337
            $this->output->writeln('');
338
        }
339
 
340
        $this->output->writeln("  $message");
341
 
342
        return $this;
343
    }
344
 
345
    /**
346
     * Returns the relative path of the given file path.
347
     */
348
    protected function getFileRelativePath(string $filePath): string
349
    {
350
        $cwd = (string) getcwd();
351
 
352
        if (! empty($cwd)) {
353
            return str_replace("$cwd/", '', $filePath);
354
        }
355
 
356
        return $filePath;
357
    }
358
}