ExceptionRenderer   A
last analyzed

Complexity

Total Complexity 15

Size/Duplication

Total Lines 126
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 15
lcom 1
cbo 2
dl 0
loc 126
ccs 0
cts 65
cp 0
rs 10
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A render() 0 8 1
A renderCode() 0 11 2
A renderHeader() 0 7 1
A classname() 0 6 2
A renderFileHeader() 0 7 1
A renderTrace() 0 15 2
A traceArguments() 0 10 3
A traceInvocation() 0 6 2
1
<?php
2
/**
3
 * This file is part of Railt package.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
declare(strict_types=1);
9
10
namespace Railt\Console;
11
12
use Railt\Io\Exception\NotReadableException;
13
use Railt\Io\File;
14
15
/**
16
 * Class ExceptionRenderer
17
 */
18
class ExceptionRenderer
19
{
20
    /**
21
     * @var Highlighter
22
     */
23
    private $highlighter;
24
25
    /**
26
     * ExceptionRenderer constructor.
27
     * @param Highlighter $highlighter
28
     */
29
    public function __construct(Highlighter $highlighter)
30
    {
31
        $this->highlighter = $highlighter;
32
    }
33
34
    /**
35
     * @param \Throwable $e
36
     * @return string
37
     */
38
    public function render(\Throwable $e): string
39
    {
40
        return
41
            $this->renderHeader($e) .
42
            $this->renderFileHeader($e) .
43
            $this->renderCode($e) .
44
            $this->renderTrace($e);
45
    }
46
47
    /**
48
     * @param \Throwable $e
49
     * @return string
50
     */
51
    private function renderCode(\Throwable $e): string
52
    {
53
        try {
54
            $file = File::fromPathname($e->getFile());
55
        } catch (NotReadableException $error) {
56
            $file = File::fromSources('', $e->getFile());
57
        }
58
59
        return $this->highlighter->highlight($file, $e->getLine()) .
0 ignored issues
show
Bug introduced by
It seems like $file defined by \Railt\Io\File::fromPathname($e->getFile()) on line 54 can also be of type object<Railt\Io\File>; however, Railt\Console\Highlighter::highlight() does only seem to accept object<Railt\Io\Readable>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
60
            \PHP_EOL;
61
    }
62
63
    /**
64
     * @param \Throwable $e
65
     * @return string
66
     */
67
    private function renderHeader(\Throwable $e): string
68
    {
69
        [$name, $msg] = [$this->classname($e), $e->getMessage()];
0 ignored issues
show
Bug introduced by
The variable $name does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $msg does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
70
71
        return \sprintf('<error> %s </error> <comment>%s</comment>', $name, $msg) .
72
            \PHP_EOL . \PHP_EOL;
73
    }
74
75
    /**
76
     * @param string|object $class
77
     * @return string
78
     */
79
    private function classname($class): string
80
    {
81
        $class = \is_object($class) ? \get_class($class) : $class;
82
83
        return \basename(\str_replace('\\', '/', $class));
84
    }
85
86
    /**
87
     * @param \Throwable $e
88
     * @return string
89
     */
90
    private function renderFileHeader(\Throwable $e): string
91
    {
92
        $message = 'in <fg=cyan;options=underscore>%s</> at line <fg=cyan>%d</>';
93
94
        return \sprintf($message, $e->getFile(), $e->getLine()) .
95
            \PHP_EOL . \PHP_EOL;
96
    }
97
98
    /**
99
     * @param \Throwable $e
100
     * @return string
101
     */
102
    private function renderTrace(\Throwable $e): string
103
    {
104
        $result = ['<comment>Stack Trace:</comment>'];
105
106
        foreach ($e->getTrace() as $i => $trace) {
107
            $line = $this->traceInvocation($trace);
108
            $args = $this->traceArguments($trace);
109
110
            $result[] = \sprintf('<fg=blue>%3s</> <comment>%s(%s)</comment>', '#' . $i, $line, $args);
111
            $result[] = \sprintf('    <fg=cyan>%s</>:<fg=cyan>%d</>', $trace['file'], $trace['line']);
112
        }
113
114
        return \implode(\PHP_EOL, $result) .
115
            \PHP_EOL;
116
    }
117
118
    /**
119
     * @param array $trace
120
     * @return string
121
     */
122
    private function traceArguments(array $trace): string
123
    {
124
        $result = [];
125
126
        foreach (($trace['args'] ?? []) as $arg) {
127
            $result[] = \is_object($arg) ? 'Object(' . $this->classname($arg) . ')' : \gettype($arg);
128
        }
129
130
        return \implode(', ', $result);
131
    }
132
133
    /**
134
     * @param array $trace
135
     * @return string
136
     */
137
    private function traceInvocation(array $trace): string
138
    {
139
        return isset($trace['class'])
140
            ? $trace['class'] . $trace['type'] . $trace['function']
141
            : $trace['function'];
142
    }
143
}
144