HtmlRenderer   F
last analyzed

Complexity

Total Complexity 68

Size/Duplication

Total Lines 321
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 154
c 1
b 0
f 0
dl 0
loc 321
ccs 0
cts 225
cp 0
rs 2.96
wmc 68

14 Methods

Rating   Name   Duplication   Size   Complexity  
A renderPreviousExceptions() 0 6 2
A isCoreFile() 0 3 2
A htmlEncode() 0 3 1
B addTypeLinks() 0 28 10
A getTypeUrl() 0 11 3
A renderTemplate() 0 25 5
C argumentsToString() 0 42 14
B renderCallStack() 0 17 8
B renderCallStackItem() 0 24 9
A createFrameworkVersionLink() 0 3 1
A createServerInformationLink() 0 20 5
A render() 0 4 1
A renderRequest() 0 25 5
A renderCurl() 0 9 2

How to fix   Complexity   

Complex Class

Complex classes like HtmlRenderer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use HtmlRenderer, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Yiisoft\Yii\Web\ErrorHandler;
4
5
use Alexkart\CurlBuilder\Command;
6
use Yiisoft\Yii\Web\Info;
7
8
final class HtmlRenderer extends ThrowableRenderer
9
{
10
    // TODO expose config
11
    private $maxSourceLines = 19;
12
    private $maxTraceLines = 13;
13
14
    private $traceLine = '{html}';
0 ignored issues
show
introduced by Alexander Makarov
The private property $traceLine is not used, and could be removed.
Loading history...
15
16
    public function render(\Throwable $t): string
17
    {
18
        return $this->renderTemplate('exception', [
19
            'throwable' => $t,
20
        ]);
21
    }
22
23
    private function htmlEncode(string $text): string
24
    {
25
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
26
    }
27
28
    private function renderTemplate(string $template, array $params): string
29
    {
30
        $path = __DIR__ . '/templates/' . $template . '.php';
31
        if (!file_exists($path)) {
32
            throw new \RuntimeException("$template not found at $path");
33
        }
34
35
        $renderer = function () use ($path, $params): void {
36
            extract($params, EXTR_OVERWRITE);
37
            require $path;
38
        };
39
40
        $obInitialLevel = ob_get_level();
41
        ob_start();
42
        ob_implicit_flush(0);
43
        try {
44
            $renderer->bindTo($this)();
45
            return ob_get_clean();
46
        } catch (\Throwable $e) {
47
            while (ob_get_level() > $obInitialLevel) {
48
                if ([email protected]_end_clean()) {
49
                    ob_clean();
50
                }
51
            }
52
            throw $e;
53
        }
54
    }
55
56
    /**
57
     * Renders the previous exception stack for a given Exception.
58
     * @param \Throwable $t the exception whose precursors should be rendered.
59
     * @return string HTML content of the rendered previous exceptions.
60
     * Empty string if there are none.
61
     * @throws \Throwable
62
     */
63
    private function renderPreviousExceptions(\Throwable $t): string
0 ignored issues
show
Unused Code introduced by Alexander Makarov
The method renderPreviousExceptions() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
64
    {
65
        if (($previous = $t->getPrevious()) !== null) {
66
            return $this->renderTemplate('previousException', ['throwable' => $previous]);
67
        }
68
        return '';
69
    }
70
71
    /**
72
     * Renders a single call stack element.
73
     * @param string|null $file name where call has happened.
74
     * @param int|null $line number on which call has happened.
75
     * @param string|null $class called class name.
76
     * @param string|null $method called function/method name.
77
     * @param array $args array of method arguments.
78
     * @param int $index number of the call stack element.
79
     * @return string HTML content of the rendered call stack element.
80
     * @throws \Throwable
81
     */
82
    private function renderCallStackItem(?string $file, ?int $line, ?string $class, ?string $method, array $args, int $index): string
83
    {
84
        $lines = [];
85
        $begin = $end = 0;
86
        if ($file !== null && $line !== null) {
87
            $line--; // adjust line number from one-based to zero-based
88
            $lines = @file($file);
89
            if ($line < 0 || $lines === false || ($lineCount = count($lines)) < $line) {
90
                return '';
91
            }
92
            $half = (int)(($index === 1 ? $this->maxSourceLines : $this->maxTraceLines) / 2);
93
            $begin = $line - $half > 0 ? $line - $half : 0;
94
            $end = $line + $half < $lineCount ? $line + $half : $lineCount - 1;
95
        }
96
        return $this->renderTemplate('callStackItem', [
97
            'file' => $file,
98
            'line' => $line,
99
            'class' => $class,
100
            'method' => $method,
101
            'index' => $index,
102
            'lines' => $lines,
103
            'begin' => $begin,
104
            'end' => $end,
105
            'args' => $args,
106
        ]);
107
    }
108
109
    /**
110
     * Renders call stack.
111
     * @param \Throwable $t exception to get call stack from
112
     * @return string HTML content of the rendered call stack.
113
     * @throws \Throwable
114
     */
115
    private function renderCallStack(\Throwable $t): string
0 ignored issues
show
Unused Code introduced by Alexander Makarov
The method renderCallStack() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
116
    {
117
        $out = '<ul>';
118
        $out .= $this->renderCallStackItem($t->getFile(), $t->getLine(), null, null, [], 1);
119
        for ($i = 0, $trace = $t->getTrace(), $length = count($trace); $i < $length; ++$i) {
120
            $file = !empty($trace[$i]['file']) ? $trace[$i]['file'] : null;
121
            $line = !empty($trace[$i]['line']) ? $trace[$i]['line'] : null;
122
            $class = !empty($trace[$i]['class']) ? $trace[$i]['class'] : null;
123
            $function = null;
124
            if (!empty($trace[$i]['function']) && $trace[$i]['function'] !== 'unknown') {
125
                $function = $trace[$i]['function'];
126
            }
127
            $args = !empty($trace[$i]['args']) ? $trace[$i]['args'] : [];
128
            $out .= $this->renderCallStackItem($file, $line, $class, $function, $args, $i + 2);
129
        }
130
        $out .= '</ul>';
131
        return $out;
132
    }
133
134
    /**
135
     * Determines whether given name of the file belongs to the framework.
136
     * @param string $file name to be checked.
137
     * @return bool whether given name of the file belongs to the framework.
138
     */
139
    private function isCoreFile(?string $file): bool
0 ignored issues
show
Unused Code introduced by Alexander Makarov
The method isCoreFile() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
140
    {
141
        return $file === null || strpos(realpath($file), Info::frameworkPath() . DIRECTORY_SEPARATOR) === 0;
142
    }
143
144
    /**
145
     * Adds informational links to the given PHP type/class.
146
     * @param string $code type/class name to be linkified.
147
     * @param string $title custom title to use
148
     * @return string linkified with HTML type/class name.
149
     * @throws \ReflectionException
150
     */
151
    private function addTypeLinks(string $code, string $title = null): string
0 ignored issues
show
Unused Code introduced by Alexander Makarov
The method addTypeLinks() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
152
    {
153
        if (preg_match('/(.*?)::([^(]+)/', $code, $matches)) {
154
            [, $class, $method] = $matches;
155
            $text = $title ? $this->htmlEncode($title) : $this->htmlEncode($class) . '::' . $this->htmlEncode($method);
156
        } else {
157
            $class = $code;
158
            $method = null;
159
            $text = $title ? $this->htmlEncode($title) : $this->htmlEncode($class);
160
        }
161
        $url = null;
162
        $shouldGenerateLink = true;
163
        if ($method !== null && substr_compare($method, '{closure}', -9) !== 0) {
164
            $reflection = new \ReflectionClass($class);
165
            if ($reflection->hasMethod($method)) {
166
                $reflectionMethod = $reflection->getMethod($method);
167
                $shouldGenerateLink = $reflectionMethod->isPublic() || $reflectionMethod->isProtected();
168
            } else {
169
                $shouldGenerateLink = false;
170
            }
171
        }
172
        if ($shouldGenerateLink) {
173
            $url = $this->getTypeUrl($class, $method);
174
        }
175
        if ($url === null) {
176
            return $text;
177
        }
178
        return '<a href="' . $url . '" target="_blank">' . $text . '</a>';
179
    }
180
181
    /**
182
     * Returns the informational link URL for a given PHP type/class.
183
     * @param string $class the type or class name.
184
     * @param string|null $method the method name.
185
     * @return string|null the informational link URL.
186
     * @see addTypeLinks()
187
     */
188
    private function getTypeUrl(?string $class, ?string $method): ?string
189
    {
190
        if (strncmp($class, 'Yiisoft\\', 8) !== 0) {
191
            return null;
192
        }
193
        $page = $this->htmlEncode(strtolower(str_replace('\\', '-', $class)));
194
        $url = "http://www.yiiframework.com/doc-3.0/$page.html";
195
        if ($method) {
196
            $url .= "#$method()-detail";
197
        }
198
        return $url;
199
    }
200
201
    /**
202
     * Converts arguments array to its string representation.
203
     *
204
     * @param array $args arguments array to be converted
205
     * @return string string representation of the arguments array
206
     */
207
    private function argumentsToString(array $args): string
208
    {
209
        $count = 0;
210
        $isAssoc = $args !== array_values($args);
211
        foreach ($args as $key => $value) {
212
            $count++;
213
            if ($count >= 5) {
214
                if ($count > 5) {
215
                    unset($args[$key]);
216
                } else {
217
                    $args[$key] = '...';
218
                }
219
                continue;
220
            }
221
            if (is_object($value)) {
222
                $args[$key] = '<span class="title">' . $this->htmlEncode(get_class($value)) . '</span>';
223
            } elseif (is_bool($value)) {
224
                $args[$key] = '<span class="keyword">' . ($value ? 'true' : 'false') . '</span>';
225
            } elseif (is_string($value)) {
226
                $fullValue = $this->htmlEncode($value);
227
                if (mb_strlen($value, 'UTF-8') > 32) {
228
                    $displayValue = $this->htmlEncode(mb_substr($value, 0, 32, 'UTF-8')) . '...';
229
                    $args[$key] = "<span class=\"string\" title=\"$fullValue\">'$displayValue'</span>";
230
                } else {
231
                    $args[$key] = "<span class=\"string\">'$fullValue'</span>";
232
                }
233
            } elseif (is_array($value)) {
234
                $args[$key] = '[' . $this->argumentsToString($value) . ']';
235
            } elseif ($value === null) {
236
                $args[$key] = '<span class="keyword">null</span>';
237
            } elseif (is_resource($value)) {
238
                $args[$key] = '<span class="keyword">resource</span>';
239
            } else {
240
                $args[$key] = '<span class="number">' . $value . '</span>';
241
            }
242
            if (is_string($key)) {
243
                $args[$key] = '<span class="string">\'' . $this->htmlEncode($key) . "'</span> => $args[$key]";
244
            } elseif ($isAssoc) {
245
                $args[$key] = "<span class=\"number\">$key</span> => $args[$key]";
246
            }
247
        }
248
        return implode(', ', $args);
249
    }
250
251
    /**
252
     * Renders the information about request.
253
     * @return string the rendering result
254
     */
255
    private function renderRequest(): string
0 ignored issues
show
Unused Code introduced by Alexander Makarov
The method renderRequest() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
256
    {
257
        if ($this->request === null) {
258
            return '';
259
        }
260
261
        $request = $this->request;
262
263
        $output = '';
264
265
        $output .= $request->getMethod() . ' ' . $request->getUri() . "\n";
266
267
        foreach ($request->getHeaders() as $name => $values) {
268
            if ($name === 'Host') {
269
                continue;
270
            }
271
272
            foreach ($values as $value) {
273
                $output .= "$name: $value\n";
274
            }
275
        }
276
277
        $output .= "\n" . $request->getBody() . "\n\n";
278
279
        return '<pre>' . $this->htmlEncode(rtrim($output, "\n")) . '</pre>';
280
    }
281
282
    private function renderCurl(): string
0 ignored issues
show
Unused Code introduced by Alexander Kartavenko
The method renderCurl() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
283
    {
284
        try {
285
            $output = (new Command())->setRequest($this->request)->build();
286
        } catch (\Throwable $e) {
287
            $output = 'Error generating curl command: ' . $e->getMessage();
288
        }
289
290
        return $this->htmlEncode($output);
291
    }
292
293
294
    /**
295
     * Creates string containing HTML link which refers to the home page of determined web-server software
296
     * and its full name.
297
     * @return string server software information hyperlink.
298
     */
299
    private function createServerInformationLink(): string
0 ignored issues
show
Unused Code introduced by Alexander Makarov
The method createServerInformationLink() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
300
    {
301
        $serverUrls = [
302
            'http://httpd.apache.org/' => ['apache'],
303
            'http://nginx.org/' => ['nginx'],
304
            'http://lighttpd.net/' => ['lighttpd'],
305
            'http://gwan.com/' => ['g-wan', 'gwan'],
306
            'http://iis.net/' => ['iis', 'services'],
307
            'https://secure.php.net/manual/en/features.commandline.webserver.php' => ['development'],
308
        ];
309
        if (isset($_SERVER['SERVER_SOFTWARE'])) {
310
            foreach ($serverUrls as $url => $keywords) {
311
                foreach ($keywords as $keyword) {
312
                    if (stripos($_SERVER['SERVER_SOFTWARE'], $keyword) !== false) {
313
                        return '<a href="' . $url . '" target="_blank">' . $this->htmlEncode($_SERVER['SERVER_SOFTWARE']) . '</a>';
314
                    }
315
                }
316
            }
317
        }
318
        return '';
319
    }
320
321
    /**
322
     * Creates string containing HTML link which refers to the page with the current version
323
     * of the framework and version number text.
324
     * @return string framework version information hyperlink.
325
     */
326
    public function createFrameworkVersionLink(): string
327
    {
328
        return '<a href="http://github.com/yiisoft/yii2/" target="_blank">' . $this->htmlEncode(Info::frameworkVersion()) . '</a>';
329
    }
330
}
331