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

codeigniter4 / CodeIgniter4 / 22217583285

20 Feb 2026 08:51AM UTC coverage: 85.938% (+0.3%) from 85.68%
22217583285

Pull #9966

github

web-flow
Merge 3948c7063 into 5c78ba265
Pull Request #9966: refactor: cleanup `Exceptions`

7 of 32 new or added lines in 3 files covered. (21.88%)

1 existing line in 1 file now uncovered.

22264 of 25907 relevant lines covered (85.94%)

206.87 hits per line

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

38.38
/system/Debug/Exceptions.php
1
<?php
2

3
declare(strict_types=1);
4

5
/**
6
 * This file is part of CodeIgniter 4 framework.
7
 *
8
 * (c) CodeIgniter Foundation <admin@codeigniter.com>
9
 *
10
 * For the full copyright and license information, please view
11
 * the LICENSE file that was distributed with this source code.
12
 */
13

14
namespace CodeIgniter\Debug;
15

16
use CodeIgniter\API\ResponseTrait;
17
use CodeIgniter\Exceptions\HasExitCodeInterface;
18
use CodeIgniter\Exceptions\HTTPExceptionInterface;
19
use CodeIgniter\HTTP\CLIRequest;
20
use CodeIgniter\HTTP\IncomingRequest;
21
use CodeIgniter\HTTP\ResponseInterface;
22
use Config\Exceptions as ExceptionsConfig;
23
use ErrorException;
24
use Psr\Log\LogLevel;
25
use Throwable;
26

27
/**
28
 * Exceptions manager
29
 *
30
 * @see \CodeIgniter\Debug\ExceptionsTest
31
 */
32
class Exceptions
33
{
34
    use ResponseTrait;
35

36
    /**
37
     * The request.
38
     *
39
     * @var CLIRequest|IncomingRequest
40
     */
41
    protected $request;
42

43
    /**
44
     * The outgoing response.
45
     *
46
     * @var ResponseInterface
47
     */
48
    protected $response;
49

50
    private ?Throwable $exceptionCaughtByExceptionHandler = null;
51

52
    public function __construct(
53
        protected ExceptionsConfig $config,
54
    ) {
55
    }
7✔
56

57
    /**
58
     * Responsible for registering the error, exception and shutdown
59
     * handling of our application.
60
     *
61
     * @return void
62
     */
63
    public function initialize()
64
    {
65
        set_exception_handler($this->exceptionHandler(...));
3✔
66
        set_error_handler($this->errorHandler(...));
3✔
67
        register_shutdown_function($this->shutdownHandler(...));
3✔
68
    }
69

70
    /**
71
     * The callback to be registered to `set_exception_handler()`.
72
     *
73
     * @return void
74
     */
75
    public function exceptionHandler(Throwable $exception)
76
    {
77
        $this->exceptionCaughtByExceptionHandler = $exception;
×
78

79
        [$statusCode, $exitCode] = $this->determineCodes($exception);
×
80

81
        $this->request = service('request');
×
82

NEW
83
        if ($this->config->log && ! in_array($statusCode, $this->config->ignoreCodes, true)) {
×
NEW
84
            $uri = $this->request->getPath() === '' ? '/' : $this->request->getPath();
×
85

NEW
86
            log_message('critical', "{exClass}: {message}\n{routeInfo}\nin {exFile} on line {exLine}.\n{trace}", [
×
NEW
87
                'exClass'   => $exception::class,
×
88
                'message'   => $exception->getMessage(),
×
NEW
89
                'routeInfo' => sprintf('[Method: %s, Route: %s]', $this->request->getMethod(), $uri),
×
90
                'exFile'    => clean_path($exception->getFile()), // {file} refers to THIS file
×
91
                'exLine'    => $exception->getLine(), // {line} refers to THIS line
×
92
                'trace'     => render_backtrace($exception->getTrace()),
×
93
            ]);
×
94

95
            // Get the first exception.
NEW
96
            $firstException = $exception;
×
97

NEW
98
            while (($prevException = $firstException->getPrevious()) instanceof Throwable) {
×
NEW
99
                $firstException = $prevException;
×
100

NEW
101
                log_message('critical', "[Caused by] {exClass}: {message}\nin {exFile} on line {exLine}.\n{trace}", [
×
NEW
102
                    'exClass' => $prevException::class,
×
103
                    'message' => $prevException->getMessage(),
×
104
                    'exFile'  => clean_path($prevException->getFile()), // {file} refers to THIS file
×
105
                    'exLine'  => $prevException->getLine(), // {line} refers to THIS line
×
106
                    'trace'   => render_backtrace($prevException->getTrace()),
×
107
                ]);
×
108
            }
109
        }
110

111
        $this->response = service('response');
×
112

NEW
113
        $handler = $this->config->handler($statusCode, $exception);
×
NEW
114
        $handler->handle($exception, $this->request, $this->response, $statusCode, $exitCode);
×
115
    }
116

117
    /**
118
     * The callback to be registered to `set_error_handler()`.
119
     *
120
     * @return bool
121
     *
122
     * @throws ErrorException
123
     */
124
    public function errorHandler(int $severity, string $message, ?string $file = null, ?int $line = null)
125
    {
126
        if ($this->isDeprecationError($severity)) {
95✔
127
            if ($this->isSessionSidDeprecationError($message, $file, $line)) {
2✔
128
                return true;
×
129
            }
130

131
            if ($this->isImplicitNullableDeprecationError($message, $file, $line)) {
2✔
132
                return true;
×
133
            }
134

135
            if (! $this->config->logDeprecations || (bool) env('CODEIGNITER_SCREAM_DEPRECATIONS')) {
2✔
136
                throw new ErrorException($message, 0, $severity, $file, $line);
×
137
            }
138

139
            return $this->handleDeprecationError($message, $file, $line);
2✔
140
        }
141

142
        if ((error_reporting() & $severity) !== 0) {
93✔
143
            throw new ErrorException($message, 0, $severity, $file, $line);
60✔
144
        }
145

146
        return false; // return false to propagate the error to PHP standard error handler
33✔
147
    }
148

149
    /**
150
     * Checks to see if any errors have happened during shutdown that
151
     * need to be caught and handle them.
152
     *
153
     * @return void
154
     */
155
    public function shutdownHandler()
156
    {
NEW
157
        $error = error_get_last();
×
158

NEW
159
        if ($error === null) {
×
NEW
160
            return;
×
161
        }
162

NEW
163
        ['type' => $type, 'message' => $message, 'file' => $file, 'line' => $line] = $error;
×
164

NEW
165
        if ($this->exceptionCaughtByExceptionHandler instanceof Throwable) {
×
NEW
166
            $message .= "\n【Previous Exception】\n"
×
NEW
167
                . $this->exceptionCaughtByExceptionHandler::class . "\n"
×
NEW
168
                . $this->exceptionCaughtByExceptionHandler->getMessage() . "\n"
×
NEW
169
                . $this->exceptionCaughtByExceptionHandler->getTraceAsString();
×
170
        }
171

NEW
172
        if (in_array($type, [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE], true)) {
×
NEW
173
            $this->exceptionHandler(new ErrorException($message, 0, $type, $file, $line));
×
174
        }
175
    }
176

177
    /**
178
     * Handles session.sid_length and session.sid_bits_per_character deprecations in PHP 8.4.
179
     */
180
    private function isSessionSidDeprecationError(string $message, ?string $file = null, ?int $line = null): bool
181
    {
182
        if (PHP_VERSION_ID >= 80400 && str_contains($message, 'session.sid_')) {
2✔
UNCOV
183
            log_message(
×
184
                LogLevel::WARNING,
×
185
                '[DEPRECATED] {message} in {errFile} on line {errLine}.',
×
186
                [
×
187
                    'message' => $message,
×
188
                    'errFile' => clean_path($file ?? ''),
×
189
                    'errLine' => $line ?? 0,
×
190
                ],
×
191
            );
×
192

193
            return true;
×
194
        }
195

196
        return false;
2✔
197
    }
198

199
    /**
200
     * Workaround to implicit nullable deprecation errors in PHP 8.4.
201
     *
202
     * "Implicitly marking parameter $xxx as nullable is deprecated,
203
     *  the explicit nullable type must be used instead"
204
     *
205
     * @TODO remove this before v4.6.0 release
206
     */
207
    private function isImplicitNullableDeprecationError(string $message, ?string $file = null, ?int $line = null): bool
208
    {
209
        if (
210
            PHP_VERSION_ID >= 80400
2✔
211
            && str_contains($message, 'the explicit nullable type must be used instead')
2✔
212
            // Only Kint and Faker, which cause this error, are logged.
213
            && (str_starts_with($message, 'Kint\\') || str_starts_with($message, 'Faker\\'))
2✔
214
        ) {
215
            log_message(
×
216
                LogLevel::WARNING,
×
217
                '[DEPRECATED] {message} in {errFile} on line {errLine}.',
×
218
                [
×
219
                    'message' => $message,
×
220
                    'errFile' => clean_path($file ?? ''),
×
221
                    'errLine' => $line ?? 0,
×
222
                ],
×
223
            );
×
224

225
            return true;
×
226
        }
227

228
        return false;
2✔
229
    }
230

231
    /**
232
     * Determines the HTTP status code and the exit status code for this request.
233
     */
234
    protected function determineCodes(Throwable $exception): array
235
    {
236
        $statusCode = 500;
1✔
237
        $exitStatus = EXIT_ERROR;
1✔
238

239
        if ($exception instanceof HTTPExceptionInterface) {
1✔
240
            $statusCode = $exception->getCode();
×
241
        }
242

243
        if ($exception instanceof HasExitCodeInterface) {
1✔
244
            $exitStatus = $exception->getExitCode();
1✔
245
        }
246

247
        return [$statusCode, $exitStatus];
1✔
248
    }
249

250
    private function isDeprecationError(int $error): bool
251
    {
252
        $deprecations = E_DEPRECATED | E_USER_DEPRECATED;
95✔
253

254
        return ($error & $deprecations) !== 0;
95✔
255
    }
256

257
    private function handleDeprecationError(string $message, ?string $file = null, ?int $line = null): true
258
    {
259
        // Remove the trace of the error handler.
260
        $trace = array_slice(debug_backtrace(), 2);
2✔
261

262
        log_message(
2✔
263
            $this->config->deprecationLogLevel,
2✔
264
            "[DEPRECATED] {message} in {errFile} on line {errLine}.\n{trace}",
2✔
265
            [
2✔
266
                'message' => $message,
2✔
267
                'errFile' => clean_path($file ?? ''),
2✔
268
                'errLine' => $line ?? 0,
2✔
269
                'trace'   => render_backtrace($trace),
2✔
270
            ],
2✔
271
        );
2✔
272

273
        return true;
2✔
274
    }
275
}
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

© 2026 Coveralls, Inc