Completed
Push — master ( 4ad6bd...3873e4 )
by Ingo
11:53
created

DetailedErrorFormatter::format()   C

Complexity

Conditions 7
Paths 5

Size

Total Lines 41
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 26
nc 5
nop 1
dl 0
loc 41
rs 6.7272
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Logging;
4
5
use SilverStripe\Dev\Debug;
6
use Monolog\Formatter\FormatterInterface;
7
use Exception;
8
9
/**
10
 * Monolog-compatible error handler that will output a detailed error message to the screen.
11
 */
12
class DetailedErrorFormatter implements FormatterInterface
13
{
14
    public function format(array $record)
15
    {
16
        if (isset($record['context']['exception'])) {
17
            /** @var Exception $exception */
18
            $exception = $record['context']['exception'];
19
            $context = array(
20
                'code' => $exception->getCode(),
21
                'message' => 'Uncaught ' . get_class($exception) . ': ' . $exception->getMessage(),
22
                'file' => $exception->getFile(),
23
                'line' => $exception->getLine(),
24
                'trace' => $exception->getTrace(),
25
            );
26
        } else {
27
            $context = isset($record['context']) ? $record['context'] : $record;
28
            foreach (array('code','message','file','line') as $key) {
29
                if (!isset($context[$key])) {
30
                    $context[$key] = isset($record[$key]) ? $record[$key] : null;
31
                }
32
            }
33
34
            $trace = debug_backtrace();
35
36
            // Filter out monolog plumbing from the trace
37
            // If the context file & line isn't found in the trace, then the trace is most likely
38
            // call to the fatal error handler and is not useful, so exclude it entirely
39
            $i = $this->findInTrace($trace, $context['file'], $context['line']);
40
            if ($i !== null) {
41
                $context['trace'] = array_slice($trace, $i);
42
            } else {
43
                $context['trace'] = null;
44
            }
45
        }
46
47
        return $this->output(
48
            $context['code'],
49
            $context['message'],
50
            $context['file'],
51
            $context['line'],
52
            $context['trace']
53
        );
54
    }
55
56
    public function formatBatch(array $records)
57
    {
58
        return implode("\n", array_map(array($this, 'format'), $records));
59
    }
60
61
    /**
62
     * Find a call on the given file & line in the trace
63
     * @param array $trace The result of debug_backtrace()
64
     * @param string $file The filename to look for
65
     * @param string $line The line number to look for
66
     * @return int|null The matching row number, if found, or null if not found
67
     */
68
    protected function findInTrace(array $trace, $file, $line)
69
    {
70
        foreach ($trace as $i => $call) {
71
            if (isset($call['file']) && isset($call['line']) && $call['file'] == $file && $call['line'] == $line) {
72
                return $i;
73
            }
74
        }
75
        return null;
76
    }
77
78
    /**
79
     * Render a developer facing error page, showing the stack trace and details
80
     * of the code where the error occured.
81
     *
82
     * @param int $errno
83
     * @param string $errstr
84
     * @param string $errfile
85
     * @param int $errline
86
     * @param array $errcontext
87
     * @return string
88
     */
89
    protected function output($errno, $errstr, $errfile, $errline, $errcontext)
0 ignored issues
show
Coding Style introduced by
output uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
output uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
90
    {
91
        $reporter = Debug::create_debug_view();
92
93
        // Coupling alert: This relies on knowledge of how the director gets its URL, it could be improved.
94
        $httpRequest = null;
95
        if (isset($_SERVER['REQUEST_URI'])) {
96
            $httpRequest = $_SERVER['REQUEST_URI'];
97
        } elseif (isset($_REQUEST['url'])) {
98
            $httpRequest = $_REQUEST['url'];
99
        }
100
        if (isset($_SERVER['REQUEST_METHOD'])) {
101
            $httpRequest = $_SERVER['REQUEST_METHOD'] . ' ' . $httpRequest;
102
        }
103
104
        $output = $reporter->renderHeader();
105
        $output .= $reporter->renderError($httpRequest, $errno, $errstr, $errfile, $errline);
106
107
        if (file_exists($errfile)) {
108
            $lines = file($errfile);
109
110
            // Make the array 1-based
111
            array_unshift($lines, "");
112
            unset($lines[0]);
113
114
            $offset = $errline-10;
115
            $lines = array_slice($lines, $offset, 16, true);
116
            $output .= $reporter->renderSourceFragment($lines, $errline);
117
        }
118
        $output .= $reporter->renderTrace($errcontext);
119
        $output .= $reporter->renderFooter();
120
121
        return $output;
122
    }
123
}
124