Completed
Push — master ( 4930f4...cb8f3a )
by Lars
9s
created

Debug::setExitOnError()   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 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
3
namespace voku\db;
4
5
use voku\helper\Bootup;
6
use voku\helper\UTF8;
7
8
/**
9
 * Debug: this handles debug and error-logging.
10
 *
11
 * @package   voku\db
12
 */
13
class Debug
14
{
15
  /**
16
   * @var array
17
   */
18
  private $_errors = array();
19
20
  /**
21
   * @var bool
22
   */
23
  private $exit_on_error = true;
24
25
  /**
26
   * @var bool
27
   */
28
  private $echo_on_error = true;
29
30
  /**
31
   * @var string
32
   */
33
  private $css_mysql_box_border = '3px solid red';
34
35
  /**
36
   * @var string
37
   */
38
  private $css_mysql_box_bg = '#FFCCCC';
39
40
41
  /**
42
   * @var string
43
   */
44
  private $logger_class_name;
45
46
  /**
47
   * @var DB
48
   */
49
  private $_db;
50
51
  /**
52
   * @var string
53
   *
54
   * 'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'
55
   */
56
  private $logger_level;
57
58
  /**
59
   * Debug constructor.
60
   *
61
   * @param DB $db
62
   */
63 12
  public function __construct(DB $db)
64
  {
65 12
    $this->_db = $db;
66 12
  }
67
68
  /**
69
   * Check is the current user is a developer.
70
   *
71
   * INFO:
72
   * By default we will return "true" if the remote-ip-address is localhost or
73
   * if the script is called via CLI. But you can also overwrite this method or
74
   * you can implement a global "checkForDev()"-function.
75
   *
76
   * @return bool
77
   */
78 21
  public function checkForDev()
0 ignored issues
show
Coding Style introduced by
checkForDev uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
checkForDev uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
79
  {
80
    // init
81 21
    $return = false;
82
83 21
    if (function_exists('checkForDev')) {
84
      $return = checkForDev();
85
    } else {
86
87
      // for testing with dev-address
88 21
      $noDev = isset($_GET['noDev']) ? (int)$_GET['noDev'] : 0;
89 21
      $remoteIpAddress = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : false;
90
91
      if (
92 21
          $noDev != 1
93
          &&
94
          (
95 21
              $remoteIpAddress === '127.0.0.1'
96
              ||
97 21
              $remoteIpAddress === '::1'
98
              ||
99 21
              PHP_SAPI === 'cli'
100
          )
101
      ) {
102 21
        $return = true;
103
      }
104
    }
105
106 21
    return $return;
107
  }
108
109
  /**
110
   * Clear the errors in "$this->_errors".
111
   *
112
   * @return bool
113
   */
114 4
  public function clearErrors()
115
  {
116 4
    $this->_errors = array();
117
118 4
    return true;
119
  }
120
121
  /**
122
   * Display SQL-Errors or throw Exceptions (for dev).
123
   *
124
   * @param string       $error
125
   * @param null|boolean $force_exception_after_error
126
   *
127
   * @throws \Exception
128
   */
129 21
  public function displayError($error, $force_exception_after_error = null)
130
  {
131 21
    $fileInfo = $this->getFileAndLineFromSql();
132
133 21
    $this->logger(
134
        array(
135 21
            'error',
136 21
            '<strong>' . date(
137 21
                'd. m. Y G:i:s'
138 21
            ) . ' (' . $fileInfo['file'] . ' line: ' . $fileInfo['line'] . ') (sql-error):</strong> ' . $error . '<br>',
139
        )
140
    );
141
142 21
    $this->_errors[] = $error;
143
144 21
    if ($this->checkForDev() === true) {
145
146 21
      if ($this->echo_on_error) {
147 4
        $box_border = $this->css_mysql_box_border;
148 4
        $box_bg = $this->css_mysql_box_bg;
149
150
        echo '
151 4
        <div class="OBJ-mysql-box" style="border:' . $box_border . '; background:' . $box_bg . '; padding:10px; margin:10px;">
152
          <b style="font-size:14px;">MYSQL Error:</b>
153
          <code style="display:block;">
154 4
            file / line: ' . $fileInfo['file'] . ' / ' . $fileInfo['line'] . '
155 4
            ' . $error . '
156
          </code>
157
        </div>
158 4
        ';
159
      }
160
161 21
      if ($force_exception_after_error === true) {
162 4
        throw new \Exception($error);
163 17
      } elseif ($force_exception_after_error === false) {
0 ignored issues
show
Unused Code introduced by
This elseif statement is empty, and could be removed.

This check looks for the bodies of elseif statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These elseif bodies can be removed. If you have an empty elseif but statements in the else branch, consider inverting the condition.

Loading history...
164
        // nothing
165 14
      } elseif ($force_exception_after_error === null) {
166
        // default
167 14
        if ($this->exit_on_error === true) {
168 2
          throw new \Exception($error);
169
        }
170
      }
171
    }
172 15
  }
173
174
  /**
175
   * Get errors from "$this->_errors".
176
   *
177
   * @return array
178
   */
179 3
  public function getErrors()
180
  {
181 3
    return $this->_errors;
182
  }
183
184
  /**
185
   * Try to get the file & line from the current sql-query.
186
   *
187
   * @return array will return array['file'] and array['line']
188
   */
189 21
  private function getFileAndLineFromSql()
190
  {
191
    // init
192 21
    $return = array();
193 21
    $file = '';
194 21
    $line = '';
195
196 21
    if (Bootup::is_php('5.4') === true) {
197 21
      $referrer = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10);
198
    } else {
199
      $referrer = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
200
    }
201
202 21
    foreach ($referrer as $key => $ref) {
203
204
      if (
205 21
          $ref['function'] === 'execSQL'
206
          ||
207 21
          $ref['function'] === 'query'
208
          ||
209 21
          $ref['function'] === 'qry'
210
          ||
211 21
          $ref['function'] === 'execute'
212
          ||
213 21
          $ref['function'] === 'insert'
214
          ||
215 21
          $ref['function'] === 'update'
216
          ||
217 21
          $ref['function'] === 'replace'
218
          ||
219 21
          $ref['function'] === 'delete'
220
      ) {
221 16
        $file = $referrer[$key]['file'];
222 21
        $line = $referrer[$key]['line'];
223
      }
224
225
    }
226
227 21
    $return['file'] = $file;
228 21
    $return['line'] = $line;
229
230 21
    return $return;
231
  }
232
233
  /**
234
   * @return string
235
   */
236
  public function getLoggerClassName()
237
  {
238
    return $this->logger_class_name;
239
  }
240
241
  /**
242
   * @return string
243
   */
244
  public function getLoggerLevel()
245
  {
246
    return $this->logger_level;
247
  }
248
249
  /**
250
   * @return boolean
251
   */
252
  public function isEchoOnError()
253
  {
254
    return $this->echo_on_error;
255
  }
256
257
  /**
258
   * @return boolean
259
   */
260
  public function isExitOnError()
261
  {
262
    return $this->exit_on_error;
263
  }
264
265
  /**
266
   * Log the current query via "$this->logger".
267
   *
268
   * @param string $sql     sql-query
269
   * @param int    $duration
270
   * @param int    $results field_count | insert_id | affected_rows
271
   * @param bool   $sql_error
272
   *
273
   * @return bool
274
   */
275 31
  public function logQuery($sql, $duration, $results, $sql_error = false)
276
  {
277 31
    $logLevelUse = strtolower($this->logger_level);
278
279
    if (
280 31
        $sql_error === false
281
        &&
282 31
        ($logLevelUse !== 'trace' && $logLevelUse !== 'debug')
283
    ) {
284 28
      return false;
285
    }
286
287
    // set log-level
288 11
    if ($sql_error === true) {
289 10
      $logLevel = 'error';
290
    } else {
291 1
      $logLevel = $logLevelUse;
292
    }
293
294
    // get extra info
295 11
    $infoExtra = mysqli_info($this->_db->getLink());
296 11
    if ($infoExtra) {
297 3
      $infoExtra = ' | info => ' . $infoExtra;
298
    }
299
300
    //
301
    // logging
302
    //
303
304 11
    $info = 'time => ' . round($duration, 5) . ' | results => ' . (int)$results . $infoExtra . ' | SQL => ' . UTF8::htmlentities($sql);
305
306 11
    $fileInfo = $this->getFileAndLineFromSql();
307
308 11
    return $this->logger(
309
        array(
310 11
            $logLevel,
311 11
            '<strong>' . date('d. m. Y G:i:s') . ' (' . $fileInfo['file'] . ' line: ' . $fileInfo['line'] . '):</strong> ' . $info . '<br>',
312 11
            'sql',
313
        )
314
    );
315
  }
316
317
  /**
318
   * Wrapper-Function for a "Logger"-Class.
319
   *
320
   * INFO:
321
   * The "Logger"-ClassName is set by "$this->logger_class_name",<br />
322
   * the "Logger"-Method is the [0] element from the "$log"-parameter,<br />
323
   * the text you want to log is the [1] element and<br />
324
   * the type you want to log is the next [2] element.
325
   *
326
   * @param string[] $log [method, text, type]<br />e.g.: array('error', 'this is a error', 'sql')
327
   *
328
   * @return bool
329
   */
330 22
  public function logger(array $log)
331
  {
332 22
    $logMethod = '';
333 22
    $logText = '';
334 22
    $logType = '';
335 22
    $logClass = $this->logger_class_name;
336
337 22
    if (isset($log[0])) {
338 22
      $logMethod = $log[0];
339
    }
340 22
    if (isset($log[1])) {
341 22
      $logText = $log[1];
342
    }
343 22
    if (isset($log[2])) {
344 11
      $logType = $log[2];
345
    }
346
347
    if (
348 22
        $logClass
349
        &&
350 22
        class_exists($logClass)
351
        &&
352 22
        method_exists($logClass, $logMethod)
353
    ) {
354
      return $logClass::$logMethod($logText, $logType);
355
    }
356
357 22
    return false;
358
  }
359
360
  /**
361
   * Send a error mail to the admin / dev.
362
   *
363
   * @param string $subject
364
   * @param string $htmlBody
365
   * @param int    $priority
366
   */
367 11
  public function mailToAdmin($subject, $htmlBody, $priority = 3)
368
  {
369 11
    if (function_exists('mailToAdmin')) {
370
      mailToAdmin($subject, $htmlBody, $priority);
371
    } else {
372
373 11
      if ($priority === 3) {
374 11
        $this->logger(
375
            array(
376 11
                'debug',
377 11
                $subject . ' | ' . $htmlBody,
378
            )
379
        );
380
      } elseif ($priority > 3) {
381
        $this->logger(
382
            array(
383
                'error',
384
                $subject . ' | ' . $htmlBody,
385
            )
386
        );
387
      } elseif ($priority < 3) {
388
        $this->logger(
389
            array(
390
                'info',
391
                $subject . ' | ' . $htmlBody,
392
            )
393
        );
394
      }
395
396
    }
397 11
  }
398
399
  /**
400
   * @param boolean $echo_on_error
401
   */
402 10
  public function setEchoOnError($echo_on_error)
403
  {
404 10
    $this->echo_on_error = (boolean)$echo_on_error;
405 10
  }
406
407
  /**
408
   * @param boolean $exit_on_error
409
   */
410 10
  public function setExitOnError($exit_on_error)
411
  {
412 10
    $this->exit_on_error = (boolean)$exit_on_error;
413 10
  }
414
415
  /**
416
   * @param string $logger_class_name
417
   */
418 10
  public function setLoggerClassName($logger_class_name)
419
  {
420 10
    $this->logger_class_name = (string)$logger_class_name;
421 10
  }
422
423
  /**
424
   * @param string $logger_level
425
   */
426 10
  public function setLoggerLevel($logger_level)
427
  {
428 10
    $this->logger_level = (string)$logger_level;
429 10
  }
430
431
}
432