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

codeigniter4 / CodeIgniter4 / 8668320707

12 Apr 2024 09:45PM UTC coverage: 86.605% (-0.007%) from 86.612%
8668320707

push

github

web-flow
Merge pull request #8770 from kenjis/fix-type-FabricatorModel

refactor: FabricatorModel

19959 of 23046 relevant lines covered (86.61%)

190.21 hits per line

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

86.81
/system/Debug/BaseExceptionHandler.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\HTTP\RequestInterface;
17
use CodeIgniter\HTTP\ResponseInterface;
18
use Config\Exceptions as ExceptionsConfig;
19
use Throwable;
20

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

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

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

43
    public function __construct(ExceptionsConfig $config)
44
    {
45
        $this->config = $config;
11✔
46

47
        $this->obLevel = ob_get_level();
11✔
48

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

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

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

75
        while ($prevException = $firstException->getPrevious()) {
4✔
76
            $firstException = $prevException;
×
77
        }
78

79
        $trace = $firstException->getTrace();
4✔
80

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

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

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

105
        return $trace;
2✔
106
    }
107

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

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

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

141
        return $args;
2✔
142
    }
143

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

154
        return number_to_size($bytes, 2);
×
155
    }
156

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

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

179
        try {
180
            $source = file_get_contents($file);
1✔
181
        } catch (Throwable) {
×
182
            return false;
×
183
        }
184

185
        $source = str_replace(["\r\n", "\r"], "\n", $source);
1✔
186
        $source = explode("\n", highlight_string($source, true));
1✔
187

188
        if (PHP_VERSION_ID < 80300) {
1✔
189
            $source = str_replace('<br />', "\n", $source[1]);
1✔
190
            $source = explode("\n", str_replace("\r\n", "\n", $source));
1✔
191
        } else {
192
            // We have to remove these tags since we're preparing the result
193
            // ourselves and these tags are added manually at the end.
194
            $source = str_replace(['<pre><code>', '</code></pre>'], '', $source);
×
195
        }
196

197
        // Get just the part to show
198
        $start = max($lineNumber - (int) round($lines / 2), 0);
1✔
199

200
        // Get just the lines we need to display, while keeping line numbers...
201
        $source = array_splice($source, $start, $lines, true);
1✔
202

203
        // Used to format the line number in the source
204
        $format = '% ' . strlen((string) ($start + $lines)) . 'd';
1✔
205

206
        $out = '';
1✔
207
        // Because the highlighting may have an uneven number
208
        // of open and close span tags on one line, we need
209
        // to ensure we can close them all to get the lines
210
        // showing correctly.
211
        $spans = 0;
1✔
212

213
        foreach ($source as $n => $row) {
1✔
214
            $spans += substr_count($row, '<span') - substr_count($row, '</span');
1✔
215
            $row = str_replace(["\r", "\n"], ['', ''], $row);
1✔
216

217
            if (($n + $start + 1) === $lineNumber) {
1✔
218
                preg_match_all('#<[^>]+>#', $row, $tags);
1✔
219

220
                $out .= sprintf(
1✔
221
                    "<span class='line highlight'><span class='number'>{$format}</span> %s\n</span>%s",
1✔
222
                    $n + $start + 1,
1✔
223
                    strip_tags($row),
1✔
224
                    implode('', $tags[0])
1✔
225
                );
1✔
226
            } else {
227
                $out .= sprintf('<span class="line"><span class="number">' . $format . '</span> %s', $n + $start + 1, $row) . "\n";
1✔
228
                // We're closing only one span tag we added manually line before,
229
                // so we have to increment $spans count to close this tag later.
230
                $spans++;
1✔
231
            }
232
        }
233

234
        if ($spans > 0) {
1✔
235
            $out .= str_repeat('</span>', $spans);
1✔
236
        }
237

238
        return '<pre><code>' . $out . '</code></pre>';
1✔
239
    }
240

241
    /**
242
     * Given an exception and status code will display the error to the client.
243
     *
244
     * @param string|null $viewFile
245
     */
246
    protected function render(Throwable $exception, int $statusCode, $viewFile = null): void
247
    {
248
        if ($viewFile === null) {
2✔
249
            echo 'The error view file was not specified. Cannot display error view.';
×
250

251
            exit(1);
×
252
        }
253

254
        if (! is_file($viewFile)) {
2✔
255
            echo 'The error view file "' . $viewFile . '" was not found. Cannot display error view.';
×
256

257
            exit(1);
×
258
        }
259

260
        echo (function () use ($exception, $statusCode, $viewFile): string {
2✔
261
            $vars = $this->collectVars($exception, $statusCode);
2✔
262
            extract($vars, EXTR_SKIP);
2✔
263

264
            // CLI error views output to STDERR/STDOUT, so ob_start() does not work.
265
            ob_start();
2✔
266
            include $viewFile;
2✔
267

268
            return ob_get_clean();
2✔
269
        })();
2✔
270
    }
271
}
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