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

codeigniter4 / CodeIgniter4 / 6997881217

26 Nov 2023 08:37PM UTC coverage: 85.221% (-0.04%) from 85.263%
6997881217

push

github

web-flow
Merge pull request #8239 from kenjis/fix-ExceptionHandler-exception-display

fix: ExceptionHandler displays incorrect Exception classname

3 of 18 new or added lines in 2 files covered. (16.67%)

1 existing line in 1 file now uncovered.

18585 of 21808 relevant lines covered (85.22%)

199.39 hits per line

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

51.76
/system/Debug/BaseExceptionHandler.php
1
<?php
2

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

12
namespace CodeIgniter\Debug;
13

14
use CodeIgniter\HTTP\RequestInterface;
15
use CodeIgniter\HTTP\ResponseInterface;
16
use Config\Exceptions as ExceptionsConfig;
17
use Throwable;
18

19
/**
20
 * Provides common functions for exception handlers,
21
 * especially around displaying the output.
22
 */
23
abstract class BaseExceptionHandler
24
{
25
    /**
26
     * Config for debug exceptions.
27
     */
28
    protected ExceptionsConfig $config;
29

30
    /**
31
     * Nesting level of the output buffering mechanism
32
     */
33
    protected int $obLevel;
34

35
    /**
36
     * The path to the directory containing the
37
     * cli and html error view directories.
38
     */
39
    protected ?string $viewPath = null;
40

41
    public function __construct(ExceptionsConfig $config)
42
    {
43
        $this->config = $config;
10✔
44

45
        $this->obLevel = ob_get_level();
10✔
46

47
        if ($this->viewPath === null) {
10✔
48
            $this->viewPath = rtrim($this->config->errorViewPath, '\\/ ') . DIRECTORY_SEPARATOR;
10✔
49
        }
50
    }
51

52
    /**
53
     * The main entry point into the handler.
54
     *
55
     * @return void
56
     */
57
    abstract public function handle(
58
        Throwable $exception,
59
        RequestInterface $request,
60
        ResponseInterface $response,
61
        int $statusCode,
62
        int $exitCode
63
    );
64

65
    /**
66
     * Gathers the variables that will be made available to the view.
67
     */
68
    protected function collectVars(Throwable $exception, int $statusCode): array
69
    {
70
        // Get the first exception.
71
        $firstException = $exception;
4✔
72

73
        while ($prevException = $firstException->getPrevious()) {
4✔
NEW
74
            $firstException = $prevException;
×
75
        }
76

77
        $trace = $firstException->getTrace();
4✔
78

79
        if ($this->config->sensitiveDataInTrace !== []) {
4✔
80
            $trace = $this->maskSensitiveData($trace, $this->config->sensitiveDataInTrace);
×
81
        }
82

83
        return [
4✔
84
            'title'   => get_class($exception),
4✔
85
            'type'    => get_class($exception),
4✔
86
            'code'    => $statusCode,
4✔
87
            'message' => $exception->getMessage(),
4✔
88
            'file'    => $exception->getFile(),
4✔
89
            'line'    => $exception->getLine(),
4✔
90
            'trace'   => $trace,
4✔
91
        ];
4✔
92
    }
93

94
    /**
95
     * Mask sensitive data in the trace.
96
     */
97
    protected function maskSensitiveData(array $trace, array $keysToMask, string $path = ''): array
98
    {
99
        foreach ($trace as $i => $line) {
2✔
100
            $trace[$i]['args'] = $this->maskData($line['args'], $keysToMask);
2✔
101
        }
102

103
        return $trace;
2✔
104
    }
105

106
    /**
107
     * @param array|object $args
108
     *
109
     * @return array|object
110
     */
111
    private function maskData($args, array $keysToMask, string $path = '')
112
    {
113
        foreach ($keysToMask as $keyToMask) {
2✔
114
            $explode = explode('/', $keyToMask);
2✔
115
            $index   = end($explode);
2✔
116

117
            if (strpos(strrev($path . '/' . $index), strrev($keyToMask)) === 0) {
2✔
118
                if (is_array($args) && array_key_exists($index, $args)) {
2✔
119
                    $args[$index] = '******************';
1✔
120
                } elseif (
121
                    is_object($args) && property_exists($args, $index)
2✔
122
                    && isset($args->{$index}) && is_scalar($args->{$index})
2✔
123
                ) {
124
                    $args->{$index} = '******************';
1✔
125
                }
126
            }
127
        }
128

129
        if (is_array($args)) {
2✔
130
            foreach ($args as $pathKey => $subarray) {
2✔
131
                $args[$pathKey] = $this->maskData($subarray, $keysToMask, $path . '/' . $pathKey);
1✔
132
            }
133
        } elseif (is_object($args)) {
1✔
134
            foreach ($args as $pathKey => $subarray) {
1✔
135
                $args->{$pathKey} = $this->maskData($subarray, $keysToMask, $path . '/' . $pathKey);
1✔
136
            }
137
        }
138

139
        return $args;
2✔
140
    }
141

142
    /**
143
     * Describes memory usage in real-world units. Intended for use
144
     * with memory_get_usage, etc.
145
     *
146
     * @used-by app/Views/errors/html/error_exception.php
147
     */
148
    protected static function describeMemory(int $bytes): string
149
    {
150
        helper('number');
×
151

152
        return number_to_size($bytes, 2);
×
153
    }
154

155
    /**
156
     * Creates a syntax-highlighted version of a PHP file.
157
     *
158
     * @used-by app/Views/errors/html/error_exception.php
159
     *
160
     * @return bool|string
161
     */
162
    protected static function highlightFile(string $file, int $lineNumber, int $lines = 15)
163
    {
164
        if (empty($file) || ! is_readable($file)) {
×
165
            return false;
×
166
        }
167

168
        // Set our highlight colors:
169
        if (function_exists('ini_set')) {
×
170
            ini_set('highlight.comment', '#767a7e; font-style: italic');
×
171
            ini_set('highlight.default', '#c7c7c7');
×
172
            ini_set('highlight.html', '#06B');
×
173
            ini_set('highlight.keyword', '#f1ce61;');
×
174
            ini_set('highlight.string', '#869d6a');
×
175
        }
176

177
        try {
178
            $source = file_get_contents($file);
×
179
        } catch (Throwable $e) {
×
180
            return false;
×
181
        }
182

183
        $source = str_replace(["\r\n", "\r"], "\n", $source);
×
184
        $source = explode("\n", highlight_string($source, true));
×
185
        $source = str_replace('<br />', "\n", $source[1]);
×
186
        $source = explode("\n", str_replace("\r\n", "\n", $source));
×
187

188
        // Get just the part to show
189
        $start = max($lineNumber - (int) round($lines / 2), 0);
×
190

191
        // Get just the lines we need to display, while keeping line numbers...
192
        $source = array_splice($source, $start, $lines, true);
×
193

194
        // Used to format the line number in the source
195
        $format = '% ' . strlen((string) ($start + $lines)) . 'd';
×
196

197
        $out = '';
×
198
        // Because the highlighting may have an uneven number
199
        // of open and close span tags on one line, we need
200
        // to ensure we can close them all to get the lines
201
        // showing correctly.
202
        $spans = 1;
×
203

204
        foreach ($source as $n => $row) {
×
205
            $spans += substr_count($row, '<span') - substr_count($row, '</span');
×
206
            $row = str_replace(["\r", "\n"], ['', ''], $row);
×
207

208
            if (($n + $start + 1) === $lineNumber) {
×
209
                preg_match_all('#<[^>]+>#', $row, $tags);
×
210

211
                $out .= sprintf(
×
212
                    "<span class='line highlight'><span class='number'>{$format}</span> %s\n</span>%s",
×
213
                    $n + $start + 1,
×
214
                    strip_tags($row),
×
215
                    implode('', $tags[0])
×
216
                );
×
217
            } else {
218
                $out .= sprintf('<span class="line"><span class="number">' . $format . '</span> %s', $n + $start + 1, $row) . "\n";
×
219
            }
220
        }
221

222
        if ($spans > 0) {
×
223
            $out .= str_repeat('</span>', $spans);
×
224
        }
225

226
        return '<pre><code>' . $out . '</code></pre>';
×
227
    }
228

229
    /**
230
     * Given an exception and status code will display the error to the client.
231
     *
232
     * @param string|null $viewFile
233
     */
234
    protected function render(Throwable $exception, int $statusCode, $viewFile = null): void
235
    {
236
        if (empty($viewFile) || ! is_file($viewFile)) {
2✔
237
            echo 'The error view files were not found. Cannot render exception trace.';
×
238

239
            exit(1);
×
240
        }
241

242
        echo(function () use ($exception, $statusCode, $viewFile): string {
2✔
243
            $vars = $this->collectVars($exception, $statusCode);
2✔
244
            extract($vars, EXTR_SKIP);
2✔
245

246
            // CLI error views output to STDERR/STDOUT, so ob_start() does not work.
247
            ob_start();
2✔
248
            include $viewFile;
2✔
249

250
            return ob_get_clean();
2✔
251
        })();
2✔
252
    }
253
}
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