Completed
Push — master ( 0dcb22...d7c9a3 )
by Lars
10:36 queued 01:45
created

Debug::mailToAdmin()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 31
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 9.664

Importance

Changes 0
Metric Value
dl 0
loc 31
ccs 9
cts 21
cp 0.4286
rs 8.439
c 0
b 0
f 0
cc 5
eloc 19
nc 5
nop 3
crap 9.664
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
          $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
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
                'd. m. Y G:i:s'
138 21
            ) . ' (' . $fileInfo['file'] . ' line: ' . $fileInfo['line'] . ') (sql-error):</strong> ' . $error . '<br>',
139
        )
140 21
    );
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 4
      }
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 17
      } elseif ($force_exception_after_error === null) {
166
        // default
167 14
        if ($this->exit_on_error === true) {
168 2
          throw new \Exception($error);
169
        }
170 12
      }
171 15
    }
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
      $referrer = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10);
198
    } else {
199 21
      $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 21
          ||
207 21
          $ref['function'] === 'query'
208 21
          ||
209 21
          $ref['function'] === 'qry'
210 21
          ||
211 21
          $ref['function'] === 'execute'
212 21
          ||
213 21
          $ref['function'] === 'insert'
214 21
          ||
215 21
          $ref['function'] === 'update'
216 21
          ||
217 21
          $ref['function'] === 'replace'
218 21
          ||
219 21
          $ref['function'] === 'delete'
220 21
      ) {
221 16
        $file = $referrer[$key]['file'];
222 16
        $line = $referrer[$key]['line'];
223 16
      }
224
225 21
    }
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 35
  public function logQuery($sql, $duration, $results, $sql_error = false)
276
  {
277 35
    $logLevelUse = strtolower($this->logger_level);
278
279
    if (
280
        $sql_error === false
281 35
        &&
282 32
        ($logLevelUse !== 'trace' && $logLevelUse !== 'debug')
283 35
    ) {
284 32
      return false;
285
    }
286
287
    // set log-level
288 11
    $logLevel = $logLevelUse;
289 11
    if ($sql_error === true) {
290 10
      $logLevel = 'error';
291 10
    }
292
293
    // get extra info
294 11
    $infoExtra = \mysqli_info($this->_db->getLink());
295 11
    if ($infoExtra) {
296 3
      $infoExtra = ' | info => ' . $infoExtra;
297 3
    }
298
299
    //
300
    // logging
301
    //
302
303 11
    $info = 'time => ' . round($duration, 5) . ' | results => ' . (int)$results . $infoExtra . ' | SQL => ' . UTF8::htmlentities($sql);
304
305 11
    $fileInfo = $this->getFileAndLineFromSql();
306
307 11
    return $this->logger(
308
        array(
309 11
            $logLevel,
310 11
            '<strong>' . date('d. m. Y G:i:s') . ' (' . $fileInfo['file'] . ' line: ' . $fileInfo['line'] . '):</strong> ' . $info . '<br>',
311 11
            'sql',
312
        )
313 11
    );
314
  }
315
316
  /**
317
   * Wrapper-Function for a "Logger"-Class.
318
   *
319
   * INFO:
320
   * The "Logger"-ClassName is set by "$this->logger_class_name",<br />
321
   * the "Logger"-Method is the [0] element from the "$log"-parameter,<br />
322
   * the text you want to log is the [1] element and<br />
323
   * the type you want to log is the next [2] element.
324
   *
325
   * @param string[] $log [method, text, type]<br />e.g.: array('error', 'this is a error', 'sql')
326
   *
327
   * @return bool
328
   */
329 22
  public function logger(array $log)
330
  {
331 22
    $logMethod = '';
332 22
    $logText = '';
333 22
    $logType = '';
334 22
    $logClass = $this->logger_class_name;
335
336 22
    if (isset($log[0])) {
337 22
      $logMethod = $log[0];
338 22
    }
339 22
    if (isset($log[1])) {
340 22
      $logText = $log[1];
341 22
    }
342 22
    if (isset($log[2])) {
343 11
      $logType = $log[2];
344 11
    }
345
346
    if (
347
        $logClass
348 22
        &&
349
        class_exists($logClass)
350 22
        &&
351
        method_exists($logClass, $logMethod)
352 22
    ) {
353
      return $logClass::$logMethod($logText, $logType);
354
    }
355
356 22
    return false;
357
  }
358
359
  /**
360
   * Send a error mail to the admin / dev.
361
   *
362
   * @param string $subject
363
   * @param string $htmlBody
364
   * @param int    $priority
365
   */
366 11
  public function mailToAdmin($subject, $htmlBody, $priority = 3)
367
  {
368 11
    if (function_exists('mailToAdmin')) {
369
      mailToAdmin($subject, $htmlBody, $priority);
370
    } else {
371
372 11
      if ($priority === 3) {
373 11
        $this->logger(
374
            array(
375 11
                'debug',
376 11
                $subject . ' | ' . $htmlBody,
377
            )
378 11
        );
379 11
      } elseif ($priority > 3) {
380
        $this->logger(
381
            array(
382
                'error',
383
                $subject . ' | ' . $htmlBody,
384
            )
385
        );
386
      } elseif ($priority < 3) {
387
        $this->logger(
388
            array(
389
                'info',
390
                $subject . ' | ' . $htmlBody,
391
            )
392
        );
393
      }
394
395
    }
396 11
  }
397
398
  /**
399
   * @param boolean $echo_on_error
400
   */
401 10
  public function setEchoOnError($echo_on_error)
402
  {
403 10
    $this->echo_on_error = (boolean)$echo_on_error;
404 10
  }
405
406
  /**
407
   * @param boolean $exit_on_error
408
   */
409 10
  public function setExitOnError($exit_on_error)
410
  {
411 10
    $this->exit_on_error = (boolean)$exit_on_error;
412 10
  }
413
414
  /**
415
   * @param string $logger_class_name
416
   */
417 10
  public function setLoggerClassName($logger_class_name)
418
  {
419 10
    $this->logger_class_name = (string)$logger_class_name;
420 10
  }
421
422
  /**
423
   * @param string $logger_level
424
   */
425 10
  public function setLoggerLevel($logger_level)
426
  {
427 10
    $this->logger_level = (string)$logger_level;
428 10
  }
429
430
}
431