Completed
Push — master ( 58fd6d...05af0e )
by Lars
01:50
created

Debug::getFileAndLineFromSql()   B

Complexity

Conditions 10
Paths 3

Size

Total Lines 39

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 10

Importance

Changes 0
Metric Value
dl 0
loc 39
ccs 19
cts 19
cp 1
rs 7.6666
c 0
b 0
f 0
cc 10
nc 3
nop 0
crap 10

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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