Completed
Push — master ( 53937e...5698ad )
by Lars
06:10
created

Debug::isExitOnError()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 1
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 2
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 = array();
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 11
   */
66
  public function __construct(DB $db)
67 11
  {
68 11
    $this->_db = $db;
69
  }
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 24
   */
81
  public function checkForDev(): bool
82
  {
83 24
    // init
84
    $return = false;
85 24
86
    if (\function_exists('checkForDev')) {
87
      $return = checkForDev();
88
    } else {
89
90 24
      // for testing with dev-address
91 24
      $noDev = isset($_GET['noDev']) ? (int)$_GET['noDev'] : 0;
92
      $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 24
              ||
102 24
              PHP_SAPI === 'cli'
103 24
          )
104 24
      ) {
105 24
        $return = true;
106
      }
107
    }
108 24
109
    return $return;
110
  }
111
112
  /**
113
   * Clear the errors in "$this->_errors".
114
   *
115
   * @return bool
116 6
   */
117
  public function clearErrors(): bool
118 6
  {
119
    $this->_errors = array();
120 6
121
    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 24
   */
135
  public function displayError($error, $force_exception_after_error = null)
136 24
  {
137
    $fileInfo = $this->getFileAndLineFromSql();
138 24
139
    $this->logger(
140 24
        array(
141 24
            'error',
142
            '<strong>' . date(
143 24
                'd. m. Y G:i:s'
144
            ) . ' (' . $fileInfo['file'] . ' line: ' . $fileInfo['line'] . ') (sql-error):</strong> ' . $error . '<br>',
145 24
        )
146
    );
147 24
148
    $this->_errors[] = $error;
149 24
150 24
    if ($this->checkForDev() === true) {
151 6
      if ($this->echo_on_error) {
152 6
        $box_border = $this->css_mysql_box_border;
153
        $box_bg = $this->css_mysql_box_bg;
154
155 6
        echo '
156
        <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 6
          <code style="display:block;">
159 6
            file / line: ' . $fileInfo['file'] . ' / ' . $fileInfo['line'] . '
160
            ' . $error . '
161
          </code>
162 6
        </div>
163 6
        ';
164 24
      }
165
    }
166
167
    if (
168 24
        $force_exception_after_error === true
169
        ||
170
        (
171 21
            $force_exception_after_error === null
172 10
            &&
173 10
            $this->exit_on_error === true
174 24
        )
175 3
    ) {
176
      throw new QueryException($error);
177 21
    }
178
  }
179
180
  /**
181
   * Get errors from "$this->_errors".
182
   *
183
   * @return array
184 5
   */
185
  public function getErrors(): array
186 5
  {
187
    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 24
   */
195
  private function getFileAndLineFromSql(): array
196
  {
197 24
    // init
198 24
    $return = array();
199 24
    $file = '';
200
    $line = '';
201 24
202 24
    $referrer = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10);
203 24
204
    foreach ($referrer as $key => $ref) {
205
206
      if (
207 24
          $ref['function'] === 'execSQL'
208
          ||
209
          $ref['function'] === 'query'
210 24
          ||
211 24
          $ref['function'] === 'qry'
212 24
          ||
213 24
          $ref['function'] === 'execute'
214 24
          ||
215 24
          $ref['function'] === 'insert'
216 24
          ||
217 24
          $ref['function'] === 'update'
218 24
          ||
219 24
          $ref['function'] === 'replace'
220 24
          ||
221 24
          $ref['function'] === 'delete'
222 24
      ) {
223 24
        $file = $referrer[$key]['file'];
224 24
        $line = $referrer[$key]['line'];
225 24
      }
226 19
227 19
    }
228 19
229
    $return['file'] = $file;
230 24
    $return['line'] = $line;
231
232 24
    return $return;
233 24
  }
234
235 24
  /**
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
  public function logQuery($sql, $duration, $results, $sql_error = false): bool
278
  {
279
    $logLevelUse = strtolower($this->logger_level);
280 98
281
    if (
282 98
        $sql_error === false
283
        &&
284
        ($logLevelUse !== 'trace' && $logLevelUse !== 'debug')
285
    ) {
286 98
      return false;
287 95
    }
288 98
289 95
    // set log-level
290
    $logLevel = $logLevelUse;
291
    if ($sql_error === true) {
292
      $logLevel = 'error';
293 15
    }
294 15
295 14
    // get extra info
296 14
    $infoExtra = \mysqli_info($this->_db->getLink());
297
    if ($infoExtra) {
298
      $infoExtra = ' | info => ' . $infoExtra;
299 15
    }
300 15
301 4
    //
302 4
    // logging
303
    //
304
305
    $info = 'time => ' . round($duration, 5) . ' | results => ' . (int)$results . $infoExtra . ' | SQL => ' . UTF8::htmlentities($sql);
306
307
    $fileInfo = $this->getFileAndLineFromSql();
308 15
309
    return $this->logger(
310 15
        array(
311
            $logLevel,
312 15
            '<strong>' . date('d. m. Y G:i:s') . ' (' . $fileInfo['file'] . ' line: ' . $fileInfo['line'] . '):</strong> ' . $info . '<br>',
313
            'sql',
314 15
        )
315 15
    );
316 15
  }
317
318 15
  /**
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
  public function logger(array $log): bool
332
  {
333
    $logMethod = '';
334 25
    $logText = '';
335
    $logType = '';
336 25
    $logClass = $this->logger_class_name;
337 25
338 25
    if (isset($log[0])) {
339 25
      $logMethod = $log[0];
340
    }
341 25
    if (isset($log[1])) {
342 25
      $logText = $log[1];
343 25
    }
344 25
    if (isset($log[2])) {
345 25
      $logType = $log[2];
346 25
    }
347 25
348 15
    if (
349 15
        $logClass
350
        &&
351
        class_exists($logClass)
352
        &&
353 25
        method_exists($logClass, $logMethod)
354
    ) {
355 25
      return $logClass::$logMethod($logText, $logType);
356
    }
357 25
358
    return false;
359
  }
360
361 25
  /**
362
   * Send a error mail to the admin / dev.
363
   *
364
   * @param string $subject
365
   * @param string $htmlBody
366
   * @param int    $priority
367
   */
368
  public function mailToAdmin($subject, $htmlBody, $priority = 3)
369
  {
370
    if (\function_exists('mailToAdmin')) {
371 15
      mailToAdmin($subject, $htmlBody, $priority);
372
    } else {
373 15
374
      if ($priority === 3) {
375
        $this->logger(
376
            array(
377 15
                'debug',
378 15
                $subject . ' | ' . $htmlBody,
379
            )
380 15
        );
381 15
      } elseif ($priority > 3) {
382
        $this->logger(
383 15
            array(
384 15
                'error',
385
                $subject . ' | ' . $htmlBody,
386
            )
387
        );
388
      } elseif ($priority < 3) {
389
        $this->logger(
390
            array(
391
                'info',
392
                $subject . ' | ' . $htmlBody,
393
            )
394
        );
395
      }
396
397
    }
398
  }
399
400
  /**
401 15
   * @param boolean $echo_on_error
402
   */
403
  public function setEchoOnError($echo_on_error)
404
  {
405
    $this->echo_on_error = (boolean)$echo_on_error;
406 11
  }
407
408 11
  /**
409 11
   * @param boolean $exit_on_error
410
   */
411
  public function setExitOnError($exit_on_error)
412
  {
413
    $this->exit_on_error = (boolean)$exit_on_error;
414 11
  }
415
416 11
  /**
417 11
   * @param string $logger_class_name
418
   */
419
  public function setLoggerClassName($logger_class_name)
420
  {
421
    $this->logger_class_name = (string)$logger_class_name;
422 11
  }
423
424 11
  /**
425 11
   * @param string $logger_level
426
   */
427
  public function setLoggerLevel($logger_level)
428
  {
429
    $this->logger_level = (string)$logger_level;
430 11
  }
431
432
}
433