• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

aplus-framework / debug / 5102359423

pending completion
5102359423

push

github

natanfelles
Add Debugger::roundVersion method

513 of 515 relevant lines covered (99.61%)

2.61 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.17
/src/ExceptionHandler.php
1
<?php declare(strict_types=1);
2
/*
3
 * This file is part of Aplus Framework Debug Library.
4
 *
5
 * (c) Natan Felles <natanfelles@gmail.com>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace Framework\Debug;
11

12
use ErrorException;
13
use Framework\CLI\CLI;
14
use Framework\Helpers\Isolation;
15
use Framework\Language\Language;
16
use Framework\Log\Logger;
17
use InvalidArgumentException;
18
use RuntimeException;
19
use Throwable;
20

21
/**
22
 * Class ExceptionHandler.
23
 *
24
 * @package debug
25
 */
26
class ExceptionHandler
27
{
28
    /**
29
     * Development environment.
30
     *
31
     * @var string
32
     */
33
    public const DEVELOPMENT = 'development';
34
    /**
35
     * Production environment.
36
     *
37
     * @var string
38
     */
39
    public const PRODUCTION = 'production';
40
    protected string $developmentView = __DIR__ . '/Views/exceptions/development.php';
41
    protected string $productionView = __DIR__ . '/Views/exceptions/production.php';
42
    protected ?Logger $logger = null;
43
    protected string $environment = ExceptionHandler::PRODUCTION;
44
    protected Language $language;
45
    protected bool $testing = false;
46

47
    /**
48
     * ExceptionHandler constructor.
49
     *
50
     * @param string $environment
51
     * @param Logger|null $logger
52
     * @param Language|null $language
53
     *
54
     * @throws InvalidArgumentException if environment is invalid
55
     */
56
    public function __construct(
57
        string $environment = ExceptionHandler::PRODUCTION,
58
        Logger $logger = null,
59
        Language $language = null
60
    ) {
61
        $this->setEnvironment($environment);
15✔
62
        if ($logger) {
15✔
63
            $this->logger = $logger;
3✔
64
        }
65
        if ($language) {
15✔
66
            $this->setLanguage($language);
1✔
67
        }
68
    }
69

70
    public function setEnvironment(string $environment) : static
71
    {
72
        if ( ! \in_array($environment, [
15✔
73
            static::DEVELOPMENT,
15✔
74
            static::PRODUCTION,
15✔
75
        ], true)) {
15✔
76
            throw new InvalidArgumentException('Invalid environment: ' . $environment);
1✔
77
        }
78
        $this->environment = $environment;
15✔
79
        return $this;
15✔
80
    }
81

82
    public function getEnvironment() : string
83
    {
84
        return $this->environment;
7✔
85
    }
86

87
    /**
88
     * @return Logger|null
89
     */
90
    public function getLogger() : ?Logger
91
    {
92
        return $this->logger;
8✔
93
    }
94

95
    public function setLanguage(Language $language = null) : static
96
    {
97
        $this->language = $language ?? new Language();
7✔
98
        $this->language->addDirectory(__DIR__ . '/Languages');
7✔
99
        return $this;
7✔
100
    }
101

102
    /**
103
     * @return Language
104
     */
105
    public function getLanguage() : Language
106
    {
107
        if ( ! isset($this->language)) {
7✔
108
            $this->setLanguage();
7✔
109
        }
110
        return $this->language;
7✔
111
    }
112

113
    protected function validateView(string $file) : string
114
    {
115
        $realpath = \realpath($file);
2✔
116
        if ( ! $realpath || ! \is_file($realpath)) {
2✔
117
            throw new InvalidArgumentException(
2✔
118
                'Invalid exceptions view file: ' . $file
2✔
119
            );
2✔
120
        }
121
        return $realpath;
2✔
122
    }
123

124
    public function setDevelopmentView(string $file) : static
125
    {
126
        $this->developmentView = $this->validateView($file);
1✔
127
        return $this;
1✔
128
    }
129

130
    public function getDevelopmentView() : string
131
    {
132
        return $this->developmentView;
3✔
133
    }
134

135
    public function setProductionView(string $file) : static
136
    {
137
        $this->productionView = $this->validateView($file);
1✔
138
        return $this;
1✔
139
    }
140

141
    public function getProductionView() : string
142
    {
143
        return $this->productionView;
3✔
144
    }
145

146
    public function initialize(bool $handleErrors = true) : void
147
    {
148
        \set_exception_handler([$this, 'exceptionHandler']);
4✔
149
        if ($handleErrors) {
4✔
150
            \set_error_handler([$this, 'errorHandler']);
4✔
151
        }
152
    }
153

154
    /**
155
     * Exception handler.
156
     *
157
     * @param Throwable $exception The Throwable, exception, instance
158
     *
159
     * @throws RuntimeException if view file is not found
160
     */
161
    public function exceptionHandler(Throwable $exception) : void
162
    {
163
        if (\ob_get_length()) {
7✔
164
            \ob_end_clean();
×
165
        }
166
        $this->log((string) $exception);
7✔
167
        if ($this->isCli()) {
7✔
168
            $this->cliError($exception);
1✔
169
            return;
1✔
170
        }
171
        \http_response_code(500);
6✔
172
        if ( ! \headers_sent()) {
6✔
173
            $this->sendHeaders();
6✔
174
        }
175
        if ($this->isJson()) {
6✔
176
            $this->sendJson($exception);
2✔
177
            return;
2✔
178
        }
179
        $file = $this->getEnvironment() === static::DEVELOPMENT
4✔
180
            ? $this->getDevelopmentView()
2✔
181
            : $this->getProductionView();
2✔
182
        Isolation::require($file, [
4✔
183
            'handler' => $this,
4✔
184
            'exception' => $exception,
4✔
185
        ]);
4✔
186
    }
187

188
    protected function isCli() : bool
189
    {
190
        return \PHP_SAPI === 'cli' || \defined('STDIN');
1✔
191
    }
192

193
    protected function isJson() : bool
194
    {
195
        return isset($_SERVER['HTTP_CONTENT_TYPE'])
6✔
196
            && \str_starts_with($_SERVER['HTTP_CONTENT_TYPE'], 'application/json');
6✔
197
    }
198

199
    protected function sendJson(Throwable $exception) : void
200
    {
201
        $data = $this->getEnvironment() === static::DEVELOPMENT
2✔
202
            ? [
1✔
203
                'exception' => $exception::class,
1✔
204
                'message' => $exception->getMessage(),
1✔
205
                'file' => $exception->getFile(),
1✔
206
                'line' => $exception->getLine(),
1✔
207
                'trace' => $exception->getTrace(),
1✔
208
            ]
1✔
209
            : [
1✔
210
                'message' => $this->getLanguage()->render('debug', 'exceptionDescription'),
1✔
211
            ];
1✔
212
        echo \json_encode([
2✔
213
            'status' => [
2✔
214
                'code' => 500,
2✔
215
                'reason' => 'Internal Server Error',
2✔
216
            ],
2✔
217
            'data' => $data,
2✔
218
        ], \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE);
2✔
219
    }
220

221
    protected function sendHeaders() : void
222
    {
223
        $contentType = 'text/html';
6✔
224
        if ($this->isJson()) {
6✔
225
            $contentType = 'application/json';
2✔
226
        }
227
        \header('Content-Type: ' . $contentType . '; charset=UTF-8');
6✔
228
    }
229

230
    protected function cliError(Throwable $exception) : void
231
    {
232
        $language = $this->getLanguage();
1✔
233
        $message = $language->render('debug', 'exception')
1✔
234
            . ': ' . $exception::class . \PHP_EOL;
1✔
235
        $message .= $language->render('debug', 'message')
1✔
236
            . ': ' . $exception->getMessage() . \PHP_EOL;
1✔
237
        $message .= $language->render('debug', 'file')
1✔
238
            . ': ' . $exception->getFile() . \PHP_EOL;
1✔
239
        $message .= $language->render('debug', 'line')
1✔
240
            . ': ' . $exception->getLine() . \PHP_EOL;
1✔
241
        $message .= $language->render('debug', 'trace')
1✔
242
            . ': ' . $exception->getTraceAsString();
1✔
243
        CLI::error($message, $this->testing ? null : 1);
1✔
244
    }
245

246
    protected function log(string $message) : void
247
    {
248
        $this->getLogger()?->logCritical($message);
7✔
249
    }
250

251
    /**
252
     * Error handler.
253
     *
254
     * @param int $errno The level of the error raised
255
     * @param string $errstr The error message
256
     * @param string|null $errfile The filename that the error was raised in
257
     * @param int|null $errline The line number where the error was raised
258
     *
259
     * @see http://php.net/manual/en/function.set-error-handler.php
260
     *
261
     * @throws ErrorException if the error is included in the error_reporting
262
     *
263
     * @return bool
264
     */
265
    public function errorHandler(
266
        int $errno,
267
        string $errstr,
268
        string $errfile = null,
269
        int $errline = null
270
    ) : bool {
271
        if ( ! (\error_reporting() & $errno)) {
4✔
272
            return true;
4✔
273
        }
274
        $type = match ($errno) {
4✔
275
            \E_ERROR => 'Error',
4✔
276
            \E_WARNING => 'Warning',
4✔
277
            \E_PARSE => 'Parse',
4✔
278
            \E_NOTICE => 'Notice',
4✔
279
            \E_CORE_ERROR => 'Core Error',
4✔
280
            \E_CORE_WARNING => 'Core Warning',
4✔
281
            \E_COMPILE_ERROR => 'Compile Error',
4✔
282
            \E_COMPILE_WARNING => 'Compile Warning',
4✔
283
            \E_USER_ERROR => 'User Error',
4✔
284
            \E_USER_WARNING => 'User Warning',
4✔
285
            \E_USER_NOTICE => 'User Notice',
4✔
286
            \E_STRICT => 'Strict',
4✔
287
            \E_RECOVERABLE_ERROR => 'Recoverable Error',
4✔
288
            \E_DEPRECATED => 'Deprecated',
4✔
289
            \E_USER_DEPRECATED => 'User Deprecated',
4✔
290
            \E_ALL => 'All',
4✔
291
            default => '',
4✔
292
        };
4✔
293
        throw new ErrorException(
4✔
294
            ($type ? $type . ': ' : '') . $errstr,
4✔
295
            0,
4✔
296
            $errno,
4✔
297
            $errfile,
4✔
298
            $errline
4✔
299
        );
4✔
300
    }
301
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc