1

I have made custom error handler (see below) that works just fine. I added method add() that is used to print and log error. Basically it handles all the PHP registered errors.

I tried to add add_error() function outside the class, for more developer friendly use of adding errors. The issue I am having is that, I would like to get proper line & file from it, but using debug_backtrace() seems too complicated. I mean.. It does the trick, but it returns array of other unnecessary stuff and I tried filtering the array, to return the file and line the error was called from, but with no luck.

If I call $error->add('test','test') for example from load.php file where on top the class file error-handle.php is included, it returns

C:\WebServer\www\projects\backend-framework\includes\error-handle.php:144:
array (size=2)
  0 => 
    array (size=7)
      'file' => string 'C:\WebServer\www\projects\backend-framework\load.php' (length=52)
      'line' => int 34
      'function' => string 'add' (length=3)
      'class' => string 'ErrorHandle' (length=11)
      'object' => 
        object(ErrorHandle)[1]
          public 'count' => int 0
          public 'last_error' => null
          public 'errors' => 
            array (size=0)
              ...
          public 'debug' => int 1
          public 'report_errors' => int 1
          public 'log_errors' => int 1
      'type' => string '->' (length=2)
      'args' => 
        array (size=2)
          0 => string 'test' (length=5)
          1 => string 'test' (length=5)
  1 => 
    array (size=4)
      'file' => string 'C:\WebServer\www\projects\backend-framework\index.php' (length=53)
      'line' => int 2
      'args' => 
        array (size=1)
          0 => string 'C:\WebServer\www\projects\backend-framework\load.php' (length=52)
      'function' => string 'require_once' (length=12)

test: test in C:\WebServer\www\projects\backend-framework\index.php on line 2

As you can see it returns index.php instead of load.php

Bascially whenever I call add_error() or $error->add() I need to get proper file & line where the function was called from. Possibly by using debug_backtrace() with some advanced filtering.

I will be thankful for any guidance or ideas how to get this result. Thanks in advance!

CUSTOM ERROR HANDLER:

<?php
/** Custom error handle **/
class ErrorHandle {
    public $count = 0;
    public $last_error;
    public $errors = array();
    public $debug;
    public $report_errors;
    public $log_errors;

    function __construct($debug = false, $report_errors = false, $log_errors = true){
        // Set error report & log args
        $this->debug = $debug;
        $this->report_errors = $report_errors;
        $this->log_errors = $log_errors;

        // Setup error reporting & logging
        $this->report_errors($this->report_errors);
        $this->log_errors($this->log_errors);


        // Register error handle
        set_error_handler(array($this, 'error_handler'));
        // Register fatal error handle
        register_shutdown_function(array($this, 'fatal_error_handler'));
    }

    function error_handler($type, $message, $file, $line){
        $html_message = '';
        switch ($type) {
            case E_ERROR:
                $error_type = 'Runtime Error';
                break;
            case E_WARNING:
                $error_type = 'Runtime Warning';
                break;
            case E_PARSE:
                $error_type = 'Runtime Parse';
                break;
            case E_NOTICE:
                $error_type = 'Runtime Notice';
                break;
            case E_CORE_ERROR:
                $error_type = 'Core Error';
                break;
            case E_CORE_WARNING:
                $error_type = 'Core Warning';
                break;
            case E_COMPILE_ERROR:
                $error_type = 'Compile Error';
                break;
            case E_COMPILE_WARNING:
                $error_type = 'Compile Warning';
                break;
            case E_USER_ERROR:
                $error_type = 'User Error';
                break;
            case E_USER_WARNING:
                $error_type = 'User Warning';
                break;
            case E_USER_NOTICE:
                $error_type = 'User Notice';
                break;
            case E_STRICT:
                $error_type = 'PHP Suggestion';
                break;
            case E_RECOVERABLE_ERROR:
                $error_type = 'PHP Notice';
                break;
            case E_DEPRECATED:
                $error_type = 'PHP Warning';
                break;
            case E_USER_DEPRECATED:
                $error_type = 'PHP Deprecation';
                break;
            default:
                $error_type = 'Unknown';
        }

        $this->add($error_type, $message, $file, $line);
    }

    // Fatal error handler
    function fatal_error_handler() {
        $last_error = error_get_last();

        if(in_array($last_error['type'],array( E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR, E_CORE_WARNING, E_COMPILE_WARNING, E_PARSE))){
            $this->error_handler($last_error['type'], $last_error['message'], $last_error['file'], $last_error['line']);
        }

        die();
    }

    // Turn ON/OFF display errors
    function report_errors($bool=false){

        ini_set('display_startup_errors', 0);
        ini_set('docref_root', 0);
        ini_set('docref_ext', 0);

        if($bool){
            error_reporting(999999999);
            ini_set('error_reporting', 999999999);
            ini_set('display_errors', 1);

            ini_set('report_memleaks', 1);
            ini_set('track_errors', 1);
            ini_set('display_startup_errors', 1);

            return true;
        }else{
            error_reporting(0);
            ini_set('error_reporting', 0);
            ini_set('display_errors', 0);

            return false;
        }
    }

    function log_errors($bool){
        if($this->log_errors){
            ini_set("log_errors", 1);
            ini_set("error_log", LOGS_DIR . 'php-error_' . date('d.m.y') . '.txt');
            return true;
        }
        return false;
    }

    function add($type, $message, $file = '', $line = 0){

        $html_message = '';
        $backtrace = debug_backtrace();


        if ($file == '') $file = $backtrace[1]['file'];
        if ($line == 0) $line = $backtrace[1]['line'];

        //var_dump($backtrace);

        $error = array(
            'type'      => $type,
            'message'   => $message,
            'file'      => $file,
            'line'      => $line
        );

        if(!in_multi_array($error, $this->errors)){
            $this->count++;
            $this->errors[$this->count] = $error;
            $this->last_error = $this->errors[$this->count];

            if($this->report_errors == true) echo $type . ': <strong>' . $message . '</strong> ' . '<i>in</i> <u>' . $file . '</u> <i>on line</i> <strong>' . $line . '</strong></i></br>';

            if($this->log_errors == true) error_log( $type . ': ' . $message . ' in ' . $file . ' on line ' . $line );
            return true;
        }
        return false;
    }

}
?>
richardev
  • 976
  • 1
  • 10
  • 33

2 Answers2

1

As you can see it returns index.php instead of load.php

It actually returns both, it's just that you're accessing $backtrace[1]['file'] when it appears that you actually want $backtrace[0]['file'].

I think debug_backtrace makes a lot more sense when you consider that depth must be taken into consideration. Use the following example as a starting point:

function debug($depth) {
    $trace = debug_backtrace()[$depth];
    if (isset($trace['file'], $trace['line'])) {
        return [$trace['file'], $trace['line']];
    }
    return false;
}

// check depth 0
if (list($file, $line) = debug(0)) {
    // ...
}

With this, you can experiment to see what depth you're actually looking for. Of course, that all depends on how your application is organized so certain files may have different depths.

mister martin
  • 6,197
  • 4
  • 30
  • 63
  • Thanks. I'll try to look in to that. Meanwhile, I've come accross this method, that seems to be working on top level functions. `$dbt=debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS,2); $caller = isset($dbt[1]['function']) ? $dbt[1]['function'] : null;` Talking about depths - that's the main reason why I thought about filtering for **debug_backtrace()** – richardev Nov 01 '16 at 15:57
0

Okay so I made a small work around. This is method from ErrorHandle class:

     /**
     * Report (print), Log error 
     * Use this function inside functions or methods. If you need
     * need to add custom error outside - use global add_error() function.
     * (This is because there is not yet a proper debug_backtrace method to use
     * to return error FILE name & LINE number) properly!!!
     */
    public function add($type, $message, $file = '', $line = 0){

        $html_message = '';

        /**
         * WARNING! This is beta version
         * If custom error is added, we need to backtrace it & filter the result to
         * get accurate FILE name & LINE number.
         */
        if($file == '' || $line == 0) :

            $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);

            // Filtering - if we find something to exclude, we remove it to get closer to actual file & line nr.
            foreach ($backtrace as $key => $value) {
                // The filter
                if((isset($value) && strpos($value['file'], 'error-handle.php')) || (isset($value['class']) && $value['class'] == 'ErrorHandle' && $value['function'] == 'add')){
                    array_splice($backtrace, $key, 1);
                }
            }

            if ($file == '') $file = $backtrace[0]['file'];
            if ($line == 0) $line = $backtrace[0]['line'];

        endif;


        $error = array(
            'type'      => $type,
            'message'   => $message,
            'file'      => $file,
            'line'      => $line
        );

        // Let's check if we haven't stored the same error previously, so there's no dublication
        if(!in_multi_array($error, $this->errors)){
            $this->count++;
            $this->errors[$this->count] = $error;
            $this->last_error = $this->errors[$this->count];

            // Report Error Message
            if($this->report_errors) echo $type . ': <strong>' . $message . '</strong> ' . '<i>in</i> <u>' . $file . '</u> <i>on line</i> <strong>' . $line . '</strong></i></br>';
            // Log Error Message
            if($this->log_errors) error_log( $type . ': ' . $message . ' in ' . $file . ' on line ' . $line );

            return true;
        }
        return false;
    }

And outside the class I also created this function:

/**
 * Add error 
 * Don't use this function inside functions or methods!!!
 * If you need to add error inside function or method - use $error->add() function.
 * (This is because there is not yet a proper debug_backtrace method to use
 * to return error FILE name & LINE number) properly!!!
 */
function add_error($type, $message, $file = '', $line = 0){
    global $error;
    return $error->add($type, $message, $file, $line);
}

Everything seems to work fine. Tried this on different depths & levels. BUT.. the only issue, that I can't find a way to fix - $error->add() must be always used within functions & methods, while add_error() function outside (can't be used within functions & methods). Not sure yet why, but this is something to begin with :)

richardev
  • 976
  • 1
  • 10
  • 33