Completed
Push — master ( e10040...39d9a4 )
by Lars
04:01
created

Debug::getErrors()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace voku\db;
6
7
use voku\db\exceptions\QueryException;
8
use voku\helper\UTF8;
9
10
/**
11
 * Debug: This class can handle debug and error-logging for SQL-queries for the "Simple-MySQLi"-classes.
12
 */
13
class Debug
14
{
15
    /**
16
     * @var array
17
     */
18
    private $_errors = [];
19
20
    /**
21
     * @var bool
22
     */
23
    private $exit_on_error = true;
24
25
    /**
26
     * echo the error if "checkForDev()" returns true
27
     *
28
     * @var bool
29
     */
30
    private $echo_on_error = true;
31
32
    /**
33
     * @var string
34
     */
35
    private $css_mysql_box_border = '3px solid red';
36
37
    /**
38
     * @var string
39
     */
40
    private $css_mysql_box_bg = '#FFCCCC';
41
42
    /**
43
     * @var string
44
     */
45
    private $logger_class_name;
46
47
    /**
48
     * @var DB
49
     */
50
    private $_db;
51
52
    /**
53
     * @var string
54
     *
55
     * 'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'
56
     */
57
    private $logger_level;
58
59
    /**
60
     * Debug constructor.
61
     *
62
     * @param DB $db
63
     */
64 23
    public function __construct(DB $db)
65
    {
66 23
        $this->_db = $db;
67 23
    }
68
69
    /**
70
     * Check is the current user is a developer.
71
     *
72
     * INFO:
73
     * By default we will return "true" if the remote-ip-address is localhost or
74
     * if the script is called via CLI. But you can also overwrite this method or
75
     * you can implement a global "checkForDev()"-function.
76
     *
77
     * @return bool
78
     */
79 12
    public function checkForDev(): bool
80
    {
81
        // init
82 12
        $return = false;
83
84 12
        if (\function_exists('checkForDev')) {
85
            $return = checkForDev();
86
        } else {
87
88
            // for testing with dev-address
89 12
            $noDev = isset($_GET['noDev']) ? (int) $_GET['noDev'] : 0;
90 12
            $remoteIpAddress = $_SERVER['REMOTE_ADDR'] ?? false;
91
92
            if (
93 12
                $noDev !== 1
94
                &&
95
                (
96 12
                    $remoteIpAddress === '127.0.0.1'
97
                    ||
98 12
                    $remoteIpAddress === '::1'
99
                    ||
100 12
                    \PHP_SAPI === 'cli'
101
                )
102
            ) {
103 12
                $return = true;
104
            }
105
        }
106
107 12
        return $return;
108
    }
109
110
    /**
111
     * Clear the errors in "$this->_errors".
112
     *
113
     * @return bool
114
     */
115 18
    public function clearErrors(): bool
116
    {
117 18
        $this->_errors = [];
118
119 18
        return true;
120
    }
121
122
    /**
123
     * Display SQL-Errors or throw Exceptions (for dev).
124
     *
125
     * @param string    $error                          <p>The error message.</p>
126
     * @param bool|null $force_exception_after_error    <p>
127
     *                                                  If you use default "null" here, then the behavior depends
128
     *                                                  on "$this->exit_on_error (default: true)".
129
     *                                                  </p>
130
     *
131
     * @throws QueryException
132
     */
133 68
    public function displayError($error, $force_exception_after_error = null)
134
    {
135 68
        $fileInfo = $this->getFileAndLineFromSql();
136
137 68
        $this->logger(
138
            [
139 68
                'error',
140 68
                '<strong>' . \date(
141 68
                    'd. m. Y G:i:s'
142 68
                ) . ' (' . $fileInfo['file'] . ' line: ' . $fileInfo['line'] . ') (sql-error):</strong> ' . $error . '<br>',
143
            ]
144
        );
145
146 68
        $this->_errors[] = $error;
147
148
        if (
149 68
            $this->echo_on_error
150
            &&
151 68
            $this->checkForDev() === true
152
        ) {
153 12
            $box_border = $this->css_mysql_box_border;
154 12
            $box_bg = $this->css_mysql_box_bg;
155
156 12
            if (\PHP_SAPI === 'cli') {
157 12
                echo "\n";
158 12
                echo 'file:line -> ' . $fileInfo['file'] . ':' . $fileInfo['line'] . "\n";
159 12
                echo 'error: ' . \str_replace(["\r\n", "\n", "\r"], '', $error);
160 12
                echo "\n";
161
            } else {
162
                echo '
163
        <div class="OBJ-mysql-box" style="border: ' . $box_border . '; background: ' . $box_bg . '; padding: 10px; margin: 10px;">
164
          <b style="font-size: 14px;">MYSQL Error:</b>
165
          <code style="display: block;">
166
            file:line -> ' . $fileInfo['file'] . ':' . $fileInfo['line'] . '
167
            ' . $error . '
168
          </code>
169
        </div>
170
        ';
171
            }
172
        }
173
174
        if (
175 68
            $force_exception_after_error === true
176
            ||
177
            (
178 68
                $force_exception_after_error === null
179
                &&
180 68
                $this->exit_on_error === true
181
            )
182
        ) {
183
            throw new QueryException($error);
184
        }
185 68
    }
186
187
    /**
188
     * Get errors from "$this->_errors".
189
     *
190
     * @return array
191
     */
192 15
    public function getErrors(): array
193
    {
194 15
        return $this->_errors;
195
    }
196
197
    /**
198
     * Try to get the file & line from the current sql-query.
199
     *
200
     * @return array will return array['file'] and array['line']
201
     */
202 68
    private function getFileAndLineFromSql(): array
203
    {
204
        // init
205 68
        $return = [];
206 68
        $file = '[unknown]';
207 68
        $line = '[unknown]';
208
209 68
        $referrer = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 10);
210
211 68
        foreach ($referrer as $key => $ref) {
212
            if (
213 68
                $ref['function'] === 'execSQL'
214
                ||
215 68
                $ref['function'] === 'query'
216
                ||
217 68
                $ref['function'] === 'qry'
218
                ||
219 68
                $ref['function'] === 'execute'
220
                ||
221 68
                $ref['function'] === 'insert'
222
                ||
223 68
                $ref['function'] === 'update'
224
                ||
225 68
                $ref['function'] === 'replace'
226
                ||
227 68
                $ref['function'] === 'delete'
228
            ) {
229 53
                $file = $referrer[$key]['file'];
230 68
                $line = $referrer[$key]['line'];
231
            }
232
        }
233
234 68
        $return['file'] = $file;
235 68
        $return['line'] = $line;
236
237 68
        return $return;
238
    }
239
240
    /**
241
     * @return string
242
     */
243
    public function getLoggerClassName(): string
244
    {
245
        return $this->logger_class_name;
246
    }
247
248
    /**
249
     * @return string
250
     */
251
    public function getLoggerLevel(): string
252
    {
253
        return $this->logger_level;
254
    }
255
256
    /**
257
     * @return bool
258
     */
259
    public function isEchoOnError(): bool
260
    {
261
        return $this->echo_on_error;
262
    }
263
264
    /**
265
     * @return bool
266
     */
267
    public function isExitOnError(): bool
268
    {
269
        return $this->exit_on_error;
270
    }
271
272
    /**
273
     * Log the current query via "$this->logger".
274
     *
275
     * @param string $sql     sql-query
276
     * @param int    $duration
277
     * @param int    $results field_count | insert_id | affected_rows
278
     * @param bool   $sql_error
279
     *
280
     * @return mixed <p>Will return false, if no logging was used.</p>
281
     */
282 140
    public function logQuery($sql, $duration, $results, $sql_error = false)
283
    {
284 140
        $logLevelUse = \strtolower($this->logger_level);
285
286
        if (
287 140
            $sql_error === false
288
            &&
289 140
            ($logLevelUse !== 'trace' && $logLevelUse !== 'debug')
290
        ) {
291 135
            return false;
292
        }
293
294
        // set log-level
295 41
        $logLevel = $logLevelUse;
296 41
        if ($sql_error === true) {
297 38
            $logLevel = 'error';
298
        }
299
300
        // get extra info
301 41
        $infoExtra = '';
302 41
        $tmpLink = $this->_db->getLink();
303 41
        if ($tmpLink && $tmpLink instanceof \mysqli) {
304
            /** @noinspection PhpUsageOfSilenceOperatorInspection */
305 41
            $infoExtra = @\mysqli_info($tmpLink);
306 41
            if ($infoExtra) {
307 12
                $infoExtra = ' | info => ' . $infoExtra;
308
            }
309
        }
310
311
        //
312
        // logging
313
        //
314
315 41
        $info = 'time => ' . \round($duration, 5) . ' | results => ' . (int) $results . $infoExtra . ' | SQL => ' . UTF8::htmlentities($sql);
316
317 41
        $fileInfo = $this->getFileAndLineFromSql();
318
319 41
        return $this->logger(
320
            [
321 41
                $logLevel,
322 41
                '<strong>' . \date('d. m. Y G:i:s') . ' (' . $fileInfo['file'] . ' line: ' . $fileInfo['line'] . '):</strong> ' . $info . '<br>',
323 41
                'sql',
324
            ]
325
        );
326
    }
327
328
    /**
329
     * Wrapper-Function for a "Logger"-Class.
330
     *
331
     * INFO:
332
     * The "Logger"-ClassName is set by "$this->logger_class_name",<br />
333
     * the "Logger"-Method is the [0] element from the "$log"-parameter,<br />
334
     * the text you want to log is the [1] element and<br />
335
     * the type you want to log is the next [2] element.
336
     *
337
     * @param string[] $log [method, text, type]<br />e.g.: array('error', 'this is a error', 'sql')
338
     *
339
     * @return mixed <p>Will return false, if no logging was used.</p>
340
     */
341 71
    public function logger(array $log)
342
    {
343
        // init
344 71
        $logMethod = '';
345 71
        $logText = '';
346 71
        $logType = 'sql';
347 71
        $logClass = $this->logger_class_name;
348
349 71
        if (isset($log[0])) {
350 71
            $logMethod = $log[0];
351
        }
352 71
        if (isset($log[1])) {
353 71
            $logText = $log[1];
354
        }
355 71
        if (isset($log[2])) {
356 41
            $logType = $log[2];
357
        }
358
359
        if (
360 71
            $logClass
361
            &&
362 71
            $logMethod
363
            &&
364 71
            \class_exists($logClass)
365
            &&
366 71
            \method_exists($logClass, $logMethod)
367
        ) {
368
            if (\method_exists($logClass, 'getInstance')) {
369
                return $logClass::getInstance()->$logMethod($logText, ['log_type' => $logType]);
370
            }
371
372
            return $logClass::$logMethod($logText, $logType);
373
        }
374
375 71
        return false;
376
    }
377
378
    /**
379
     * Send a error mail to the admin / dev.
380
     *
381
     * @param string $subject
382
     * @param string $htmlBody
383
     * @param int    $priority
384
     */
385 41
    public function mailToAdmin($subject, $htmlBody, $priority = 3)
386
    {
387 41
        if (\function_exists('mailToAdmin')) {
388
            mailToAdmin($subject, $htmlBody, $priority);
389
        } else {
390 41
            if ($priority === 3) {
391 41
                $this->logger(
392
                    [
393 41
                        'debug',
394 41
                        $subject . ' | ' . $htmlBody,
395
                    ]
396
                );
397
            } elseif ($priority > 3) {
398
                $this->logger(
399
                    [
400
                        'error',
401
                        $subject . ' | ' . $htmlBody,
402
                    ]
403
                );
404
            } elseif ($priority < 3) {
405
                $this->logger(
406
                    [
407
                        'info',
408
                        $subject . ' | ' . $htmlBody,
409
                    ]
410
                );
411
            }
412
        }
413 41
    }
414
415
    /**
416
     * @param bool $echo_on_error
417
     */
418 23
    public function setEchoOnError($echo_on_error)
419
    {
420 23
        $this->echo_on_error = (bool) $echo_on_error;
421 23
    }
422
423
    /**
424
     * @param bool $exit_on_error
425
     */
426 23
    public function setExitOnError($exit_on_error)
427
    {
428 23
        $this->exit_on_error = (bool) $exit_on_error;
429 23
    }
430
431
    /**
432
     * @param string $logger_class_name
433
     */
434 23
    public function setLoggerClassName($logger_class_name)
435
    {
436 23
        $this->logger_class_name = (string) $logger_class_name;
437 23
    }
438
439
    /**
440
     * @param string $logger_level
441
     */
442 23
    public function setLoggerLevel($logger_level)
443
    {
444 23
        $this->logger_level = (string) $logger_level;
445 23
    }
446
}
447