Completed
Push — master ( 9055a2...5efc13 )
by Lars
03:52
created

Debug::checkForDev()   C

Complexity

Conditions 8
Paths 9

Size

Total Lines 30
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 8.125

Importance

Changes 0
Metric Value
dl 0
loc 30
ccs 14
cts 16
cp 0.875
rs 5.3846
c 0
b 0
f 0
cc 8
eloc 17
nc 9
nop 0
crap 8.125
1
<?php
2
3
namespace voku\db;
4
5
use voku\db\exceptions\QueryException;
6
use voku\helper\Bootup;
7
use voku\helper\UTF8;
8
9
/**
10
 * Debug: This class can handle debug and error-logging for SQL-queries for the "Simple-MySQLi"-classes.
11
 *
12
 * @package   voku\db
13
 */
14
class Debug
15
{
16
  /**
17
   * @var array
18
   */
19
  private $_errors = array();
20
21
  /**
22
   * @var bool
23
   */
24
  private $exit_on_error = true;
25
26
  /**
27
   * echo the error if "checkForDev()" returns true
28
   *
29
   * @var bool
30
   */
31
  private $echo_on_error = true;
32
33
  /**
34
   * @var string
35
   */
36
  private $css_mysql_box_border = '3px solid red';
37
38
  /**
39
   * @var string
40
   */
41
  private $css_mysql_box_bg = '#FFCCCC';
42
43
  /**
44
   * @var string
45
   */
46
  private $logger_class_name;
47
48
  /**
49
   * @var DB
50
   */
51
  private $_db;
52
53
  /**
54
   * @var string
55
   *
56
   * 'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'
57
   */
58
  private $logger_level;
59
60
  /**
61
   * Debug constructor.
62
   *
63
   * @param DB $db
64
   */
65 11
  public function __construct(DB $db)
66
  {
67 11
    $this->_db = $db;
68 11
  }
69
70
  /**
71
   * Check is the current user is a developer.
72
   *
73
   * INFO:
74
   * By default we will return "true" if the remote-ip-address is localhost or
75
   * if the script is called via CLI. But you can also overwrite this method or
76
   * you can implement a global "checkForDev()"-function.
77
   *
78
   * @return bool
79
   */
80 24
  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...
81
  {
82
    // init
83 24
    $return = false;
84
85 24
    if (function_exists('checkForDev')) {
86
      $return = checkForDev();
87
    } else {
88
89
      // for testing with dev-address
90 24
      $noDev = isset($_GET['noDev']) ? (int)$_GET['noDev'] : 0;
91 24
      $remoteIpAddress = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : false;
92
93
      if (
94
          $noDev != 1
95 24
          &&
96
          (
97
              $remoteIpAddress === '127.0.0.1'
98 24
              ||
99
              $remoteIpAddress === '::1'
100 24
              ||
101 24
              PHP_SAPI === 'cli'
102 24
          )
103 24
      ) {
104 24
        $return = true;
105 24
      }
106
    }
107
108 24
    return $return;
109
  }
110
111
  /**
112
   * Clear the errors in "$this->_errors".
113
   *
114
   * @return bool
115
   */
116 6
  public function clearErrors()
117
  {
118 6
    $this->_errors = array();
119
120 6
    return true;
121
  }
122
123
  /**
124
   * Display SQL-Errors or throw Exceptions (for dev).
125
   *
126
   * @param string       $error                       <p>The error message.</p>
127
   * @param null|boolean $force_exception_after_error <p>
128
   *                                                  If you use default "null" here, then the behavior depends
129
   *                                                  on "$this->exit_on_error (default: true)".
130
   *                                                  </p>
131
   *
132
   * @throws QueryException
133
   */
134 24
  public function displayError($error, $force_exception_after_error = null)
135
  {
136 24
    $fileInfo = $this->getFileAndLineFromSql();
137
138 24
    $this->logger(
139
        array(
140 24
            'error',
141 24
            '<strong>' . date(
142
                'd. m. Y G:i:s'
143 24
            ) . ' (' . $fileInfo['file'] . ' line: ' . $fileInfo['line'] . ') (sql-error):</strong> ' . $error . '<br>',
144
        )
145 24
    );
146
147 24
    $this->_errors[] = $error;
148
149 24
    if ($this->checkForDev() === true) {
150 24
      if ($this->echo_on_error) {
151 6
        $box_border = $this->css_mysql_box_border;
152 6
        $box_bg = $this->css_mysql_box_bg;
153
154
        echo '
155 6
        <div class="OBJ-mysql-box" style="border:' . $box_border . '; background:' . $box_bg . '; padding:10px; margin:10px;">
156
          <b style="font-size:14px;">MYSQL Error:</b>
157
          <code style="display:block;">
158 6
            file / line: ' . $fileInfo['file'] . ' / ' . $fileInfo['line'] . '
159 6
            ' . $error . '
160
          </code>
161
        </div>
162 6
        ';
163 6
      }
164 24
    }
165
166
    if (
167
        $force_exception_after_error === true
168 24
        ||
169
        (
170
            $force_exception_after_error === null
171 21
            &&
172 10
            $this->exit_on_error === true
173 10
        )
174 24
    ) {
175 3
      throw new QueryException($error);
176
    }
177 21
  }
178
179
  /**
180
   * Get errors from "$this->_errors".
181
   *
182
   * @return array
183
   */
184 5
  public function getErrors()
185
  {
186 5
    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 24
  private function getFileAndLineFromSql()
195
  {
196
    // init
197 24
    $return = array();
198 24
    $file = '';
199 24
    $line = '';
200
201 24
    if (Bootup::is_php('5.4') === true) {
202
      $referrer = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10);
203
    } else {
204 24
      $referrer = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
205
    }
206
207 24
    foreach ($referrer as $key => $ref) {
208
209
      if (
210 24
          $ref['function'] === 'execSQL'
211 24
          ||
212 24
          $ref['function'] === 'query'
213 24
          ||
214 24
          $ref['function'] === 'qry'
215 24
          ||
216 24
          $ref['function'] === 'execute'
217 24
          ||
218 24
          $ref['function'] === 'insert'
219 24
          ||
220 24
          $ref['function'] === 'update'
221 24
          ||
222 24
          $ref['function'] === 'replace'
223 24
          ||
224 24
          $ref['function'] === 'delete'
225 24
      ) {
226 19
        $file = $referrer[$key]['file'];
227 19
        $line = $referrer[$key]['line'];
228 19
      }
229
230 24
    }
231
232 24
    $return['file'] = $file;
233 24
    $return['line'] = $line;
234
235 24
    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 98
  public function logQuery($sql, $duration, $results, $sql_error = false)
281
  {
282 98
    $logLevelUse = strtolower($this->logger_level);
283
284
    if (
285
        $sql_error === false
286 98
        &&
287 95
        ($logLevelUse !== 'trace' && $logLevelUse !== 'debug')
288 98
    ) {
289 95
      return false;
290
    }
291
292
    // set log-level
293 15
    $logLevel = $logLevelUse;
294 15
    if ($sql_error === true) {
295 14
      $logLevel = 'error';
296 14
    }
297
298
    // get extra info
299 15
    $infoExtra = \mysqli_info($this->_db->getLink());
300 15
    if ($infoExtra) {
301 3
      $infoExtra = ' | info => ' . $infoExtra;
302 3
    }
303
304
    //
305
    // logging
306
    //
307
308 15
    $info = 'time => ' . round($duration, 5) . ' | results => ' . (int)$results . $infoExtra . ' | SQL => ' . UTF8::htmlentities($sql);
309
310 15
    $fileInfo = $this->getFileAndLineFromSql();
311
312 15
    return $this->logger(
313
        array(
314 15
            $logLevel,
315 15
            '<strong>' . date('d. m. Y G:i:s') . ' (' . $fileInfo['file'] . ' line: ' . $fileInfo['line'] . '):</strong> ' . $info . '<br>',
316 15
            'sql',
317
        )
318 15
    );
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 25
  public function logger(array $log)
335
  {
336 25
    $logMethod = '';
337 25
    $logText = '';
338 25
    $logType = '';
339 25
    $logClass = $this->logger_class_name;
340
341 25
    if (isset($log[0])) {
342 25
      $logMethod = $log[0];
343 25
    }
344 25
    if (isset($log[1])) {
345 25
      $logText = $log[1];
346 25
    }
347 25
    if (isset($log[2])) {
348 15
      $logType = $log[2];
349 15
    }
350
351
    if (
352
        $logClass
353 25
        &&
354
        class_exists($logClass)
355 25
        &&
356
        method_exists($logClass, $logMethod)
357 25
    ) {
358
      return $logClass::$logMethod($logText, $logType);
359
    }
360
361 25
    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 15
  public function mailToAdmin($subject, $htmlBody, $priority = 3)
372
  {
373 15
    if (function_exists('mailToAdmin')) {
374
      mailToAdmin($subject, $htmlBody, $priority);
375
    } else {
376
377 15
      if ($priority === 3) {
378 15
        $this->logger(
379
            array(
380 15
                'debug',
381 15
                $subject . ' | ' . $htmlBody,
382
            )
383 15
        );
384 15
      } 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 15
  }
402
403
  /**
404
   * @param boolean $echo_on_error
405
   */
406 11
  public function setEchoOnError($echo_on_error)
407
  {
408 11
    $this->echo_on_error = (boolean)$echo_on_error;
409 11
  }
410
411
  /**
412
   * @param boolean $exit_on_error
413
   */
414 11
  public function setExitOnError($exit_on_error)
415
  {
416 11
    $this->exit_on_error = (boolean)$exit_on_error;
417 11
  }
418
419
  /**
420
   * @param string $logger_class_name
421
   */
422 11
  public function setLoggerClassName($logger_class_name)
423
  {
424 11
    $this->logger_class_name = (string)$logger_class_name;
425 11
  }
426
427
  /**
428
   * @param string $logger_level
429
   */
430 11
  public function setLoggerLevel($logger_level)
431
  {
432 11
    $this->logger_level = (string)$logger_level;
433 11
  }
434
435
}
436