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

aplus-framework / debug / 9591460565

24 Apr 2024 07:27PM UTC coverage: 99.634%. Remained the same
9591460565

push

github

natanfelles
Upgrade helpers library to version 4

544 of 546 relevant lines covered (99.63%)

2.78 hits per line

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

99.21
/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
    public const string DEVELOPMENT = 'development';
32
    /**
33
     * Production environment.
34
     */
35
    public const string PRODUCTION = 'production';
36
    protected string $developmentView = __DIR__ . '/Views/exceptions/development.php';
37
    protected string $productionView = __DIR__ . '/Views/exceptions/production.php';
38
    protected ?Logger $logger = null;
39
    protected string $environment = ExceptionHandler::PRODUCTION;
40
    protected Language $language;
41
    protected bool $testing = false;
42
    protected SearchEngines $searchEngines;
43

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

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

79
    public function getEnvironment() : string
80
    {
81
        return $this->environment;
9✔
82
    }
83

84
    /**
85
     * @return Logger|null
86
     */
87
    public function getLogger() : ?Logger
88
    {
89
        return $this->logger;
10✔
90
    }
91

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

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

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

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

127
    public function getDevelopmentView() : string
128
    {
129
        return $this->developmentView;
3✔
130
    }
131

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

138
    public function getProductionView() : string
139
    {
140
        return $this->productionView;
3✔
141
    }
142

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

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

185
    protected function isCli() : bool
186
    {
187
        return \PHP_SAPI === 'cli' || \defined('STDIN');
1✔
188
    }
189

190
    protected function isJson() : bool
191
    {
192
        return isset($_SERVER['HTTP_CONTENT_TYPE'])
8✔
193
            && \str_starts_with($_SERVER['HTTP_CONTENT_TYPE'], 'application/json');
8✔
194
    }
195

196
    protected function acceptJson() : bool
197
    {
198
        return isset($_SERVER['HTTP_ACCEPT'])
6✔
199
            && \str_contains($_SERVER['HTTP_ACCEPT'], 'application/json');
6✔
200
    }
201

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

224
    protected function sendHeaders() : void
225
    {
226
        $contentType = 'text/html';
8✔
227
        if ($this->isJson() || $this->acceptJson()) {
8✔
228
            $contentType = 'application/json';
4✔
229
        }
230
        \header('Content-Type: ' . $contentType . '; charset=UTF-8');
8✔
231
    }
232

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

249
    protected function log(string $message) : void
250
    {
251
        $this->getLogger()?->logCritical($message);
9✔
252
    }
253

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

305
    public function getSearchEngines() : SearchEngines
306
    {
307
        if (!isset($this->searchEngines)) {
2✔
308
            $this->setSearchEngines(new SearchEngines());
2✔
309
        }
310
        return $this->searchEngines;
2✔
311
    }
312

313
    public function setSearchEngines(SearchEngines $searchEngines) : static
314
    {
315
        $this->searchEngines = $searchEngines;
2✔
316
        return $this;
2✔
317
    }
318
}
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