Completed
Push — master ( dbe3f7...286249 )
by Lars
03:20
created

Debug::setLoggerClassName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
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
 * @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 11
  public function __construct(DB $db)
67
  {
68 11
    $this->_db = $db;
69 11
  }
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 24
  public function checkForDev(): bool
82
  {
83
    // init
84 24
    $return = false;
85
86 24
    if (\function_exists('checkForDev')) {
87
      $return = checkForDev();
88
    } else {
89
90
      // for testing with dev-address
91 24
      $noDev = isset($_GET['noDev']) ? (int)$_GET['noDev'] : 0;
92 24
      $remoteIpAddress = $_SERVER['REMOTE_ADDR'] ?? false;
93
94
      if (
95 24
          $noDev != 1
96
          &&
97
          (
98 24
              $remoteIpAddress === '127.0.0.1'
99
              ||
100 24
              $remoteIpAddress === '::1'
101
              ||
102 24
              PHP_SAPI === 'cli'
103
          )
104
      ) {
105 24
        $return = true;
106
      }
107
    }
108
109 24
    return $return;
110
  }
111
112
  /**
113
   * Clear the errors in "$this->_errors".
114
   *
115
   * @return bool
116
   */
117 6
  public function clearErrors(): bool
118
  {
119 6
    $this->_errors = [];
120
121 6
    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|boolean $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 24
  public function displayError($error, $force_exception_after_error = null)
136
  {
137 24
    $fileInfo = $this->getFileAndLineFromSql();
138
139 24
    $this->logger(
140
        [
141 24
            'error',
142 24
            '<strong>' . \date(
143 24
                'd. m. Y G:i:s'
144 24
            ) . ' (' . $fileInfo['file'] . ' line: ' . $fileInfo['line'] . ') (sql-error):</strong> ' . $error . '<br>',
145
        ]
146
    );
147
148 24
    $this->_errors[] = $error;
149
150 24
    if ($this->checkForDev() === true) {
151 24
      if ($this->echo_on_error) {
152 6
        $box_border = $this->css_mysql_box_border;
153 6
        $box_bg = $this->css_mysql_box_bg;
154
155
        echo '
156 6
        <div class="OBJ-mysql-box" style="border:' . $box_border . '; background:' . $box_bg . '; padding:10px; margin:10px;">
157
          <b style="font-size:14px;">MYSQL Error:</b>
158
          <code style="display:block;">
159 6
            file / line: ' . $fileInfo['file'] . ' / ' . $fileInfo['line'] . '
160 6
            ' . $error . '
161
          </code>
162
        </div>
163
        ';
164
      }
165
    }
166
167
    if (
168 24
        $force_exception_after_error === true
169
        ||
170
        (
171 21
            $force_exception_after_error === null
172
            &&
173 24
            $this->exit_on_error === true
174
        )
175
    ) {
176 3
      throw new QueryException($error);
177
    }
178 21
  }
179
180
  /**
181
   * Get errors from "$this->_errors".
182
   *
183
   * @return array
184
   */
185 5
  public function getErrors(): array
186
  {
187 5
    return $this->_errors;
188
  }
189
190
  /**
191
   * Try to get the file & line from the current sql-query.
192
   *
193
   * @return array will return array['file'] and array['line']
194
   */
195 24
  private function getFileAndLineFromSql(): array
196
  {
197
    // init
198 24
    $return = [];
199 24
    $file = '';
200 24
    $line = '';
201
202 24
    $referrer = \debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10);
203
204 24
    foreach ($referrer as $key => $ref) {
205
206
      if (
207 24
          $ref['function'] === 'execSQL'
208
          ||
209 24
          $ref['function'] === 'query'
210
          ||
211 24
          $ref['function'] === 'qry'
212
          ||
213 24
          $ref['function'] === 'execute'
214
          ||
215 24
          $ref['function'] === 'insert'
216
          ||
217 24
          $ref['function'] === 'update'
218
          ||
219 24
          $ref['function'] === 'replace'
220
          ||
221 24
          $ref['function'] === 'delete'
222
      ) {
223 19
        $file = $referrer[$key]['file'];
224 24
        $line = $referrer[$key]['line'];
225
      }
226
227
    }
228
229 24
    $return['file'] = $file;
230 24
    $return['line'] = $line;
231
232 24
    return $return;
233
  }
234
235
  /**
236
   * @return string
237
   */
238
  public function getLoggerClassName(): string
239
  {
240
    return $this->logger_class_name;
241
  }
242
243
  /**
244
   * @return string
245
   */
246
  public function getLoggerLevel(): string
247
  {
248
    return $this->logger_level;
249
  }
250
251
  /**
252
   * @return boolean
253
   */
254
  public function isEchoOnError(): bool
255
  {
256
    return $this->echo_on_error;
257
  }
258
259
  /**
260
   * @return boolean
261
   */
262
  public function isExitOnError(): bool
263
  {
264
    return $this->exit_on_error;
265
  }
266
267
  /**
268
   * Log the current query via "$this->logger".
269
   *
270
   * @param string $sql     sql-query
271
   * @param int    $duration
272
   * @param int    $results field_count | insert_id | affected_rows
273
   * @param bool   $sql_error
274
   *
275
   * @return bool
276
   */
277 98
  public function logQuery($sql, $duration, $results, $sql_error = false): bool
278
  {
279 98
    $logLevelUse = \strtolower($this->logger_level);
280
281
    if (
282 98
        $sql_error === false
283
        &&
284 98
        ($logLevelUse !== 'trace' && $logLevelUse !== 'debug')
285
    ) {
286 95
      return false;
287
    }
288
289
    // set log-level
290 15
    $logLevel = $logLevelUse;
291 15
    if ($sql_error === true) {
292 14
      $logLevel = 'error';
293
    }
294
295
    // get extra info
296 15
    $infoExtra = \mysqli_info($this->_db->getLink());
297 15
    if ($infoExtra) {
298 4
      $infoExtra = ' | info => ' . $infoExtra;
299
    }
300
301
    //
302
    // logging
303
    //
304
305 15
    $info = 'time => ' . \round($duration, 5) . ' | results => ' . (int)$results . $infoExtra . ' | SQL => ' . UTF8::htmlentities($sql);
306
307 15
    $fileInfo = $this->getFileAndLineFromSql();
308
309 15
    return $this->logger(
310
        [
311 15
            $logLevel,
312 15
            '<strong>' . \date('d. m. Y G:i:s') . ' (' . $fileInfo['file'] . ' line: ' . $fileInfo['line'] . '):</strong> ' . $info . '<br>',
313 15
            'sql',
314
        ]
315
    );
316
  }
317
318
  /**
319
   * Wrapper-Function for a "Logger"-Class.
320
   *
321
   * INFO:
322
   * The "Logger"-ClassName is set by "$this->logger_class_name",<br />
323
   * the "Logger"-Method is the [0] element from the "$log"-parameter,<br />
324
   * the text you want to log is the [1] element and<br />
325
   * the type you want to log is the next [2] element.
326
   *
327
   * @param string[] $log [method, text, type]<br />e.g.: array('error', 'this is a error', 'sql')
328
   *
329
   * @return bool
330
   */
331 25
  public function logger(array $log): bool
332
  {
333 25
    $logMethod = '';
334 25
    $logText = '';
335 25
    $logType = '';
336 25
    $logClass = $this->logger_class_name;
337
338 25
    if (isset($log[0])) {
339 25
      $logMethod = $log[0];
340
    }
341 25
    if (isset($log[1])) {
342 25
      $logText = $log[1];
343
    }
344 25
    if (isset($log[2])) {
345 15
      $logType = $log[2];
346
    }
347
348
    if (
349 25
        $logClass
350
        &&
351 25
        \class_exists($logClass)
352
        &&
353 25
        \method_exists($logClass, $logMethod)
354
    ) {
355
      return $logClass::$logMethod($logText, $logType);
356
    }
357
358 25
    return false;
359
  }
360
361
  /**
362
   * Send a error mail to the admin / dev.
363
   *
364
   * @param string $subject
365
   * @param string $htmlBody
366
   * @param int    $priority
367
   */
368 15
  public function mailToAdmin($subject, $htmlBody, $priority = 3)
369
  {
370 15
    if (\function_exists('mailToAdmin')) {
371
      mailToAdmin($subject, $htmlBody, $priority);
372
    } else {
373
374 15
      if ($priority === 3) {
375 15
        $this->logger(
376
            [
377 15
                'debug',
378 15
                $subject . ' | ' . $htmlBody,
379
            ]
380
        );
381
      } elseif ($priority > 3) {
382
        $this->logger(
383
            [
384
                'error',
385
                $subject . ' | ' . $htmlBody,
386
            ]
387
        );
388
      } elseif ($priority < 3) {
389
        $this->logger(
390
            [
391
                'info',
392
                $subject . ' | ' . $htmlBody,
393
            ]
394
        );
395
      }
396
397
    }
398 15
  }
399
400
  /**
401
   * @param boolean $echo_on_error
402
   */
403 11
  public function setEchoOnError($echo_on_error)
404
  {
405 11
    $this->echo_on_error = (boolean)$echo_on_error;
406 11
  }
407
408
  /**
409
   * @param boolean $exit_on_error
410
   */
411 11
  public function setExitOnError($exit_on_error)
412
  {
413 11
    $this->exit_on_error = (boolean)$exit_on_error;
414 11
  }
415
416
  /**
417
   * @param string $logger_class_name
418
   */
419 11
  public function setLoggerClassName($logger_class_name)
420
  {
421 11
    $this->logger_class_name = (string)$logger_class_name;
422 11
  }
423
424
  /**
425
   * @param string $logger_level
426
   */
427 11
  public function setLoggerLevel($logger_level)
428
  {
429 11
    $this->logger_level = (string)$logger_level;
430 11
  }
431
432
}
433