Completed
Push — master ( e43f1a...57db71 )
by Arman
26s queued 12s
created

ErrorHandler::setup()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 6
c 1
b 0
f 0
dl 0
loc 13
rs 10
cc 3
nc 1
nop 0
1
<?php
2
3
/**
4
 * Quantum PHP Framework
5
 *
6
 * An open source software development framework for PHP
7
 *
8
 * @package Quantum
9
 * @author Arman Ag. <[email protected]>
10
 * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
11
 * @link http://quantum.softberg.org/
12
 * @since 2.4.0
13
 */
14
15
namespace Quantum\Tracer;
16
17
use Quantum\Libraries\Storage\FileSystem;
18
use Quantum\Factory\ViewFactory;
19
use Quantum\Http\Request\File;
20
use Quantum\Logger\FileLogger;
21
use Quantum\Http\Response;
22
use Quantum\Logger\Logger;
23
use Quantum\Di\Di;
24
use ErrorException;
25
use Throwable;
26
27
/**
28
 * Class ErrorHandler
29
 * @package Quantum\Tracer
30
 */
31
class ErrorHandler
32
{
33
34
    /**
35
     * Number of lines to be returned
36
     */
37
    const NUM_LINES = 10;
38
39
    /**
40
     * @var array
41
     */
42
    private static $trace = [];
43
44
    /**
45
     * @var \string[][]
46
     */
47
    private static $errorTypes = [
48
        E_ERROR => ['fn' => 'error', 'severity' => 'Error'],
49
        E_WARNING => ['fn' => 'warning', 'severity' => 'Warning'],
50
        E_PARSE => ['fn' => 'error', 'severity' => 'Parsing Error'],
51
        E_NOTICE => ['fn' => 'notice', 'severity' => 'Notice'],
52
        E_CORE_ERROR => ['fn' => 'error', 'severity' => 'Core Error'],
53
        E_CORE_WARNING => ['fn' => 'warning', 'severity' => 'Core Warning'],
54
        E_COMPILE_ERROR => ['fn' => 'error', 'severity' => 'Compile Error'],
55
        E_COMPILE_WARNING => ['fn' => 'warning', 'severity' => 'Compile Warning'],
56
        E_USER_ERROR => ['fn' => 'error', 'severity' => 'User Error'],
57
        E_USER_WARNING => ['fn' => 'warning', 'severity' => 'User Warning'],
58
        E_USER_NOTICE => ['fn' => 'notice', 'severity' => 'User Notice'],
59
        E_STRICT => ['fn' => 'notice', 'severity' => 'Runtime Notice'],
60
        E_RECOVERABLE_ERROR => ['fn' => 'error', 'severity' => 'Catchable Fatal Error']
61
    ];
62
63
    /**
64
     * Setups the handlers
65
     * @throws \ErrorException
66
     */
67
    public static function setup()
68
    {
69
        set_error_handler(function ($severity, $message, $file, $line) {
70
71
            if (!(error_reporting() && $severity)) {
72
                return;
73
            }
74
75
            throw new ErrorException($message, 0, $severity, $file, $line);
76
        });
77
78
        set_exception_handler(function (Throwable $e) {
79
            self::handle($e);
80
        });
81
    }
82
83
    /**
84
     * Handles errors and exceptions
85
     * @param \Throwable $e
86
     * @throws \Quantum\Exceptions\DiException
87
     * @throws \Quantum\Exceptions\ViewException
88
     * @throws \ReflectionException
89
     */
90
    protected static function handle(Throwable $e)
91
    {
92
        self::composeStackTrace($e);
93
94
        $fn = 'info';
95
        $severity = null;
96
97
        $errorType = self::getErrorType($e);
98
99
        if ($errorType) {
100
            extract($errorType);
101
        }
102
103
        $view = ViewFactory::getInstance();
104
105
        if (filter_var(config()->get('debug'), FILTER_VALIDATE_BOOLEAN)) {
106
            Response::html($view->renderPartial('errors/trace', ['stackTrace' => self::$trace, 'errorMessage' => $e->getMessage(), 'severity' => $severity]));
107
            Response::send();
108
        } else {
109
            $logFile = LOGS_DIR . DS . date('Y-m-d') . '.log';
110
111
            $fileLogger = new FileLogger($logFile);
112
113
            $logger = new Logger($fileLogger);
114
115
            $logMessage = '[' . date('Y-m-d H:i:s') . '] ' . $severity . ': ' . $e->getMessage() . PHP_EOL . $e->getTraceAsString() . PHP_EOL;
116
117
            $logger->$fn($logMessage);
118
119
            Response::html($view->renderPartial('errors/500'));
120
            Response::send();
121
        }
122
123
        exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
124
    }
125
126
    /**
127
     * Composes the stack trace
128
     * @param \Throwable $e
129
     */
130
    protected static function composeStackTrace(Throwable $e)
131
    {
132
        self::$trace[] = [
133
            'file' => $e->getFile(),
134
            'code' => self::getSourceCode($e->getFile(), $e->getLine(), 'error-line')
135
        ];
136
137
        foreach ($e->getTrace() as $item) {
138
            if (isset($item['class']) && $item['class'] == __CLASS__) {
139
                continue;
140
            }
141
142
            if (isset($item['file'])) {
143
                self::$trace[] = [
144
                    'file' => $item['file'],
145
                    'code' => self::getSourceCode($item['file'], $item['line'], 'switch-line')
146
                ];
147
            }
148
        }
149
    }
150
151
    /**
152
     * Gets the source code where the error happens
153
     * @param string $filename
154
     * @param int $lineNumber
155
     * @param string $className
156
     * @return string
157
     * @throws \Quantum\Exceptions\DiException
158
     * @throws \ReflectionException
159
     */
160
    protected static function getSourceCode(string $filename, int $lineNumber, string $className)
161
    {
162
        $fs = Di::get(FileSystem::class);
163
164
        $start = max($lineNumber - floor(self::NUM_LINES / 2), 1);
165
166
        $lines = $fs->getLines($filename, $start, self::NUM_LINES, FILE_IGNORE_NEW_LINES);
167
168
        $code = '<ol start="' . key($lines) . '">';
169
        foreach ($lines as $currentLineNumber => $line) {
170
            $code .= '<li ' . ($currentLineNumber == $lineNumber - 1 ? 'class="' . $className . '"' : '') . '><pre>' . $line . '</pre></li>';
171
        }
172
        $code .= '</ol>';
173
174
        return $code;
175
    }
176
177
    /**
178
     * Gets the error type
179
     * @param \Throwable $e
180
     * @return string[]|null
181
     */
182
    private static function getErrorType(Throwable $e)
183
    {
184
        if ($e instanceof ErrorException) {
185
            $severity = $e->getSeverity();
186
187
            return self::$errorTypes[$severity] ?? null;
188
        } else if ($e->getCode()) {
189
            return self::$errorTypes[$e->getCode()] ?? null;
190
        }
191
192
        return null;
193
    }
194
}