Passed
Push — master ( 765510...8508e9 )
by Dmitriy
06:19 queued 04:17
created

HtmlRenderer::addTypeLinks()   C

Complexity

Conditions 11
Paths 144

Size

Total Lines 32
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 11.0908

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
eloc 24
c 1
b 0
f 0
nc 144
nop 2
dl 0
loc 32
ccs 20
cts 22
cp 0.9091
crap 11.0908
rs 6.95

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\Web\ErrorHandler;
6
7
use Alexkart\CurlBuilder\Command;
8
use Yiisoft\Yii\Web\Info;
9
10
final class HtmlRenderer extends ThrowableRenderer
11
{
12
    private int $maxSourceLines = 19;
13
    private int $maxTraceLines = 13;
14
15
    private string $traceLine = '{html}';
16
17
    private string $defaultTemplatePath;
18
19
    private string $errorTemplate;
20
    private string $exceptionTemplate;
21
22 5
    public function __construct(array $templates = [])
23
    {
24 5
        $this->defaultTemplatePath = $templates['path'];
25 5
        $this->errorTemplate = $templates['error'] ?? $this->defaultTemplatePath . '/error.php';
26 5
        $this->exceptionTemplate = $templates['exception'] ?? $this->defaultTemplatePath . '/exception.php';
27 5
    }
28
29
    public function withMaxSourceLines(int $maxSourceLines): self
30
    {
31
        $new = clone $this;
32
        $new->maxSourceLines = $maxSourceLines;
33
        return $new;
34
    }
35
36
    public function withMaxTraceLines(int $maxTraceLines): self
37
    {
38
        $new = clone $this;
39
        $new->maxTraceLines = $maxTraceLines;
40
        return $new;
41
    }
42
43
    public function withTraceLine(string $traceLine): self
44
    {
45
        $new = clone $this;
46
        $new->traceLine = $traceLine;
47
        return $new;
48
    }
49
50 3
    public function render(\Throwable $t): string
51
    {
52 3
        return $this->renderTemplate($this->errorTemplate, [
53 3
            'throwable' => $t,
54
        ]);
55
    }
56
57 2
    public function renderVerbose(\Throwable $t): string
58
    {
59 2
        return $this->renderTemplate($this->exceptionTemplate, [
60 2
            'throwable' => $t,
61
        ]);
62
    }
63
64 2
    private function htmlEncode(string $text): string
65
    {
66 2
        return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
67
    }
68
69 5
    private function renderTemplate(string $path, array $params): string
70
    {
71 5
        if (!file_exists($path)) {
72 1
            throw new \RuntimeException("Template not found at $path");
73
        }
74
75 4
        $renderer = function (): void {
76 4
            extract(func_get_arg(1), EXTR_OVERWRITE);
77 4
            require func_get_arg(0);
78 4
        };
79
80 4
        $obInitialLevel = ob_get_level();
81 4
        ob_start();
82 4
        ob_implicit_flush(0);
83
        try {
84 4
            $renderer->bindTo($this)($path, $params);
85 4
            return ob_get_clean();
86
        } catch (\Throwable $e) {
87
            while (ob_get_level() > $obInitialLevel) {
88
                if ([email protected]_end_clean()) {
89
                    ob_clean();
90
                }
91
            }
92
            throw $e;
93
        }
94
    }
95
96
    /**
97
     * Renders the previous exception stack for a given Exception.
98
     * @param \Throwable $t the exception whose precursors should be rendered.
99
     * @return string HTML content of the rendered previous exceptions.
100
     * Empty string if there are none.
101
     * @throws \Throwable
102
     */
103 1
    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...
104
    {
105 1
        if (($previous = $t->getPrevious()) !== null) {
106
            $templatePath = $this->defaultTemplatePath . '/previousException.php';
107
            return $this->renderTemplate($templatePath, ['throwable' => $previous]);
108
        }
109 1
        return '';
110
    }
111
112
    /**
113
     * Renders a single call stack element.
114
     * @param string|null $file name where call has happened.
115
     * @param int|null $line number on which call has happened.
116
     * @param string|null $class called class name.
117
     * @param string|null $method called function/method name.
118
     * @param array $args array of method arguments.
119
     * @param int $index number of the call stack element.
120
     * @return string HTML content of the rendered call stack element.
121
     * @throws \Throwable
122
     */
123 1
    private function renderCallStackItem(?string $file, ?int $line, ?string $class, ?string $method, array $args, int $index): string
124
    {
125 1
        $lines = [];
126 1
        $begin = $end = 0;
127 1
        if ($file !== null && $line !== null) {
128 1
            $line--; // adjust line number from one-based to zero-based
129 1
            $lines = @file($file);
130 1
            if ($line < 0 || $lines === false || ($lineCount = count($lines)) < $line) {
131
                return '';
132
            }
133 1
            $half = (int)(($index === 1 ? $this->maxSourceLines : $this->maxTraceLines) / 2);
134 1
            $begin = $line - $half > 0 ? $line - $half : 0;
135 1
            $end = $line + $half < $lineCount ? $line + $half : $lineCount - 1;
136
        }
137 1
        $templatePath = $this->defaultTemplatePath . '/callStackItem.php';
138 1
        return $this->renderTemplate($templatePath, [
139 1
            'file' => $file,
140 1
            'line' => $line,
141 1
            'class' => $class,
142 1
            'method' => $method,
143 1
            'index' => $index,
144 1
            'lines' => $lines,
145 1
            'begin' => $begin,
146 1
            'end' => $end,
147 1
            'args' => $args,
148
        ]);
149
    }
150
151
    /**
152
     * Renders call stack.
153
     * @param \Throwable $t exception to get call stack from
154
     * @return string HTML content of the rendered call stack.
155
     * @throws \Throwable
156
     */
157 1
    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...
158
    {
159 1
        $out = '<ul>';
160 1
        $out .= $this->renderCallStackItem($t->getFile(), $t->getLine(), null, null, [], 1);
161 1
        for ($i = 0, $trace = $t->getTrace(), $length = count($trace); $i < $length; ++$i) {
162 1
            $file = !empty($trace[$i]['file']) ? $trace[$i]['file'] : null;
163 1
            $line = !empty($trace[$i]['line']) ? $trace[$i]['line'] : null;
164 1
            $class = !empty($trace[$i]['class']) ? $trace[$i]['class'] : null;
165 1
            $function = null;
166 1
            if (!empty($trace[$i]['function']) && $trace[$i]['function'] !== 'unknown') {
167 1
                $function = $trace[$i]['function'];
168
            }
169 1
            $args = !empty($trace[$i]['args']) ? $trace[$i]['args'] : [];
170 1
            $out .= $this->renderCallStackItem($file, $line, $class, $function, $args, $i + 2);
171
        }
172 1
        $out .= '</ul>';
173 1
        return $out;
174
    }
175
176
    /**
177
     * Determines whether given name of the file belongs to the framework.
178
     * @param string $file name to be checked.
179
     * @return bool whether given name of the file belongs to the framework.
180
     */
181 1
    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...
182
    {
183 1
        return $file === null || strpos(realpath($file), Info::frameworkPath() . DIRECTORY_SEPARATOR) === 0;
184
    }
185
186
    /**
187
     * Adds informational links to the given PHP type/class.
188
     * @param string $code type/class name to be linkified.
189
     * @param string $title custom title to use
190
     * @return string linkified with HTML type/class name.
191
     * @throws \ReflectionException
192
     */
193 1
    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...
194
    {
195 1
        if (preg_match('/(.*?)::([^(]+)/', $code, $matches)) {
196 1
            [, $class, $method] = $matches;
197 1
            $text = $title ? $this->htmlEncode($title) : $this->htmlEncode($class) . '::' . $this->htmlEncode($method);
198
        } else {
199 1
            $class = $code;
200 1
            $method = null;
201 1
            $text = $title ? $this->htmlEncode($title) : $this->htmlEncode($class);
202
        }
203 1
        $url = null;
204 1
        $shouldGenerateLink = true;
205 1
        if ($method !== null && substr_compare($method, '{closure}', -9) !== 0) {
206
            try {
207 1
                $reflection = new \ReflectionClass($class);
208 1
                if ($reflection->hasMethod($method)) {
209 1
                    $reflectionMethod = $reflection->getMethod($method);
210 1
                    $shouldGenerateLink = $reflectionMethod->isPublic() || $reflectionMethod->isProtected();
211
                } else {
212 1
                    $shouldGenerateLink = false;
213
                }
214
            } catch (\Throwable $e) {
215
                $shouldGenerateLink = false;
216
            }
217
        }
218 1
        if ($shouldGenerateLink) {
219 1
            $url = $this->getTypeUrl($class, $method);
220
        }
221 1
        if ($url === null) {
222 1
            return $text;
223
        }
224 1
        return '<a href="' . $url . '" target="_blank">' . $text . '</a>';
225
    }
226
227
    /**
228
     * Returns the informational link URL for a given PHP type/class.
229
     * @param string $class the type or class name.
230
     * @param string|null $method the method name.
231
     * @return string|null the informational link URL.
232
     * @see addTypeLinks()
233
     */
234 1
    private function getTypeUrl(?string $class, ?string $method): ?string
235
    {
236 1
        if (strncmp($class, 'Yiisoft\\', 8) !== 0) {
237 1
            return null;
238
        }
239 1
        $page = $this->htmlEncode(strtolower(str_replace('\\', '-', $class)));
240 1
        $url = "http://www.yiiframework.com/doc-3.0/$page.html";
241 1
        if ($method) {
242 1
            $url .= "#$method()-detail";
243
        }
244 1
        return $url;
245
    }
246
247
    /**
248
     * Converts arguments array to its string representation.
249
     *
250
     * @param array $args arguments array to be converted
251
     * @return string string representation of the arguments array
252
     */
253 1
    private function argumentsToString(array $args): string
254
    {
255 1
        $count = 0;
256 1
        $isAssoc = $args !== array_values($args);
257 1
        foreach ($args as $key => $value) {
258
            $count++;
259
            if ($count >= 5) {
260
                if ($count > 5) {
261
                    unset($args[$key]);
262
                } else {
263
                    $args[$key] = '...';
264
                }
265
                continue;
266
            }
267
            if (is_object($value)) {
268
                $args[$key] = '<span class="title">' . $this->htmlEncode(get_class($value)) . '</span>';
269
            } elseif (is_bool($value)) {
270
                $args[$key] = '<span class="keyword">' . ($value ? 'true' : 'false') . '</span>';
271
            } elseif (is_string($value)) {
272
                $fullValue = $this->htmlEncode($value);
273
                if (mb_strlen($value, 'UTF-8') > 32) {
274
                    $displayValue = $this->htmlEncode(mb_substr($value, 0, 32, 'UTF-8')) . '...';
275
                    $args[$key] = "<span class=\"string\" title=\"$fullValue\">'$displayValue'</span>";
276
                } else {
277
                    $args[$key] = "<span class=\"string\">'$fullValue'</span>";
278
                }
279
            } elseif (is_array($value)) {
280
                unset($args[$key]);
281
                $args[$key] = '[' . $this->argumentsToString($value) . ']';
282
            } elseif ($value === null) {
283
                $args[$key] = '<span class="keyword">null</span>';
284
            } elseif (is_resource($value)) {
285
                $args[$key] = '<span class="keyword">resource</span>';
286
            } else {
287
                $args[$key] = '<span class="number">' . $value . '</span>';
288
            }
289
            if (is_string($key)) {
290
                $args[$key] = '<span class="string">\'' . $this->htmlEncode($key) . "'</span> => $args[$key]";
291
            } elseif ($isAssoc) {
292
                $args[$key] = "<span class=\"number\">$key</span> => $args[$key]";
293
            }
294
        }
295 1
        return implode(', ', $args);
296
    }
297
298
    /**
299
     * Renders the information about request.
300
     * @return string the rendering result
301
     */
302 1
    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...
303
    {
304 1
        if ($this->request === null) {
305
            return '';
306
        }
307
308 1
        $request = $this->request;
309 1
        $output = $request->getMethod() . ' ' . $request->getUri() . "\n";
310
311 1
        foreach ($request->getHeaders() as $name => $values) {
312 1
            if ($name === 'Host') {
313
                continue;
314
            }
315
316 1
            foreach ($values as $value) {
317 1
                $output .= "$name: $value\n";
318
            }
319
        }
320
321 1
        $output .= "\n" . $request->getBody() . "\n\n";
322
323 1
        return '<pre>' . $this->htmlEncode(rtrim($output, "\n")) . '</pre>';
324
    }
325
326 1
    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...
327
    {
328
        try {
329 1
            $output = (new Command())->setRequest($this->request)->build();
330
        } catch (\Throwable $e) {
331
            $output = 'Error generating curl command: ' . $e->getMessage();
332
        }
333
334 1
        return $this->htmlEncode($output);
335
    }
336
337
338
    /**
339
     * Creates string containing HTML link which refers to the home page of determined web-server software
340
     * and its full name.
341
     * @return string server software information hyperlink.
342
     */
343 1
    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...
344
    {
345 1
        if (isset($_SERVER['SERVER_SOFTWARE'])) {
346
            $serverUrls = [
347
                'http://httpd.apache.org/' => ['apache'],
348
                'http://nginx.org/' => ['nginx'],
349
                'http://lighttpd.net/' => ['lighttpd'],
350
                'http://gwan.com/' => ['g-wan', 'gwan'],
351
                'http://iis.net/' => ['iis', 'services'],
352
                'https://secure.php.net/manual/en/features.commandline.webserver.php' => ['development'],
353
            ];
354
355
            foreach ($serverUrls as $url => $keywords) {
356
                foreach ($keywords as $keyword) {
357
                    if (stripos($_SERVER['SERVER_SOFTWARE'], $keyword) !== false) {
358
                        return '<a href="' . $url . '" target="_blank">' . $this->htmlEncode($_SERVER['SERVER_SOFTWARE']) . '</a>';
359
                    }
360
                }
361
            }
362
        }
363 1
        return '';
364
    }
365
366
    /**
367
     * Creates string containing HTML link which refers to the page with the current version
368
     * of the framework and version number text.
369
     * @return string framework version information hyperlink.
370
     */
371 1
    public function createFrameworkVersionLink(): string
372
    {
373 1
        return '<a href="http://github.com/yiisoft/yii-web/" target="_blank">' . $this->htmlEncode(Info::frameworkVersion()) . '</a>';
374
    }
375
}
376