Completed
Pull Request — master (#22)
by Lars
14:42
created

Debug::setLoggerLevel()   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
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 10
  public function __construct(DB $db)
64
  {
65 10
    $this->_db = $db;
66 10
  }
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
          $noDev != 1
93 21
          &&
94
          (
95
              $remoteIpAddress === '127.0.0.1'
96 21
              ||
97
              $remoteIpAddress === '::1'
98 21
              ||
99 21
              PHP_SAPI === 'cli'
100 21
          )
101 21
      ) {
102 21
        $return = true;
103 21
      }
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                       <p>The error message.</p>
125
   * @param null|boolean $force_exception_after_error <p>
126
   *                                                  If you use default "null" here, then the behavior depends
127
   *                                                  on "$this->exit_on_error (default: true)".
128
   *                                                  </p>
129
   *
130
   * @throws \Exception
131
   */
132 21
  public function displayError($error, $force_exception_after_error = null)
133
  {
134 21
    $fileInfo = $this->getFileAndLineFromSql();
135
136 21
    $this->logger(
137
        array(
138 21
            'error',
139 21
            '<strong>' . date(
140
                'd. m. Y G:i:s'
141 21
            ) . ' (' . $fileInfo['file'] . ' line: ' . $fileInfo['line'] . ') (sql-error):</strong> ' . $error . '<br>',
142
        )
143 21
    );
144
145 21
    $this->_errors[] = $error;
146
147 21
    if ($this->checkForDev() === true) {
148
149 21
      if ($this->echo_on_error) {
150 4
        $box_border = $this->css_mysql_box_border;
151 4
        $box_bg = $this->css_mysql_box_bg;
152
153
        echo '
154 4
        <div class="OBJ-mysql-box" style="border:' . $box_border . '; background:' . $box_bg . '; padding:10px; margin:10px;">
155
          <b style="font-size:14px;">MYSQL Error:</b>
156
          <code style="display:block;">
157 4
            file / line: ' . $fileInfo['file'] . ' / ' . $fileInfo['line'] . '
158 4
            ' . $error . '
159
          </code>
160
        </div>
161 4
        ';
162 4
      }
163
164 21
      if ($force_exception_after_error === true) {
165 4
        throw new \Exception($error);
166
      }
167
168 17
      if ($force_exception_after_error === false) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if 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 if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

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