Backtrace fatal error php

(PHP 4 >= 4.3.0, PHP 5, PHP 7, PHP 8)

(PHP 4 >= 4.3.0, PHP 5, PHP 7, PHP 8)

debug_backtraceGenerates a backtrace

Description

debug_backtrace(int $options = DEBUG_BACKTRACE_PROVIDE_OBJECT, int $limit = 0): array

Parameters

options

This parameter is a bitmask for the following options:

debug_backtrace() options

DEBUG_BACKTRACE_PROVIDE_OBJECT Whether or not to populate the «object» index.
DEBUG_BACKTRACE_IGNORE_ARGS Whether or not to omit the «args» index, and thus all the function/method arguments,
to save memory.
limit

This parameter can be used to limit the number of stack frames returned.
By default (limit=0) it returns all stack frames.

Return Values

Returns an array of associative arrays. The possible returned elements
are as follows:

Possible returned elements from debug_backtrace()

Name Type Description
function string The current function name. See also
__FUNCTION__.
line int The current line number. See also
__LINE__.
file string The current file name. See also
__FILE__.
class string The current class name. See also
__CLASS__
object object The current object.
type string The current call type. If a method call, «->» is returned. If a static
method call, «::» is returned. If a function call, nothing is returned.
args array If inside a function, this lists the functions arguments. If
inside an included file, this lists the included file name(s).

Examples

Example #1 debug_backtrace() example


<?php
// filename: /tmp/a.phpfunction a_test($str)
{
echo
"nHi: $str";
var_dump(debug_backtrace());
}
a_test('friend');
?>

<?php
// filename: /tmp/b.php
include_once '/tmp/a.php';
?>

Results similar to the following when executing
/tmp/b.php:

Hi: friend
array(2) {
[0]=>
array(4) {
    ["file"] => string(10) "/tmp/a.php"
    ["line"] => int(10)
    ["function"] => string(6) "a_test"
    ["args"]=>
    array(1) {
      [0] => &string(6) "friend"
    }
}
[1]=>
array(4) {
    ["file"] => string(10) "/tmp/b.php"
    ["line"] => int(2)
    ["args"] =>
    array(1) {
      [0] => string(10) "/tmp/a.php"
    }
    ["function"] => string(12) "include_once"
  }
}

See Also

  • trigger_error() — Generates a user-level error/warning/notice message
  • debug_print_backtrace() — Prints a backtrace

jurchiks101 at gmail dot com

9 years ago


Here's a function I just wrote for getting a nice and comprehensible call trace. It is probably more resource-intensive than some other alternatives but it is short, understandable, and gives nice output (Exception->getTraceAsString()).

<?php
function generateCallTrace()
{
   
$e = new Exception();
   
$trace = explode("n", $e->getTraceAsString());
   
// reverse array to make steps line up chronologically
   
$trace = array_reverse($trace);
   
array_shift($trace); // remove {main}
   
array_pop($trace); // remove call to this method
   
$length = count($trace);
   
$result = array();

        for (

$i = 0; $i < $length; $i++)
    {
       
$result[] = ($i + 1)  . ')' . substr($trace[$i], strpos($trace[$i], ' ')); // replace '#someNum' with '$i)', set the right ordering
   
}

        return

"t" . implode("nt", $result);
}
?>

Example output:
    1) /var/www/test/test.php(15): SomeClass->__construct()
    2) /var/www/test/SomeClass.class.php(36): SomeClass->callSomething()


Anonymous

10 years ago


Simple function to get a string in form "filename: [class->][function(): ]"

<?php
function get_caller_info() {
   
$c = '';
   
$file = '';
   
$func = '';
   
$class = '';
   
$trace = debug_backtrace();
    if (isset(
$trace[2])) {
       
$file = $trace[1]['file'];
       
$func = $trace[2]['function'];
        if ((
substr($func, 0, 7) == 'include') || (substr($func, 0, 7) == 'require')) {
           
$func = '';
        }
    } else if (isset(
$trace[1])) {
       
$file = $trace[1]['file'];
       
$func = '';
    }
    if (isset(
$trace[3]['class'])) {
       
$class = $trace[3]['class'];
       
$func = $trace[3]['function'];
       
$file = $trace[2]['file'];
    } else if (isset(
$trace[2]['class'])) {
       
$class = $trace[2]['class'];
       
$func = $trace[2]['function'];
       
$file = $trace[1]['file'];
    }
    if (
$file != '') $file = basename($file);
   
$c = $file . ": ";
   
$c .= ($class != '') ? ":" . $class . "->" : "";
   
$c .= ($func != '') ? $func . "(): " : "";
    return(
$c);
}
?>

Usage like:

<?php
function debug($str) {
    echo
get_caller_info() . $str . "<br>n";
}
?>

get_caller_info() will return info about the function /class->method that called debug().


jsnell at e-normous dot com

15 years ago


If you are using the backtrace function in an error handler, avoid using var_export() on the args, as you will cause fatal errors in some situations, preventing you from seeing your stack trace.  Some structures will cause PHP to generate the fatal error "Nesting level too deep - recursive dependency?" This is a design feature of php, not a bug (see http://bugs.php.net/bug.php?id=30471)

robert at medianis dot net

6 years ago


Just a short note on debug_backtrace options for PHP 5.3.6 or newer:

debug_backtrace() - show all options
debug_backtrace(0) - exlude ["object"]
debug_backtrace(1) - same as debug_backtrace()
debug_backtrace(2) - exlude ["object"] AND ["args"]

use this example and try calling debug_backtrace with different options

<?php
function F1()
{
    echo
"<br />";
    echo
"in F1 now";
    echo
"<pre>".print_r(debug_backtrace(2),true)."</pre>";
}

class

DebugOptionsTest
{
    function
F2()
    {
        echo
"<br />";
        echo
"in F2 now";
       
F1();
    }

}

echo

"<hr />calling F1";
F1();$c=new DebugOptionsTest();
echo
"<hr /><hr /><hr />calling F2";
$c->F2("testValue");?>


jake at qzdesign dot co dot uk

3 years ago


The `args` element contains only the arguments actually passed to the function or method.  It does not include default parameters if they were not explicitly specified.  (A least, this is the case with PHP 7.1.9.)  This is consistent with the behaviour of `func_get_args()`.

d at rren dot me

10 years ago


Howdy guys, just a note really - The ['args'] data within the resulting array is supplied by reference. I found myself editing the reference unknowingly which in turn shows its ugly head further down the line if you call multiple backtrace.

<?php

$trace
= array_reverse(debug_backtrace());
// LOOP BACKTRACE

$la = 0;

$lb = count($trace);

while (
$la<$lb){
// DATA FROM BACKTRACE

   
$trace[$la]['file'];

   
$trace[$la]['line'];

   
$trace[$la]['args'];

   
$trace[$la]['function'];

   
// DATA FROM BACKTRACE

    // LOOP ARGUMENTS ARRAY

$ba = 0;

   
$bb = count($trace[$la]['args']);

    while (
$ba<$bb){
$trace[$la]['args'][$ba] = "EDITING A REFERENCE/POINTER";
$ba++;

    }

    unset(
$bb);

    unset(
$ba);

   
// LOOP ARGUMENTS ARRAY
$la++;

}

unset(
$lb);

unset(
$la);

// LOOP BACKTRACE

?>


michael dot schramm at gmail dot com

13 years ago


Be carefull if you are using objects as arguments for function calls!

<?php
error_reporting
(E_ALL);

function

myPrint($trace){
    foreach(
$trace as $i=>$call){
       
/**
         * THIS IS NEEDED! If all your objects have a __toString function it's not needed!
         *
         * Catchable fatal error: Object of class B could not be converted to string
         * Catchable fatal error: Object of class A could not be converted to string
         * Catchable fatal error: Object of class B could not be converted to string
         */
       
if (is_object($call['object'])) { $call['object'] = 'CONVERTED OBJECT OF CLASS '.get_class($call['object']); }
        if (
is_array($call['args'])) {
            foreach (
$call['args'] AS &$arg) {
                if (
is_object($arg)) { $arg = 'CONVERTED OBJECT OF CLASS '.get_class($arg); }
            }
        }
$trace_text[$i] = "#".$i." ".$call['file'].'('.$call['line'].') ';
       
$trace_text[$i].= (!empty($call['object'])?$call['object'].$call['type']:'');
       
$trace_text[$i].= $call['function'].'('.implode(', ',$call['args']).')';
    }
var_dump($trace_text);
}

class

A{
    public function
test($obj){
       
$obj->test();
    }
}

class

B{
    public function
test(){
        echo
myPrint(debug_backtrace());
    }
}
$A = new A();
$B = new B();$A->test($B);?>


Emmett Brosnan

6 years ago


Quick and dirty formatted output from debug_backtrace.

$file_paths = debug_backtrace();

  foreach($file_paths AS $file_path) {
  foreach($file_path AS $key => $var) {
    if($key == 'args') {
      foreach($var AS $key_arg => $var_arg) {
        echo $key_arg . ': ' . $var_arg . '<br>';
      }
    } else {
      echo $key . ': ' . $var . '<br>';
    } 
  }
}


root at jackyyf dot com

10 years ago


When use register_shutdown_function, and the function called when shutting down, there are no line number nor filename information about this function, only function, class(if possible), type(if possible) and args are provided.

jcmargentina at gmail dot com

3 years ago


I want to point out that debug_backtrace() in new versions of php can detect recursion // circular references .. avoiding memory consumption.

Example:

<?phpclass ParentClass {
        public function
__construct()
        {
               
$this->_child = new ChildClass($this);
               
var_dump(debug_backtrace());
        }
}

class

ChildClass {
        public function
__construct(ParentClass $p)
        {
               
$this->_parent = $p;
        }
}
$test = new ParentClass();
?>

Output:

array(1) {
  [0]=>
  array(7) {
    ["file"]=>
    string(23) "/home/jcm/testdebug.php"
    ["line"]=>
    int(18)
    ["function"]=>
    string(11) "__construct"
    ["class"]=>
    string(11) "ParentClass"
    ["object"]=>
    object(ParentClass)#1 (1) {
      ["_child"]=>
      object(ChildClass)#2 (1) {
        ["_parent"]=>
        *RECURSION*
      }
    }
    ["type"]=>
    string(2) "->"
    ["args"]=>
    array(0) {
    }
  }
}

Attention in the *RECURSION* hint provided


kenorb at gmail dot com

12 years ago


One line of code to print simplest and shortest human readable backtrace:)
<?php
array_walk
(debug_backtrace(),create_function('$a,$b','print "{$a['function']}()(".basename($a['file']).":{$a['line']}); ";'));
?>

jonas at faceways dot se

9 years ago


When using debug_backtrace() to check if you're being accessed from another caller, please remember to ask debug_backtrace to only go as far as needed in depth and skip taking the entire debug object as return parameter:

<?php
   
if (count(debug_backtrace(FALSE, 1)) == 0)
    {
       
// Do something
   
}
?>


anoam at yandex dot ru

8 years ago


It works a little bit different with resources in different PHP versions.

For example:
function foo($bar)
{
  return debug_backtrace();
}

$resource = fopen(__FILE__, 'r');
$backtrace = foo($resource);
echo "when resource is opened: " . gettype($backtrace[0]['args'][0]) . "n";
fclose($resource);
echo "when resource is closed: " . gettype($backtrace[0]['args'][0]) . "n";

With 5.3.10 I got:
when resource is opened: resource
when resource is closed: resource

With 5.5.9:
when resource is opened: resource
when resource is closed: unknown type

Be carefull.


Bill Getas

12 years ago


Here's my little updated contribution - it prints colorful output in the way I prefer.  Define a helper function isRootIp() that contains an array including your IP; then calls to bt() simply return, so you can sprinkle backtraces in live sites w/o anyone knowing.

<?php

function bt()

{

    if( !
isRootIp() )

    {

        return
false;

    }

   
array_walk( debug_backtrace(), create_function( '$a,$b', 'print "<br /><b>". basename( $a['file'] ). "</b> &nbsp; <font color="red">{$a['line']}</font> &nbsp; <font color="green">{$a['function']} ()</font> &nbsp; -- ". dirname( $a['file'] ). "/";' ) );

}

?>


jlammertink at gmail dot com

12 years ago


I use this simple but effective function so i can see which method in the child class called the current method (in the parent class).

<?php

function get_caller_method()

{

   
$traces = debug_backtrace();

    if (isset(

$traces[2]))

    {

        return
$traces[2]['function'];

    }

    return

null;

}

?>


kroczu AT interia DOT pl

16 years ago


<?
// useful and comfortable debug function
// it's show memory usage and time flow between calls, so we can quickly find a block of code that need optimisation...
// example result:
/*
debug example.php> initialize
debug example.php> code-lines: 39-41 time: 2.0002 mem: 19 KB
debug example.php> code-lines: 41-44 time: 0.0000 mem: 19 KB
debug example.php> code-lines: 44-51 time: 0.6343 mem: 9117 KB
debug example.php> code-lines: 51-53 time: 0.1003 mem: 9117 KB
debug example.php> code-lines: 53-55 time: 0.0595 mem: 49 KB
*/

function debug()
{
   static $start_time = NULL;
   static $start_code_line = 0;

   $call_info = array_shift( debug_backtrace() );
   $code_line = $call_info['line'];
   $file = array_pop( explode('/', $call_info['file']));

   if( $start_time === NULL )
   {
       print "debug ".$file."> initializen";
       $start_time = time() + microtime();
       $start_code_line = $code_line;
       return 0;
   }

   printf("debug %s> code-lines: %d-%d time: %.4f mem: %d KBn", $file, $start_code_line, $code_line, (time() + microtime() - $start_time), ceil( memory_get_usage()/1024));
   $start_time = time() + microtime();
   $start_code_line = $code_line;
}

////////////////////////////////////////////////
// example:

debug();
sleep(2);
debug();
// soft-code...
$a = 3 + 5;
debug();

// hard-code
for( $i=0; $i<100000; $i++)
{
    $dummy['alamakota'.$i] = 'alamakota'.$i;
}
debug();
usleep(100000);
debug();
unset($dummy);
debug();

?>


nyoung55 at that_google_mail.com

10 years ago


Here is a function to cleanly output the debug_backtrace to the error_log

<?php

/*

* Send the output from a backtrace to the error_log

* @param string $message Optional message that will be sent the the error_log before the backtrace

*/

function log_trace($message = '') {

   
$trace = debug_backtrace();

    if (
$message) {

       
error_log($message);

    }

   
$caller = array_shift($trace);

   
$function_name = $caller['function'];

   
error_log(sprintf('%s: Called from %s:%s', $function_name, $caller['file'], $caller['line']));

    foreach (
$trace as $entry_id => $entry) {

       
$entry['file'] = $entry['file'] ? : '-';

       
$entry['line'] = $entry['line'] ? : '-';

        if (empty(
$entry['class'])) {

           
error_log(sprintf('%s %3s. %s() %s:%s', $function_name, $entry_id + 1, $entry['function'], $entry['file'], $entry['line']));

        } else {

           
error_log(sprintf('%s %3s. %s->%s() %s:%s', $function_name, $entry_id + 1, $entry['class'], $entry['function'], $entry['file'], $entry['line']));

        }

    }

}

?>


Anonymous

9 years ago


A usual entry looks like this:
<?php
array(6) {
 
'file' =>
 
string(87) "DbSelector.php"
  'line'
=>
 
int(171)
 
'function' =>
 
string(5) "error"
  'class'
=>
 
string(42) "LoggingService"
  'type'
=>
 
string(2) "::"
  'args'
=>
  array(
1) {
    [
0] =>
   
string(27) "Connecting to DB: unittests"
 
}
}
?>

Be warned though that 'file' and 'class' do not reference the same thing!
'file' means which file calls the next step.
'class' is the next step being called.

So 'file' is the caller, 'class' is the callee.


Gemorroj

9 years ago


Another variation formatting backtrace.

Parameter $ignore to ignore the extra calls.

<?php

/**

* Getting backtrace

*

* @param int $ignore ignore calls

*

* @return string

*/

protected function getBacktrace($ignore = 2)

{

   
$trace = '';

    foreach (
debug_backtrace() as $k => $v) {

        if (
$k < $ignore) {

            continue;

        }
array_walk($v['args'], function (&$item, $key) {

           
$item = var_export($item, true);

        });
$trace .= '#' . ($k - $ignore) . ' ' . $v['file'] . '(' . $v['line'] . '): ' . (isset($v['class']) ? $v['class'] . '->' : '') . $v['function'] . '(' . implode(', ', $v['args']) . ')' . "n";

    }

    return

$trace;

}

?>

kexianbin at diyism dot com

10 years ago


need no Xdebug or dbg.so on server, return more detailed message:

diyism_trace.php:
<?php
define
(TRACES_MODE, 'TEXTAREA');//'TEXTAREA' or 'FIREPHP'
$GLOBALS['traces.pre']=array();
function
my_array_diff($arr1, $arr2)
         {foreach (
$arr1 as $k=>$v)
                  {if (
in_array($v, $arr2, true))
                      {unset(
$arr1[$k]);
                      }
                  }
          return
$arr1;
         }
function
my_var_export($var, $is_str=false)
         {
$rtn=preg_replace(array('/Arrays+(/', '/[(d+)] => (.*)n/', '/[([^d].*)] => (.*)n/'), array('array (', '1 => '2''."n", ''1' => '2''."n"), substr(print_r($var, true), 0, -1));
         
$rtn=strtr($rtn, array("=> 'array ('"=>'=> array ('));
         
$rtn=strtr($rtn, array(")nn"=>")n"));
         
$rtn=strtr($rtn, array("'n"=>"',n", ")n"=>"),n"));
         
$rtn=preg_replace(array('/n +/e'), array('strtr('', array('    '=>'  '))'), $rtn);
         
$rtn=strtr($rtn, array(" Object',"=>" Object'<-"));
          if (
$is_str)
             {return
$rtn;
             }
          else
              {echo
$rtn;
              }
         }
function
tick_handler()
         {
$tmp=debug_backtrace();
         
$trace=my_array_diff($tmp, $GLOBALS['traces.pre']);
         
//echo '<pre>';var_export($trace);echo '</pre>';echo '<br/>'; //for debug diyism_trace.php
         
$trace=array_values($trace);
         
$GLOBALS['traces.pre']=$tmp;
          if (
count($trace)>0 && $trace[0]['file'].'/'.@$tmp[1]['function']!==@$GLOBALS['traces'][count($GLOBALS['traces'])-1]['key']) //filter empty array and rearrange array_values(), because some lines will trigger two tick events per line, for example: 1.last line is "some code;questmark>" 2.error_reporting(...
            
{for ($i=count($trace)-1; $i>=0; --$i)
                  {
$GLOBALS['traces'][]=$tmp_fb=array_merge(array('key'=>$trace[$i]['file'].'/'.@$tmp[$i+1]['function']), $trace[$i], array('function'=>strtr($trace[$i]['function'], array('tick_handler'=>'CONTINUE')), 'in_function'=>@$tmp[$i+1]['function']));
                  
TRACES_MODE==='FIREPHP'?fb(trace_output($tmp_fb), 'diyism_trace:'.++$GLOBALS['diyism_trace_no']):'';
                  }
             }
         }
function
trace_output($trace)
         {
$trace['in_function']=strtr(@$trace['in_function'], array('require'=>'', 'require_once'=>'', 'include'=>'', 'include_once'=>''));
         
$trace['args']=$trace['args']?strtr(preg_replace(array('/n +/'), array(''), preg_replace(array('/n  d+ => /'), array(''), substr(my_var_export($trace['args'], true), 7, -3))), array("r"=>'r', "n"=>'n')):'';
          return
$trace['file'].($trace['in_function']?'/'.$trace['in_function'].'()':'').'/'.$trace['line'].': '.$trace['function'].'('.$trace['args'].')';
         }
function
traces_output()
         {echo
'<textarea style="width:100%;height:300px;">';
         
$GLOBALS['traces']=array_slice($GLOBALS['traces'], 2);//remove registering tick line and requiring 'diyism_trace.php' line
         
foreach ($GLOBALS['traces'] as $k=>$trace)
                  {echo
htmlentities($k.':'.trace_output($trace)."n");
                  }
          echo
'</textarea>';
         }
register_tick_function('tick_handler');
TRACES_MODE==='TEXTAREA'?register_shutdown_function('traces_output'):'';
?>

test.php:
<?php
declare(ticks=1);
require
'diyism_trace.php';a('a', array('hello'));
1+2;
b();
function
a()
         {
$d=1;
         
b();
         
$d=2;
         }
function
b()
         {
1+1;
         }
?>


php noob

12 years ago


Surprisingly, no one has described one of the best uses of this: dumping a variable and showing the location. When debugging, especially a big and unfamiliar system, it's a pain remembering where I added those var dumps. Also, this way there is a separator between multiple dump calls.

<?phpfunction dump( $var ) {
   
$result = var_export( $var, true );
   
$loc = whereCalled();
    return
"n<pre>Dump: $locn$result</pre>";
}

function

whereCalled( $level = 1 ) {
   
$trace = debug_backtrace();
   
$file   = $trace[$level]['file'];
   
$line   = $trace[$level]['line'];
   
$object = $trace[$level]['object'];
    if (
is_object($object)) { $object = get_class($object); }

    return

"Where called: line $line of $object n(in $file)";
}
?>

In addition, calling 'whereCalled()' from any function will quickly identify locations that are doing something unexpected (e.g., updating a property at the wrong time). I'm new to PHP, but have used the equivalent in Perl for years.


php at kennel17 dot co dot uk

15 years ago


Further to my previous note, the 'object' element of the array can be used to get the parent object.  So changing the get_class_static() function to the following will make the code behave as expected:

<?php
   
function get_class_static() {
       
$bt = debug_backtrace();

            if (isset(

$bt[1]['object']))
            return
get_class($bt[1]['object']);
        else
            return
$bt[1]['class'];
    }
?>

HOWEVER, it still fails when being called statically.  Changing the last two lines of my previous example to

<?php
  foo
::printClassName();
 
bar::printClassName();
?>

...still gives the same problematic result in PHP5, but in this case the 'object' property is not set, so that technique is unavailable.


henzeberkheij at gmail dot com

12 years ago


I find it useful to know if a function is being called. in Java for instance you usually print a line with the functionname and arguments in the beginning of the function. I wanted to achieve the same thing in php thus i wrote the following class:

<?php

class Debug

{

    private static
$calls;

    public static function

log($message = null)

    {

        if(!
is_array(self::$calls))

           
self::$calls = array();
$call = debug_backtrace(false);

       
$call = (isset($call[1]))?$call[1]:$call[0];
$call['message'] = $message;

       
array_push(self::$calls, $call);

    }

}

?>



include this class before anything else

usage: Debug::log($message); at the beginning of your function.

write yourself a nice printout of the data;


john dot risken at gmail dot com

12 years ago


Everybody seems to have their favorite use.  I substitute this function for die().  It gives a message

to the user and emails me a PrettyPrint of what went wrong.  $info is set by me,

and it does a special check in the database object.

<?php

// var_format
function var_format($v) // pretty-print var_export

{

    return (
str_replace(array("n"," ","array"),

array(
"<br>","&nbsp;","&nbsp;<i>array</i>"),

var_export($v,true))."<br>");

}

function
myDie($info)

{

   
$mysqlerr=strpos($info,"ERROR=You have an error in your SQL syntax");

    if(
$mysqlerr>0)$info=substr($info,0,$mysqlerr)." mySql format error";

   
$out="<br>MSG='$info'<br>".var_format($_REQUEST)."<br>";

   
$bt=debug_backtrace();

   
$sp=0;

   
$trace="";

    foreach(
$bt as $k=>$v)

    {

       
extract($v);

       
$file=substr($file,1+strrpos($file,"/"));

        if(
$file=="db.php")continue; // the db object

       
$trace.=str_repeat("&nbsp;",++$sp); //spaces(++$sp);

       
$trace.="file=$file, line=$line, function=$function<br>";       

    }

   
$out.="<br>".backTrace();

    if(
substr($info,0,4)=="XXX ") // special errrors when db is inaccessible

   
{

       
$out=str_replace("<br>","n",$out);

       
$out=str_replace("&nbsp;"," ",$out);

       
mail("me@example.com","Database Execution Error for user ".$REMOTE_ADDR,"$out");

        exit(
"Database Access Error. Please try again later.");

    }

   
mail("me@example.com",'Error Monitor','Execution Error',$out);

    exit(
"DANG! An execution error in the program has been sent to the webmaster.

If you don't get an email from him soon, please call him."
);

}

?>



This produces an output like this

file=badmode.php, line=5, function=backTrace

  file=login.php, line=209, function=require

   file=midScreen.php, line=264, function=require

    file=masterindex.php, line=161, function=require

     file=production2.php, line=121, function=require

      file=index.php, line=16, function=require


http://synergy8.com

17 years ago


It should be noted that if an internal php function such as call_user_func in the backtrace, the 'file' and 'line' entries will not be set.

Most debug tracers will use these entries.  You should place a check to see if the key exists in the array before using this function.  Otherwise notices will be generated.

<?php

$arrTrace

= debug_backtrace();

foreach (

$arrTrace as $arr)
{
    if (!isset (
$arr['file']))
    {
       
$arr['file'] = '[PHP Kernel]';
    }

    if (!isset (

$arr['line']))
    {
       
$arr['line'] = '';
    }
// Do something
}?>


frank at frank dot com

14 years ago


Here is my simple example:
Code printing variable of class which instatiates the printing class.

Well, I am sure you understand when looking at the code:
Print result is: jippii

<?php
class A {

        function

something() {
               
$s = debug_backtrace();$callingObject = $s[1]['object'];
               
$test = $callingObject->jip;
                print
$test;
        }

}

class

B {
      var
$jip;

              function

execute() {
               
$a = new A();
               
$this->jip = "jippii"
               
$a->something();
        }

}

$control = new B();
$control->execute();
?>


samthor

14 years ago


Here's a way to get the arguments for an upstream function in your stack (works with class methods, static methods and non-class methods):
<?php
/**
* getArgs - find arguments of upstream method
* can be called with, e.g. "funcname", "class::staticmethod", "class->instancemethod".
*/
function getArgs( $target, $subclass_ok = true ) {

    if(

strpos( $target, "::" ) ) {
        list(
$class, $target ) = explode( "::", $target, 2 );
       
$type = "::";
    }
    else if(
strpos( $target, "->" ) ) {
        list(
$class, $target ) = explode( "->", $target, 2 );
       
$type = "->";
    }
    else {
       
$type = NULL;
       
$class = NULL;
    }
   
$class and $class = new ReflectionClass( $class );

    foreach(

debug_backtrace() as $obj ) {

        if(

$obj['function'] == $target ) {
            if(
$type and $obj['type'] == $type ) {
               
$_cl = new ReflectionClass( $obj['class'] );
                if(
$_cl->getName() == $class->getName() or ( $subclass_ok and $_cl->isSubclassOf( $class ) ) ) {
                    return
$obj['args'];
                }
                unset(
$_cl );
            }
            else if( !
$type ) {
                return
$obj['args'];
            }
        }

    }

    return

NULL;

}

?>

Some example usage:
<?php
class Foo {
    function
test() {
       
$args = getArgs( "Foo->base" );
        print(
"the parameter 'v' to my call of base was: {$args[0]}n" );
    }
    function
base( $v ) {
       
$this->test();
    }
}
$f = new Foo();
$f->base( 713 ); // will print.. ".. my call of base was: 713"?>

Trust me, there are some reasons for why you might want to do this :)

bernyregeling AT hotmail DOT com

19 years ago


I wrote this function, in addition to jlim, for a nice NO-HTML output.

Thee result has similarities to a Java-error. Hope you like it.

(BTW, this function exits the script too, if debug_backtrace is displayed)

------------------------------

    function debug_bt()

    {

        if(!function_exists('debug_backtrace'))

        {

            echo 'function debug_backtrace does not exists'."rn";

            return;

        }

        //echo '<pre>';

        echo "rn".'----------------'."rn";

        echo 'Debug backtrace:'."rn";

        echo '----------------'."rn";

        foreach(debug_backtrace() as $t)

        {

            echo "t" . '@ ';

            if(isset($t['file'])) echo basename($t['file']) . ':' . $t['line'];

            else

            {

                // if file was not set, I assumed the functioncall

                // was from PHP compiled source (ie XML-callbacks).

                echo '<PHP inner-code>';

            }

            echo ' -- ';

            if(isset($t['class'])) echo $t['class'] . $t['type'];

            echo $t['function'];

            if(isset($t['args']) && sizeof($t['args']) > 0) echo '(...)';

            else echo '()';

            echo "rn";

        }

        //echo '</pre>';

        exit;

         }


seaside dot ki at mac dot com

16 years ago


I've started creating an external debug server for PHP. A PHP app require_once's a TADebugger(), which communicates with the debug sever. Find the OS X universal binary here [PHP source sample included]:

   http://www.turingart.com/downloads/phpDebugger.zip

Currently, TADebugger allows to post these properties back to the debug server:

- Call backtraces
- String messages
- Source files, which were referenced by a backtrace call

Note, that the binary is a early version.


icefragment at gmail dot com

16 years ago


A simple python-like backtrace. Note that I don't recurse into arrays if they are passed as arguments to functions.

function backtrace()
{
    $bt = debug_backtrace();

        echo("<br /><br />Backtrace (most recent call last):<br /><br />n");   
    for($i = 0; $i <= count($bt) - 1; $i++)
    {
        if(!isset($bt[$i]["file"]))
            echo("[PHP core called function]<br />");
        else
            echo("File: ".$bt[$i]["file"]."<br />");

                if(isset($bt[$i]["line"]))
            echo("&nbsp;&nbsp;&nbsp;&nbsp;line ".$bt[$i]["line"]."<br />");
        echo("&nbsp;&nbsp;&nbsp;&nbsp;function called: ".$bt[$i]["function"]);

                if($bt[$i]["args"])
        {
            echo("<br />&nbsp;&nbsp;&nbsp;&nbsp;args: ");
            for($j = 0; $j <= count($bt[$i]["args"]) - 1; $j++)
            {
                if(is_array($bt[$i]["args"][$j]))
                {
                    print_r($bt[$i]["args"][$j]);
                }
                else
                    echo($bt[$i]["args"][$j]);   

                                            if($j != count($bt[$i]["args"]) - 1)
                    echo(", ");
            }
        }
        echo("<br /><br />");
    }
}


zmorris at mac dot com

16 years ago


Hi, I got tired of using a trace( $message, __FILE__, __LINE__ ) function I made.  It forced me to include the file and line params (since php doesn't have macros) so I decided to make an alternative.

Simply call this new version using trace( 'my message' ); and it prints out a stack trace in a clearer way than the one stored in the debug_backtrace() array.  It handles traces from outside of functions, traces in nested functions, and traces in included files, and also displays the function in a way that can be pasted right back into your php code for faster testing!

NOTE - be sure to save your files with the correct line endings for the line numbers to work correctly, which for Mac OS X is unix.  You can get to this option in the popup menu in the toolbar at the top of each window in BBEdit.

<?phpfunction    print_var( $var )
{
   if(
is_string( $var ) )
       return(
'"'.str_replace( array("x00", "x0a", "x0d", "x1a", "x09"), array('', 'n', 'r', 'Z', 't'), $var ).'"' );
   else if(
is_bool( $var ) )
   {
       if(
$var )
           return(
'true' );
       else
           return(
'false' );
   }
   else if(
is_array( $var ) )
   {
      
$result = 'array( ';
      
$comma = '';
       foreach(
$var as $key => $val )
       {
          
$result .= $comma.print_var( $key ).' => '.print_var( $val );
          
$comma = ', ';
       }
      
$result .= ' )';
       return(
$result );
   }

      return(

var_export( $var, true ) );    // anything else, just let php try to print it
}

function   

trace( $msg )
{
   echo
"<pre>n";//var_export( debug_backtrace() ); echo "</pre>n"; return;    // this line shows what is going on underneath$trace = array_reverse( debug_backtrace() );
  
$indent = '';
  
$func = '';

      echo

$msg."n";

      foreach(

$trace as $val)
   {
       echo
$indent.$val['file'].' on line '.$val['line'];

              if(

$func ) echo ' in function '.$func;

              if(

$val['function'] == 'include' ||
          
$val['function'] == 'require' ||
          
$val['function'] == 'include_once' ||
          
$val['function'] == 'require_once' )
          
$func = '';
       else
       {
          
$func = $val['function'].'(';

                      if( isset(

$val['args'][0] ) )
           {
              
$func .= ' ';
              
$comma = '';
               foreach(
$val['args'] as $val )
               {
                  
$func .= $comma.print_var( $val );
                  
$comma = ', ';
               }
              
$func .= ' ';
           }
$func .= ')';
       }

              echo

"n";$indent .= "t";
   }

      echo

"</pre>n";
}
trace( 'error outside function' );

function   

test( $param1, $param2, $param3, $param4 )
{
  
trace( 'error in test()' );
}
test( 1.1, "param2n", array( 1 => "an", "bn" => 2 ), false );?>


admin at sgssweb dot com

16 years ago


Surprizingly, debug_backtrace() cannot aquire arguments from the function that is used as the second or later argument of a function.

<?phpfunction a($p) {
   
$backtrace = debug_backtrace();

        if (isset(

$backtrace[0]['args']))
       
var_export($backtrace[0]['args']);
    else
        echo
"Cannot aquire arguments";
    echo
"<br />";

        return

$p;
}

function

b($p1, $p2, $p3) {
    echo
"$p1, $p2, $p3";
}
// This outputs:
//    array ( 0 => 'First a', )
//    Cannot aquire arguments
//    Cannot aquire arguments
//    First a, Second a, Third a
b(a("First a"), a("Second a"), a("Third a"));?>


jlim#natsoft.com.my

19 years ago


Pretty print the backtrace(). Functions are indented based on call value, and file is linked using file:// for convenience.

Enjoy, John Lim

    function adodb_backtrace($print=true)
    {
        $s = '';
        if (PHPVERSION() >= 4.3) {

                    $MAXSTRLEN = 64;

                    $s = '<pre align=left>';
            $traceArr = debug_backtrace();
            array_shift($traceArr);
            $tabs = sizeof($traceArr)-1;
            foreach ($traceArr as $arr) {
                for ($i=0; $i < $tabs; $i++) $s .= ' &nbsp; ';
                $tabs -= 1;
                $s .= '<font face="Courier New,Courier">';
                if (isset($arr['class'])) $s .= $arr['class'].'.';
                foreach($arr['args'] as $v) {
                    if (is_null($v)) $args[] = 'null';
                    else if (is_array($v)) $args[] = 'Array['.sizeof($v).']';
                    else if (is_object($v)) $args[] = 'Object:'.get_class($v);
                    else if (is_bool($v)) $args[] = $v ? 'true' : 'false';
                    else {
                        $v = (string) @$v;
                        $str = htmlspecialchars(substr($v,0,$MAXSTRLEN));
                        if (strlen($v) > $MAXSTRLEN) $str .= '...';
                        $args[] = $str;
                    }
                }

                                $s .= $arr['function'].'('.implode(', ',$args).')';
                $s .= sprintf("</font><font color=#808080 size=-1> # line %4d,".
  " file: <a href="file:/%s">%s</a></font>",
  $arr['line'],$arr['file'],$arr['file']);
                $s .= "n";
            }   
            $s .= '</pre>';
            if ($print) print $s;
        }
        return $s;
    }


spagmoid at yahoo dot NOSPAMcom

19 years ago


ATTN: jlim#natsoft.com.my

Great function, but you have a few bugs.

At the line:
foreach($arr['args'] as $v)

Change it to:
$args = array();
if(!empty($arr['args'])) foreach($arr['args'] as $v)

And since line & file are not present in the array if calling from the error handler,

$Line = (isset($arr['line'])? $arr['line'] : "unknown");
$File = (isset($arr['file'])? $arr['file'] : "unknown");

and substitute accordingly.

Here's my version of it, alas with different formatting:
----------------------------------------

function DBG_GetBacktrace()
{
    $s = '';
    $MAXSTRLEN = 64;

        $s = '<pre align=left>';
    $traceArr = debug_backtrace();
    array_shift($traceArr);
    $tabs = sizeof($traceArr)-1;
    foreach($traceArr as $arr)
    {
        for ($i=0; $i < $tabs; $i++) $s .= ' &nbsp; ';
        $tabs -= 1;
        $s .= '<font face="Courier New,Courier">';
        if (isset($arr['class'])) $s .= $arr['class'].'.';
        $args = array();
        if(!empty($arr['args'])) foreach($arr['args'] as $v)
        {
            if (is_null($v)) $args[] = 'null';
            else if (is_array($v)) $args[] = 'Array['.sizeof($v).']';
            else if (is_object($v)) $args[] = 'Object:'.get_class($v);
            else if (is_bool($v)) $args[] = $v ? 'true' : 'false';
            else
            {
                $v = (string) @$v;
                $str = htmlspecialchars(substr($v,0,$MAXSTRLEN));
                if (strlen($v) > $MAXSTRLEN) $str .= '...';
                $args[] = """.$str.""";
            }
        }
        $s .= $arr['function'].'('.implode(', ',$args).')</font>';
        $Line = (isset($arr['line'])? $arr['line'] : "unknown");
        $File = (isset($arr['file'])? $arr['file'] : "unknown");
        $s .= sprintf("<font color=#808080 size=-1> # line %4d, file: <a href="file:/%s">%s</a></font>",
            $Line, $File, $File);
        $s .= "n";
    }   
    $s .= '</pre>';
    return $s;
}


ciprian dot stingu at gmail dot com

11 years ago


A function that i use for debug
I shortened variables name and i eliminated the spaces from second function in order fit in post :(

<?php
define
("LFP", './lt.log');
function
LogTrace($Argument, $lfn = LFP, $itw = '  ')
{
   
error_log("=====r", 3, $lfn);
   
error_log("[BEGIN BACKTRACE]r", 3, $lfn);
   
$it = '';
   
$Ts = array_reverse(debug_backtrace());
    foreach(
$Ts as $T)
       { 
        if(
$T['function'] != 'include' && $T['function'] != 'require' && $T['function'] != 'include_once' && $T['function'] != 'require_once')
        {
           
$ft = $it . '<'. basename($T['file']) . '> on line ' . $T['line']; 
            if(
$T['function'] != 'LogTrace')
            {
                if(isset(
$T['class']))
                   
$ft .= ' in method ' . $T['class'] . $T['type'];
                else
                   
$ft .= ' in function ';
               
$ft .= $Trace['function'] . '(';
            }
            else
               
$ft .= '(';
            if(isset(
$T['args'][0]))
            {
                if(
$T['function'] != 'LogTrace')
                {
                   
$ct = '';
                    foreach(
$T['args'] as $A)
                    {
                       
$ft .= $ct . LogVar($A, '', $it, $itw, 0);
                       
$ct = $it . $itw . ',';
                    }
                }
                else
                   
$ft .= LogVar($T['args'][0], '', $it, $itw, 0);
            }
           
$ft .= $it . ")r";
           
error_log($ft, 3, $lfn);
           
$it .= $itw;
        }           
    }
   
error_log("[END BACKTRACE]r", 3, $lfn);
}

function

LogVar(&$Var, $vn, $pit, $itw, $nlvl, $m = '')
{
    if(
$nlvl>=16) return;
    if(
$nlvl==0){$tv=serialize($Var);$tv=unserialize($tv);}
    else
$tv=&$Var;
   
$it=$pit.$itw;
    for(
$i=0; $i<$nlvl;$i++) $it.='.'.$itw;
   
$o='';$nl="n";
    if(
is_array($tv))
    {
        if(
strlen($vn)>0) $o.=$it.$m.'<array> $'.$vn.' = (';
        else
$o.="r".$it.$m.'<array> = (';
       
$o.= $nl;$AK=array_keys($tv);
        foreach(
$AK as $AN) {$AV=&$tv[$AN];$o.=LogVar($AV,$AN,$pit,$itw,$nlvl+1);}
       
$o.=$it.')'.$nl;
    }
    else if(
is_string($tv))
    {
        if(
strlen($vn)>0)$o.=$it.$m.'<string> $'.$vn.' = ';
        else
$o.=' '.$m.'<string> = ';
        if(
$tv===null) $o.='NULL';
        else
$o.='"'.$tv.'"';
       
$o.=$nl;
    }
    else if(
is_bool($tv))
    {
        if(
strlen($vn) > 0) $o.=$it.$m.'<boolean> $'.$vn.' = ';
        else
$o.=' '.$m.'<boolean> = ';
        if(
$tv===true) $o.='TRUE';
        else
$o.='FALSE';
       
$o.=$nl;
    }
    else if(
is_object($tv))
    {
        if(
strlen($vn)>0)
        {
           
$o.=$pit.$itw;
            for(
$i=0;$i<$nlvl;$i++) $o.='.'.$itw;
           
$o.=$m.'<'.get_class($tv).'::$'.$vn.'> = {'.$nl;
        }
        else
$o.=' '.$m.'<'.get_class($tv).'::> = {'.$nl;
       
$R=new ReflectionClass($tv);
       
$o.=$it.'.'.$itw.'Class methods {'.$nl;
       
$CM=$R->getMethods();
        foreach(
$CM as $MN => $MV)
        {
           
$o.=$it.'.'.$itw.'.'.$itw.implode(' ',Reflection::getModifierNames($MV->getModifiers())).' '.$MV->getName().'(';
           
$MP=$MV->getParameters(); $ct='';
            foreach(
$MP as $MPN => $MPV)
            {
               
$o.=$ct; $o.=$MPV->isOptional()?'[':'';
                if(
$MPV->isArray()) $o.='<array> ';
                else if(
$MPV->getClass()!==null) $o.='<'.$MPV->getClass()->getName().'::> ';
               
$o.=$MPV->isPassedByReference()?'&':''; $o.='$'.$MPV->getName();
                if(
$MPV->isDefaultValueAvailable())
                 {
                    if(
$MPV->getDefaultValue()===null) $o.=' = NULL';
                    else if(
$MPV->getDefaultValue()===true) $o.=' = TRUE';
                    else if(
$MPV->getDefaultValue()===false) $o.=' = FALSE';   
                    else
$o.=' = '.$MPV->getDefaultValue();   
                }
               
$o.=$MPV->isOptional()?']':''; $ct=', ';
            }
           
$o.=')'.$nl;
        }
       
$o.=$it.'.'.$itw.'}'.$nl; $o.=$it.'.'.$itw.'Class properties {'.$nl;
       
$CV=$R->getProperties();
        foreach(
$CV as $CN => $CV)
        {
           
$M=implode(' ',Reflection::getModifierNames($CV->getModifiers())).' ';
           
$CV->setAccessible(true);
           
$o.=LogVar($CV->getValue($tv),$CV->getName(),$pit,$itw,$nlvl+2,$M);
        }
       
$o.=$it.'.'.$itw.'}'.$nl; $o.=$it.'.'.$itw.'Object variables {'.$nl;
        
$OVs=get_object_vars($tv);   
        foreach(
$OVs as $ON => $OV) $o.=LogVar($OV,$ON,$pit,$itw,$nlvl+2);
       
$o.=$it.'.'.$itw.'}'.$nl; $o.=$pit.$itw;
        for(
$i=0;$i<$nlvl;$i++)    $o.='.'.$itw;
       
$o.='}'.$nl;
    }
    else
    {
        if(
strlen($vn)>0) $o.=$it.$m.'<'.gettype($tv).'> $'.$vn.' = '.$tv;
        else
$o.=' '.$m.'<'.gettype($tv).'> = '.$tv;
       
$o.=$nl;
    }         
    return
$o;   
}
//test
date_default_timezone_set('Europe/Bucharest');
$date = new DateTime('2010-01-28');
LogTrace($date);
?>


aryel at iku dot com dot br

15 years ago


An easy function to pull all details of the debug backtrace:

<?php
function getDebugBacktrace($NL = "<BR>") {
   
$dbgTrace = debug_backtrace();
   
$dbgMsg .= $NL."Debug backtrace begin:$NL";
    foreach(
$dbgTrace as $dbgIndex => $dbgInfo) {
       
$dbgMsg .= "t at $dbgIndex  ".$dbgInfo['file']." (line {$dbgInfo['line']}) -> {$dbgInfo['function']}(".join(",",$dbgInfo['args'])")$NL";
    }
   
$dbgMsg .= "Debug backtrace end".$NL;
    return
$dbgMsg;
}
?>

Then you can call it anywhere you want to get a string with the debug backtrace in readable format (i.e. your error handling function)

<?php
$backtrace
= getDebugBacktrace();
echo
"Fatal error! Cannot connect to database!";
echo
$backtrace;
?>

If you're running on command line, you might want to replace the line split. You can do that thru the function argument:

<?php
$backtrace
= getDebugBacktrace("n");
echo
"Error! Server is running out of foos! Dumping error backtrace";
echo
$backtrace;
?>

Hope that helps,
Aryel


PHP Error

This is better error reporting for PHP written in PHP. No extra extensions are required!

It is trivial to use where all errors are displayed in the browser for normal, AJAXy requests (in paused state). Then all errors provides you with a backtrace and code context across the whole stack trace, including function arguments, server variables.

All you need to do is to include one single file and call the function (at the beginning on your code), e.g.

require('php_error.php');
php_errorreportErrors();

See the screenshots:

PHP Error | Improve Error Reporting for PHP - screenshot of backtrace
PHP Error | Improve Error Reporting for PHP - screenshot of backtrace
PHP Error | Improve Error Reporting for PHP - screenshot of backtrace

GitHub: https://github.com/JosephLenton/PHP-Error

My fork (with extra fixes): https://github.com/kenorb-contrib/PHP-Error

Debug PHP class

A complete PHP debugger class, with support for Exception, Errors, Alerts ( from user), code lines and highlight flags.

Example usage:

 <?php
        include( dirname(dirname(__FILE__))  . '/src/Debug.php' );
        //Catch all
        Debug::register();

        //Generate an errors
        if( this_function_does_not_exists( ) )
        {
            return false;
        }
    ?>

Error Handling in PHP

The example below shows the handling of internal exceptions by triggering errors and handling them with a user defined function:

Shorter way (PHP):

<?php
function e($number, $msg, $file, $line, $vars) {
   print_r(debug_backtrace());
   die();
}
set_error_handler('e');

Longer way (PHP):

// set to the user defined error handler
$old_error_handler = set_error_handler("myErrorHandler");

// error handler function
function myErrorHandler($errno, $errstr, $errfile, $errline)
{
    if (!(error_reporting() & $errno)) {
        // This error code is not included in error_reporting
        return;
    }

    switch ($errno) {
    case E_USER_ERROR:
        echo "<b>My ERROR</b> [$errno] $errstr<br />n";
        echo "  Fatal error on line $errline in file $errfile";
        echo ", PHP " . PHP_VERSION . " (" . PHP_OS . ")<br />n";
        echo "Aborting...<br />n";
        var_dump(debug_backtrace());
        exit(1);
        break;

    case E_USER_WARNING:
        echo "<b>My WARNING</b> [$errno] $errstr<br />n";
        break;

    case E_USER_NOTICE:
        echo "<b>My NOTICE</b> [$errno] $errstr<br />n";
        break;

    default:
        echo "Unknown error type: [$errno] $errstr<br />n";
        break;
    }

    /* Don't execute PHP internal error handler */
    return true;
}

See: http://www.php.net/manual/en/function.set-error-handler.php

Note: You can only have one error exception at a time. When you call the set_error_handler() function it will return the name of the old error handler. You can store this and call it yourself from your error handler – thus allowing you to have multiple error handlers.


XDebug

For more advanced solution, you can use XDebug extension for PHP.

By default when XDebug is loaded, it should show you automatically the backtrace in case of any fatal error. Or you trace into file (xdebug.auto_trace) to have a very big backtrace of the whole request or do the profiling (xdebug.profiler_enable) or other settings. If the trace file is too big, you can use xdebug_start_trace() and xdebug_stop_trace() to dump the partial trace.

Installation

Using PECL:

pecl install xdebug

On Linux:

sudo apt-get install php5-xdebug

On Mac (with Homebrew):

brew tap josegonzalez/php
brew search xdebug
php53-xdebug

Example of mine configuration:

[xdebug]

; Extensions
extension=xdebug.so
; zend_extension="/YOUR_PATH/php/extensions/no-debug-non-zts-20090626/xdebug.so"
; zend_extension="/Applications/MAMP/bin/php/php5.3.20/lib/php/extensions/no-debug-non-zts-20090626/xdebug.so" ; MAMP

; Data
xdebug.show_exception_trace=1       ; bool: Show a stack trace whenever an exception is raised.
xdebug.collect_vars = 1             ; bool: Gather information about which variables are used in a certain scope.
xdebug.show_local_vars=1            ; int: Generate stack dumps in error situations.
xdebug.collect_assignments=1        ; bool: Controls whether Xdebug should add variable assignments to function traces.
xdebug.collect_params=4             ; int1-4: Collect the parameters passed to functions when a function call is recorded.
xdebug.collect_return=1             ; bool: Write the return value of function calls to the trace files.
xdebug.var_display_max_children=256 ; int: Amount of array children and object's properties are shown.
xdebug.var_display_max_data=1024    ; int: Max string length that is shown when variables are displayed.
xdebug.var_display_max_depth=3      ; int: How many nested levels of array/object elements are displayed.
xdebug.show_mem_delta=0             ; int: Show the difference in memory usage between function calls.

; Trace
xdebug.auto_trace=0                 ; bool: The tracing of function calls will be enabled just before the script is run.
xdebug.trace_output_dir="/var/log/xdebug" ; string: Directory where the tracing files will be written to.
xdebug.trace_output_name="%H%R-%s-%t"     ; string: Name of the file that is used to dump traces into.

; Profiler
xdebug.profiler_enable=0            ; bool: Profiler which creates files read by KCacheGrind.
xdebug.profiler_output_dir="/var/log/xdebug"  ; string: Directory where the profiler output will be written to.
xdebug.profiler_output_name="%H%R-%s-%t"      ; string: Name of the file that is used to dump traces into.
xdebug.profiler_append=0            ; bool: Files will not be overwritten when a new request would map to the same file.

; CLI
xdebug.cli_color=1                  ; bool: Color var_dumps and stack traces output when in CLI mode.

; Remote debugging
xdebug.remote_enable=off            ; bool: Try to contact a debug client which is listening on the host and port.
xdebug.remote_autostart=off         ; bool: Start a remote debugging session even GET/POST/COOKIE variable is not present.
xdebug.remote_handler=dbgp          ; select: php3/gdb/dbgp: The DBGp protocol is the only supported protocol.
xdebug.remote_host=localhost        ; string: Host/ip where the debug client is running.
xdebug.remote_port=9000             ; integer: The port to which Xdebug tries to connect on the remote host.
xdebug.remote_mode=req              ; select(req,jit): Selects when a debug connection is initiated.
xdebug.idekey="xdebug-cli"          ; string: IDE Key Xdebug which should pass on to the DBGp debugger handler.
xdebug.remote_log="/var/log/xdebug.log" ; string: Filename to a file to which all remote debugger communications are logged.

Drupal 6&7

With Devel enabled:

/**
 * Implements hook_watchdog().
 */
function foo_watchdog($log_entry) {
  if ($log_entry['type'] == 'php' && $log_entry['severity'] <= WATCHDOG_WARNING) {
    function_exists('dd') && dd(debug_backtrace());
  }
}

Above function will log the backtraces on each error into temporary file (/tmp/drupal_debug.txt by default).

Or locate the file via: drush eval "echo file_directory_temp() . '/drupal_debug.txt'.

Without Devel enabled, use old school approach: var_dump(debug_backtrace()); instead of dd().


This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters

Show hidden characters

<?php
function shutdown() {
$error = error_get_last();
if ($error[‘type’] === E_ERROR) {
// fatal error has occured
$trace = array_reverse($GLOBALS[‘dbg_stack’]);
array_pop($trace);
if(php_sapi_name() == ‘cli’) {
echo ‘Backtrace for: » . $error[‘message’] . » at ‘ . $error[‘file’] . ‘:’ . $error[‘line’] . ‘:’ . «n»;
foreach($trace as $item)
echo ‘ ‘ . (isset($item[‘file’]) ? $item[‘file’] : ‘<unknown file>’) . ‘:’ . (isset($item[‘line’]) ? $item[‘line’] : ‘<unknown line>’) . ‘ calling ‘ . $item[‘function’] . ‘()’ . «n»;
} else {
echo ‘<p class=»error_backtrace»>’ . «n»;
echo ‘ Backtrace for: » . $error[‘message’] . » at ‘ . $error[‘file’] . ‘:’ . $error[‘line’] . ‘:’ . «n»;
echo ‘ <ol>’ . «n»;
foreach($trace as $item)
echo ‘ <li>’ . (isset($item[‘file’]) ? $item[‘file’] : ‘<unknown file>’) . ‘:’ . (isset($item[‘line’]) ? $item[‘line’] : ‘<unknown line>’) . ‘ calling ‘ . $item[‘function’] . ‘()</li>’ . «n»;
echo ‘ </ol>’ . «n»;
echo ‘</p>’ . «n»;
}
}
}
register_shutdown_function(‘shutdown’);
function write_dbg_stack() {
$GLOBALS[‘dbg_stack’] = debug_backtrace();
}
register_tick_function(‘write_dbg_stack’);
declare(ticks=1);

Содержание

  1. Exception::getTrace
  2. Описание
  3. Список параметров
  4. Возвращаемые значения
  5. Примеры
  6. Смотрите также
  7. User Contributed Notes 5 notes
  8. Функции обработки ошибок
  9. Содержание
  10. User Contributed Notes 9 notes
  11. debug_backtrace
  12. Description
  13. Parameters
  14. Return Values
  15. Examples
  16. See Also
  17. User Contributed Notes 36 notes

Exception::getTrace

(PHP 5, PHP 7, PHP 8)

Exception::getTrace — Получает трассировку стека

Описание

Возвращает трассировку стека исключения.

Список параметров

У этой функции нет параметров.

Возвращаемые значения

Возвращает трассировку стека исключения в виде массива ( array ).

Примеры

Пример #1 Пример использования Exception::getTrace()

function test () <
throw new Exception ;
>

try <
test ();
> catch( Exception $e ) <
var_dump ( $e -> getTrace ());
>
?>

Результатом выполнения данного примера будет что-то подобное:

Смотрите также

User Contributed Notes 5 notes

Two important points about this function which are not documented:

1) The trace does not include the file / line at which the exception is thrown; that entry is only recorded in the top-level getFile/Line methods.

2) Elements are returned in ‘closest-first’ order, e.g. if you have a script x which calls function y which calls function z which throws an exception, then the first trace element will be ‘Y’ and the second will be ‘X’.

If you are wanting to see the args within a stack trace on PHP 7.4, note that there is now a zend flag in the php.ini file that is default set to Off.

Set this flag to On and it will show the args again.

The order of the trace starts at the source of the exception and does not include main.
So for example:

function Bar () <
throw new Exception ;
>

try <
Foo ();
> catch( Exception $e ) <
var_dump ( $e -> getTrace ());
>
?>

Will output:

When calling getTrace(), there is also the name of the class in returned array:

throw new Exception ( ‘FATAL ERROR: bla bla. ‘ );

> catch( Exception $e ) <

var_dump ( $e -> getTrace ());

>
?>

Will show something like:

array(1) <
[0]=> array(6) <
[«file»]=> string(54) «/. /test.php»
[«line»]=> int(37)
[«function»]=> string(11) «__construct»
[«class»]=> string(4) «Test»
[«type»]=> string(2) «->»
[«args»]=> array(0) < >
>
>

You can use this function to format a exception:

function MakePrettyException ( Exception $e ) <
$trace = $e -> getTrace ();

$result = ‘Exception: «‘ ;
$result .= $e -> getMessage ();
$result .= ‘» @ ‘ ;
if( $trace [ 0 ][ ‘class’ ] != » ) <
$result .= $trace [ 0 ][ ‘class’ ];
$result .= ‘->’ ;
>
$result .= $trace [ 0 ][ ‘function’ ];
$result .= ‘();
‘ ;

> catch( Exception $e ) <

echo MakePrettyException ( $e );

Exception: «FATAL ERROR: bla bla. » @ Test->__construct();

Источник

Функции обработки ошибок

Смотрите также функцию syslog() .

Содержание

  • debug_backtrace — Выводит стек вызовов функций в массив
  • debug_print_backtrace — Выводит стек вызовов функций
  • error_clear_last — Очистить самую последнюю ошибку
  • error_get_last — Получение информации о последней произошедшей ошибке
  • error_log — Отправляет сообщение об ошибке заданному обработчику ошибок
  • error_reporting — Задаёт, какие ошибки PHP попадут в отчёт
  • restore_error_handler — Восстанавливает предыдущий обработчик ошибок
  • restore_exception_handler — Восстанавливает предыдущий обработчик исключений
  • set_error_handler — Задаёт пользовательский обработчик ошибок
  • set_exception_handler — Задаёт пользовательский обработчик исключений
  • trigger_error — Вызывает пользовательскую ошибку/предупреждение/уведомление
  • user_error — Псевдоним trigger_error

User Contributed Notes 9 notes

I keep seeing qualification lists for error types/error-nums as arrays; In user notes and in the manual itself. For example, in this manual entry’s example, when trying to seperate behavior for the variable trace in the error report:

// set of errors for which a var trace will be saved
$user_errors = array( E_USER_ERROR , E_USER_WARNING , E_USER_NOTICE );

if ( in_array ( $errno , $user_errors )) <
//. whatever
>

//. ?>

I was under the impression that PHP error code values where bitwise flag values. Wouldn’t bitwise masking be better? So I propose a slightly better way:
//.

$user_errors = E_USER_ERROR | E_USER_WARNING | E_USER_NOTICE ;

if ( $errno & $user_errors ) <
//. whatever
>

//. ?>
Or for those of you who don’t like the idea of using an integer as the condition in an if statement:

if (( $errno & $user_errors ) > 0 ) <
//. whatever
>
?>

I think that’s much more efficient than using _yet another_ array() constuct and an in_array().

If I am wrong, and the E_* constants aren’t supposed to be used in this fashion (ie, the constans aren’t guaranteed to be bitwise, which would be odd since that’s how they’re setup in the php.ini file), then delete me. I just don’t see why one should be using arrays when bitwise comparisons will work, considering the bitwise method should be MUCH more efficient.

Although the root user writes to the files ‘error_log’ and ‘access_log’, the Apache user has to own the file referenced by ‘error_log = filename’ or no log entries will be written.

; From php.ini
; Log errors to specified file.
error_log = /usr/local/apache/logs/php.errors

[root@www logs]$ ls -l /usr/local/apache/logs/php.errors
-rw-r—r— 1 nobody root 27K Jan 27 16:58 php.errors

PHP5 only (only tested with php5.0).

If you, for some reason, prefer exceptions over errors and have your custom error handler (set_error_handler) wrap the error into an exception you have to be careful with your script.

Because if you, instead of just calling the exception handler, throws the exception, and having a custom exception handler (set_exception_handler). And an error is being triggered inside that exception handler, you will get a weird error:
«Fatal error: Exception thrown without a stack frame in Unknown on line 0»

This error is not particulary informative, is it? 🙂

This example below will cause this error.
class PHPErrorException extends Exception
<
private $context = null ;
public function __construct
( $code , $message , $file , $line , $context = null )
<
parent :: __construct ( $message , $code );
$this -> file = $file ;
$this -> line = $line ;
$this -> context = $context ;
>
>;

function error_handler ( $code , $message , $file , $line ) <
throw new PHPErrorException ( $code , $message , $file , $line );
>

function exception_handler ( Exception $e )
<
$errors = array(
E_USER_ERROR => «User Error» ,
E_USER_WARNING => «User Warning» ,
E_USER_NOTICE => «User Notice» ,
);

echo $errors [ $e -> getCode ()]. ‘: ‘ . $e -> getMessage (). ‘ in ‘ . $e -> getFile ().
‘ on line ‘ . $e -> getLine (). «n» ;
echo $e -> getTraceAsString ();
>

set_error_handler ( ‘error_handler’ );
set_exception_handler ( ‘exception_handler’ );

// Throw exception with an /unkown/ error code.
throw new Exception ( ‘foo’ , 0 );
?>

There are however, easy fix for this as it’s only cause is sloppy code.
Like one, directly call exception_handler from error_handler instead of throwing an exception. Not only does it remedy this problem, but it’s also faster. Though this will cause a `regular` unhandled exception being printed and if only «designed» error messages are intended, this is not the ultimate solution.

So, what is there to do? Make sure the code in exception_handlers doesn’t cause any errors! In this case a simple isset() would have solved it.

If you are using PHP as an Apache module, your default behavior may be to write PHP error messages to Apache’s error log. This is because the error_log .ini directive may be set equal to «error_log» which is also the name of Apache’s error log. I think this is intentional.

However, you can separate Apache errors from PHP errors if you wish by simply setting a different value for error_log. I write mine in the /var/log folder.

When configuring your error log file in php.ini, you can use an absolute path or a relative path. A relative path will be resolved based on the location of the generating script, and you’ll get a log file in each directory you have scripts in. If you want all your error messages to go to the same file, use an absolute path to the file.

In some application development methodologies, there is the concept of an application root directory, indicated by «/» (even on Windows). However, PHP does not seem to have this concept, and using a «/» as the initial character in a log file path produces weird behavior on Windows.

If you are running on Windows and have set, in php.ini:

You will get some, but not all, error messages. The file will appear at

and contain internally generated error messages, making it appear that error logging is working. However, log messages requested by error_log() do NOT appear here, or anywhere else, making it appear that the code containing them did not get processed.

Apparently on Windows the internally generated errors will interpret «/» as «C:» (or possibly a different drive if you have Windows installed elsewhere — I haven’t tested this). However, the error_log process apparently can’t find «/» — understandably enough — and the message is dropped silently.

It is totally possible to use debug_backtrace() inside an error handling function. Here, take a look:

function errorHandler ( $errno , $errstr , $errfile , $errline , $errcontext )
<
echo ‘Into ‘ . __FUNCTION__ . ‘() at line ‘ . __LINE__ .
«nn—ERRNO—n» . print_r ( $errno , true ).
«nn—ERRSTR—n» . print_r ( $errstr , true ).
«nn—ERRFILE—n» . print_r ( $errfile , true ).
«nn—ERRLINE—n» . print_r ( $errline , true ).
«nn—ERRCONTEXT—n» . print_r ( $errcontext , true ).
«nnBacktrace of errorHandler()n» .
print_r ( debug_backtrace (), true );
>

function a ( )
<
//echo «a()’s backtracen».print_r( debug_backtrace(), true);
asdfasdf ; // oops
>

function b ()
<
//echo «b()’s backtracen».print_r( debug_backtrace(), true);
a ();
>

Into errorhandler() at line 9

—ERRSTR—
Use of undefined constant asdfasdf — assumed ‘asdfasdf’

Backtrace of errorHandler()
Array
(
[0] => Array
(
[function] => errorhandler
[args] => Array
(
[0] => 8
[1] => Use of undefined constant asdfasdf — assumed ‘asdfasdf’
[2] => /home/theotek/test-1.php
[3] => 23
[4] => Array
(
)

[1] => Array
(
[file] => /home/theotek/test-1.php
[line] => 23
[function] => a
)

[2] => Array
(
[file] => /home/theotek/test-1.php
[line] => 30
[function] => a
[args] => Array
(
)

[3] => Array
(
[file] => /home/theotek/test-1.php
[line] => 33
[function] => b
[args] => Array
(
)

So, the first member of the backtrace’s array is not really surprising, except from the missing «file» and «line» members.

The second member of the backtrace seem the be a hook inside the zend engine that is used to trigger the error.

Источник

debug_backtrace

(PHP 4 >= 4.3.0, PHP 5, PHP 7, PHP 8)

debug_backtrace — Generates a backtrace

Description

debug_backtrace() generates a PHP backtrace.

Parameters

This parameter is a bitmask for the following options:

debug_backtrace() options

DEBUG_BACKTRACE_PROVIDE_OBJECT Whether or not to populate the «object» index.
DEBUG_BACKTRACE_IGNORE_ARGS Whether or not to omit the «args» index, and thus all the function/method arguments, to save memory.

This parameter can be used to limit the number of stack frames returned. By default ( limit = 0 ) it returns all stack frames.

Return Values

Returns an array of associative array s. The possible returned elements are as follows:

Possible returned elements from debug_backtrace()

Name Type Description
function string The current function name. See also __FUNCTION__.
line int The current line number. See also __LINE__.
file string The current file name. See also __FILE__.
class string The current class name. See also __CLASS__
object object The current object.
type string The current call type. If a method call, «->» is returned. If a static method call, «::» is returned. If a function call, nothing is returned.
args array If inside a function, this lists the functions arguments. If inside an included file, this lists the included file name(s).

Examples

Example #1 debug_backtrace() example

function a_test ( $str )
<
echo «nHi: $str » ;
var_dump ( debug_backtrace ());
>

a_test ( ‘friend’ );
?>

// filename: /tmp/b.php
include_once ‘/tmp/a.php’ ;
?>

Results similar to the following when executing /tmp/b.php :

See Also

  • trigger_error() — Generates a user-level error/warning/notice message
  • debug_print_backtrace() — Prints a backtrace

User Contributed Notes 36 notes

Here’s a function I just wrote for getting a nice and comprehensible call trace. It is probably more resource-intensive than some other alternatives but it is short, understandable, and gives nice output (Exception->getTraceAsString()).

function generateCallTrace ()
<
$e = new Exception ();
$trace = explode ( «n» , $e -> getTraceAsString ());
// reverse array to make steps line up chronologically
$trace = array_reverse ( $trace );
array_shift ( $trace ); // remove


array_pop ( $trace ); // remove call to this method
$length = count ( $trace );
$result = array();

for ( $i = 0 ; $i $length ; $i ++)
<
$result [] = ( $i + 1 ) . ‘)’ . substr ( $trace [ $i ], strpos ( $trace [ $i ], ‘ ‘ )); // replace ‘#someNum’ with ‘$i)’, set the right ordering
>

return «t» . implode ( «nt» , $result );
>
?>

Example output:
1) /var/www/test/test.php(15): SomeClass->__construct()
2) /var/www/test/SomeClass.class.php(36): SomeClass->callSomething()

Simple function to get a string in form «filename: [class->][function(): ]»

function get_caller_info () <
$c = » ;
$file = » ;
$func = » ;
$class = » ;
$trace = debug_backtrace ();
if (isset( $trace [ 2 ])) <
$file = $trace [ 1 ][ ‘file’ ];
$func = $trace [ 2 ][ ‘function’ ];
if (( substr ( $func , 0 , 7 ) == ‘include’ ) || ( substr ( $func , 0 , 7 ) == ‘require’ )) <
$func = » ;
>
> else if (isset( $trace [ 1 ])) <
$file = $trace [ 1 ][ ‘file’ ];
$func = » ;
>
if (isset( $trace [ 3 ][ ‘class’ ])) <
$class = $trace [ 3 ][ ‘class’ ];
$func = $trace [ 3 ][ ‘function’ ];
$file = $trace [ 2 ][ ‘file’ ];
> else if (isset( $trace [ 2 ][ ‘class’ ])) <
$class = $trace [ 2 ][ ‘class’ ];
$func = $trace [ 2 ][ ‘function’ ];
$file = $trace [ 1 ][ ‘file’ ];
>
if ( $file != » ) $file = basename ( $file );
$c = $file . «: » ;
$c .= ( $class != » ) ? «:» . $class . «->» : «» ;
$c .= ( $func != » ) ? $func . «(): » : «» ;
return( $c );
>
?>

Usage like:

function debug ( $str ) <
echo get_caller_info () . $str . «
n» ;
>
?>

get_caller_info() will return info about the function /class->method that called debug().

Just a short note on debug_backtrace options for PHP 5.3.6 or newer:

debug_backtrace() — show all options
debug_backtrace(0) — exlude [«object»]
debug_backtrace(1) — same as debug_backtrace()
debug_backtrace(2) — exlude [«object»] AND [«args»]

use this example and try calling debug_backtrace with different options

function F1 ()
<
echo «
» ;
echo «in F1 now» ;
echo «» ;
>

class DebugOptionsTest
<
function F2 ()
<
echo «
» ;
echo «in F2 now» ;
F1 ();
>

echo » calling F1″ ;
F1 ();

$c =new DebugOptionsTest ();
echo » calling F2″ ;
$c -> F2 ( «testValue» );

Howdy guys, just a note really — The [‘args’] data within the resulting array is supplied by reference. I found myself editing the reference unknowingly which in turn shows its ugly head further down the line if you call multiple backtrace.

// LOOP BACKTRACE
$la = 0 ;
$lb = count ( $trace );
while ( $la $lb )<

// DATA FROM BACKTRACE
$trace [ $la ][ ‘file’ ];
$trace [ $la ][ ‘line’ ];
$trace [ $la ][ ‘args’ ];
$trace [ $la ][ ‘function’ ];
// DATA FROM BACKTRACE

// LOOP ARGUMENTS ARRAY
$ba = 0 ;
$bb = count ( $trace [ $la ][ ‘args’ ]);
while ( $ba $bb )<

$trace [ $la ][ ‘args’ ][ $ba ] = «EDITING A REFERENCE/POINTER» ;

$ba ++;
>
unset( $bb );
unset( $ba );
// LOOP ARGUMENTS ARRAY

$la ++;
>
unset( $lb );
unset( $la );
// LOOP BACKTRACE
?>

Be carefull if you are using objects as arguments for function calls!

function myPrint ( $trace ) <
foreach( $trace as $i => $call ) <
/**
* THIS IS NEEDED! If all your objects have a __toString function it’s not needed!
*
* Catchable fatal error: Object of class B could not be converted to string
* Catchable fatal error: Object of class A could not be converted to string
* Catchable fatal error: Object of class B could not be converted to string
*/
if ( is_object ( $call [ ‘object’ ])) < $call [ ‘object’ ] = ‘CONVERTED OBJECT OF CLASS ‘ . get_class ( $call [ ‘object’ ]); >
if ( is_array ( $call [ ‘args’ ])) <
foreach ( $call [ ‘args’ ] AS & $arg ) <
if ( is_object ( $arg )) < $arg = ‘CONVERTED OBJECT OF CLASS ‘ . get_class ( $arg ); >
>
>

$trace_text [ $i ] = «#» . $i . » » . $call [ ‘file’ ]. ‘(‘ . $call [ ‘line’ ]. ‘) ‘ ;
$trace_text [ $i ].= (!empty( $call [ ‘object’ ])? $call [ ‘object’ ]. $call [ ‘type’ ]: » );
$trace_text [ $i ].= $call [ ‘function’ ]. ‘(‘ . implode ( ‘, ‘ , $call [ ‘args’ ]). ‘)’ ;
>

class A <
public function test ( $obj ) <
$obj -> test ();
>
>

class B <
public function test () <
echo myPrint ( debug_backtrace ());
>
>

$A = new A ();
$B = new B ();

Quick and dirty formatted output from debug_backtrace.

foreach($file_paths AS $file_path) <
foreach($file_path AS $key => $var) <
if($key == ‘args’) <
foreach($var AS $key_arg => $var_arg) <
echo $key_arg . ‘: ‘ . $var_arg . ‘
‘;
>
> else <
echo $key . ‘: ‘ . $var . ‘
‘;
>
>
>

I want to point out that debug_backtrace() in new versions of php can detect recursion // circular references .. avoiding memory consumption.

class ParentClass <
public function __construct ()
<
$this -> _child = new ChildClass ( $this );
var_dump ( debug_backtrace ());
>
>

class ChildClass <
public function __construct ( ParentClass $p )
<
$this -> _parent = $p ;
>
>

$test = new ParentClass ();
?>

Output:

array(1) <
[0]=>
array(7) <
[«file»]=>
string(23) «/home/jcm/testdebug.php»
[«line»]=>
int(18)
[«function»]=>
string(11) «__construct»
[«class»]=>
string(11) «ParentClass»
[«object»]=>
object(ParentClass)#1 (1) <
[«_child»]=>
object(ChildClass)#2 (1) <
[«_parent»]=>
*RECURSION*
>
>
[«type»]=>
string(2) «->»
[«args»]=>
array(0) <
>
>
>

Attention in the *RECURSION* hint provided

When using debug_backtrace() to check if you’re being accessed from another caller, please remember to ask debug_backtrace to only go as far as needed in depth and skip taking the entire debug object as return parameter:

if ( count ( debug_backtrace ( FALSE , 1 )) == 0 )
<
// Do something
>
?>

It works a little bit different with resources in different PHP versions.

For example:
function foo($bar)
<
return debug_backtrace();
>

$resource = fopen(__FILE__, ‘r’);
$backtrace = foo($resource);
echo «when resource is opened: » . gettype($backtrace[0][‘args’][0]) . «n»;
fclose($resource);
echo «when resource is closed: » . gettype($backtrace[0][‘args’][0]) . «n»;

With 5.3.10 I got:
when resource is opened: resource
when resource is closed: resource

With 5.5.9:
when resource is opened: resource
when resource is closed: unknown type

Here’s my little updated contribution — it prints colorful output in the way I prefer. Define a helper function isRootIp() that contains an array including your IP; then calls to bt() simply return, so you can sprinkle backtraces in live sites w/o anyone knowing.

function bt ()
<
if( ! isRootIp () )
<
return false ;
>
array_walk ( debug_backtrace (), create_function ( ‘$a,$b’ , ‘print «
«. basename( $a[’file’] ). « <$a[‘line’]> <$a[‘function’]>() — «. dirname( $a[’file’] ). «/»;’ ) );
>
?>

I use this simple but effective function so i can see which method in the child class called the current method (in the parent class).

function get_caller_method ()
<
$traces = debug_backtrace ();

if (isset( $traces [ 2 ]))
<
return $traces [ 2 ][ ‘function’ ];
>

initialize
debug example.php> code-lines: 39-41 time: 2.0002 mem: 19 KB
debug example.php> code-lines: 41-44 time: 0.0000 mem: 19 KB
debug example.php> code-lines: 44-51 time: 0.6343 mem: 9117 KB
debug example.php> code-lines: 51-53 time: 0.1003 mem: 9117 KB
debug example.php> code-lines: 53-55 time: 0.0595 mem: 49 KB
*/

function debug()
<
static $start_time = NULL;
static $start_code_line = 0;

$call_info = array_shift( debug_backtrace() );
$code_line = $call_info[‘line’];
$file = array_pop( explode(‘/’, $call_info[‘file’]));

if( $start_time === NULL )
<
print «debug «.$file.»> initializen»;
$start_time = time() + microtime();
$start_code_line = $code_line;
return 0;
>

printf(«debug %s> code-lines: %d-%d time: %.4f mem: %d KBn», $file, $start_code_line, $code_line, (time() + microtime() — $start_time), ceil( memory_get_usage()/1024));
$start_time = time() + microtime();
$start_code_line = $code_line;
>

Here is a function to cleanly output the debug_backtrace to the error_log

/*
* Send the output from a backtrace to the error_log
* @param string $message Optional message that will be sent the the error_log before the backtrace
*/
function log_trace ( $message = » ) <
$trace = debug_backtrace ();
if ( $message ) <
error_log ( $message );
>
$caller = array_shift ( $trace );
$function_name = $caller [ ‘function’ ];
error_log ( sprintf ( ‘%s: Called from %s:%s’ , $function_name , $caller [ ‘file’ ], $caller [ ‘line’ ]));
foreach ( $trace as $entry_id => $entry ) <
$entry [ ‘file’ ] = $entry [ ‘file’ ] ? : ‘-‘ ;
$entry [ ‘line’ ] = $entry [ ‘line’ ] ? : ‘-‘ ;
if (empty( $entry [ ‘class’ ])) <
error_log ( sprintf ( ‘%s %3s. %s() %s:%s’ , $function_name , $entry_id + 1 , $entry [ ‘function’ ], $entry [ ‘file’ ], $entry [ ‘line’ ]));
> else <
error_log ( sprintf ( ‘%s %3s. %s->%s() %s:%s’ , $function_name , $entry_id + 1 , $entry [ ‘class’ ], $entry [ ‘function’ ], $entry [ ‘file’ ], $entry [ ‘line’ ]));
>
>
>
?>

A usual entry looks like this:
array( 6 ) <
‘file’ =>
string ( 87 ) «DbSelector.php»
‘line’ =>
int ( 171 )
‘function’ =>
string ( 5 ) «error»
‘class’ =>
string ( 42 ) «LoggingService»
‘type’ =>
string ( 2 ) «::»
‘args’ =>
array( 1 ) <
[ 0 ] =>
string ( 27 ) «Connecting to DB: unittests»
>
>
?>

Be warned though that ‘file’ and ‘class’ do not reference the same thing!
‘file’ means which file calls the next step.
‘class’ is the next step being called.

So ‘file’ is the caller, ‘class’ is the callee.

Another variation formatting backtrace.
Parameter $ignore to ignore the extra calls.
/**
* Getting backtrace
*
* @param int $ignore ignore calls
*
* @return string
*/
protected function getBacktrace ( $ignore = 2 )
<
$trace = » ;
foreach ( debug_backtrace () as $k => $v ) <
if ( $k $ignore ) <
continue;
>

$trace .= ‘#’ . ( $k — $ignore ) . ‘ ‘ . $v [ ‘file’ ] . ‘(‘ . $v [ ‘line’ ] . ‘): ‘ . (isset( $v [ ‘class’ ]) ? $v [ ‘class’ ] . ‘->’ : » ) . $v [ ‘function’ ] . ‘(‘ . implode ( ‘, ‘ , $v [ ‘args’ ]) . ‘)’ . «n» ;
>

need no Xdebug or dbg.so on server, return more detailed message:

diyism_trace.php:
( TRACES_MODE , ‘TEXTAREA’ ); //’TEXTAREA’ or ‘FIREPHP’
$GLOBALS [ ‘traces.pre’ ]=array();
function my_array_diff ( $arr1 , $arr2 )
$v )
>
>
return $arr1 ;
>
function my_var_export ( $var , $is_str = false )
< $rtn = preg_replace (array( ‘/Arrays+(/’ , ‘/[(d+)] =>(.*)n/’ , ‘/[([^d].*)] => (.*)n/’ ), array( ‘array (‘ , ‘1 => ’2» . «n» , ‘’1’ => ’2» . «n» ), substr ( print_r ( $var , true ), 0 , — 1 ));
$rtn = strtr ( $rtn , array( «=> ‘array (‘» => ‘=> array (‘ ));
$rtn = strtr ( $rtn , array( «)nn» => «)n» ));
$rtn = strtr ( $rtn , array( «‘n» => «‘,n» , «)n» => «),n» ));
$rtn = preg_replace (array( ‘/n +/e’ ), array( ‘strtr(’’, array(’ ’=>’ ’))’ ), $rtn );
$rtn = strtr ( $rtn , array( » Object’,» => » Object’ ));
if ( $is_str )
>
else
>
>
function tick_handler ()
< $tmp = debug_backtrace ();
$trace = my_array_diff ( $tmp , $GLOBALS [ ‘traces.pre’ ]);
//echo »;echo ‘
‘; //for debug diyism_trace.php
$trace = array_values ( $trace );
$GLOBALS [ ‘traces.pre’ ]= $tmp ;
if ( count ( $trace )> 0 && $trace [ 0 ][ ‘file’ ]. ‘/’ .@ $tmp [ 1 ][ ‘function’ ]!==@ $GLOBALS [ ‘traces’ ][ count ( $GLOBALS [ ‘traces’ ])- 1 ][ ‘key’ ]) //filter empty array and rearrange array_values(), because some lines will trigger two tick events per line, for example: 1.last line is «some code;questmark>» 2.error_reporting(.
= 0 ; — $i )
< $GLOBALS [ ‘traces’ ][]= $tmp_fb = array_merge (array( ‘key’ =>$trace [ $i ][ ‘file’ ]. ‘/’ .@ $tmp [ $i + 1 ][ ‘function’ ]), $trace [ $i ], array( ‘function’ => strtr ( $trace [ $i ][ ‘function’ ], array( ‘tick_handler’ => ‘CONTINUE’ )), ‘in_function’ =>@ $tmp [ $i + 1 ][ ‘function’ ]));
TRACES_MODE === ‘FIREPHP’ ? fb ( trace_output ( $tmp_fb ), ‘diyism_trace:’ .++ $GLOBALS [ ‘diyism_trace_no’ ]): » ;
>
>
>
function trace_output ( $trace )
< $trace [ ‘in_function’ ]= strtr (@ $trace [ ‘in_function’ ], array( ‘require’ =>» , ‘require_once’ => » , ‘include’ => » , ‘include_once’ => » ));
$trace [ ‘args’ ]= $trace [ ‘args’ ]? strtr ( preg_replace (array( ‘/n +/’ ), array( » ), preg_replace (array( ‘/n d+ => /’ ), array( » ), substr ( my_var_export ( $trace [ ‘args’ ], true ), 7 , — 3 ))), array( «r» => ‘r’ , «n» => ‘n’ )): » ;
return $trace [ ‘file’ ].( $trace [ ‘in_function’ ]? ‘/’ . $trace [ ‘in_function’ ]. ‘()’ : » ). ‘/’ . $trace [ ‘line’ ]. ‘: ‘ . $trace [ ‘function’ ]. ‘(‘ . $trace [ ‘args’ ]. ‘)’ ;
>
function traces_output ()
>
register_tick_function ( ‘tick_handler’ );
TRACES_MODE === ‘TEXTAREA’ ? register_shutdown_function ( ‘traces_output’ ): » ;
?>

test.php:
declare( ticks = 1 );
require ‘diyism_trace.php’ ;

Surprisingly, no one has described one of the best uses of this: dumping a variable and showing the location. When debugging, especially a big and unfamiliar system, it’s a pain remembering where I added those var dumps. Also, this way there is a separator between multiple dump calls.

function dump ( $var ) <
$result = var_export ( $var , true );
$loc = whereCalled ();
return «n» ;
>

function whereCalled ( $level = 1 ) <
$trace = debug_backtrace ();
$file = $trace [ $level ][ ‘file’ ];
$line = $trace [ $level ][ ‘line’ ];
$object = $trace [ $level ][ ‘object’ ];
if ( is_object ( $object ))

return «Where called: line $line of $object n(in $file )» ;
>
?>

In addition, calling ‘whereCalled()’ from any function will quickly identify locations that are doing something unexpected (e.g., updating a property at the wrong time). I’m new to PHP, but have used the equivalent in Perl for years.

Further to my previous note, the ‘object’ element of the array can be used to get the parent object. So changing the get_class_static() function to the following will make the code behave as expected:

function get_class_static () <
$bt = debug_backtrace ();

if (isset( $bt [ 1 ][ ‘object’ ]))
return get_class ( $bt [ 1 ][ ‘object’ ]);
else
return $bt [ 1 ][ ‘class’ ];
>
?>

HOWEVER, it still fails when being called statically. Changing the last two lines of my previous example to

:: printClassName ();
bar :: printClassName ();
?>

. still gives the same problematic result in PHP5, but in this case the ‘object’ property is not set, so that technique is unavailable.

I find it useful to know if a function is being called. in Java for instance you usually print a line with the functionname and arguments in the beginning of the function. I wanted to achieve the same thing in php thus i wrote the following class:

class Debug
<
private static $calls ;

public static function log ( $message = null )
<
if(! is_array ( self :: $calls ))
self :: $calls = array();

$call = debug_backtrace ( false );
$call = (isset( $call [ 1 ]))? $call [ 1 ]: $call [ 0 ];

$call [ ‘message’ ] = $message ;
array_push ( self :: $calls , $call );
>
>
?>

include this class before anything else
usage: Debug::log($message); at the beginning of your function.

write yourself a nice printout of the data;

Everybody seems to have their favorite use. I substitute this function for die(). It gives a message
to the user and emails me a PrettyPrint of what went wrong. $info is set by me,
and it does a special check in the database object.

function var_format ( $v ) // pretty-print var_export
<
return ( str_replace (array( «n» , » » , «array» ),
array( «
» , » » , » array» ),
var_export ( $v , true )). «
» );
>
function myDie ( $info )
<
$mysqlerr = strpos ( $info , «ERROR=You have an error in your SQL syntax» );
if( $mysqlerr > 0 ) $info = substr ( $info , 0 , $mysqlerr ). » mySql format error» ;
$out = «
MSG=’ $info ‘
» . var_format ( $_REQUEST ). «
» ;
$bt = debug_backtrace ();
$sp = 0 ;
$trace = «» ;
foreach( $bt as $k => $v )
<
extract ( $v );
$file = substr ( $file , 1 + strrpos ( $file , «/» ));
if( $file == «db.php» )continue; // the db object
$trace .= str_repeat ( » » ,++ $sp ); //spaces(++$sp);
$trace .= «file= $file , line= $line , function= $function
» ;
>
$out .= «
» . backTrace ();
if( substr ( $info , 0 , 4 )== «XXX » ) // special errrors when db is inaccessible
<
$out = str_replace ( «
» , «n» , $out );
$out = str_replace ( » » , » » , $out );
mail ( «me@example.com» , «Database Execution Error for user » . $REMOTE_ADDR , » $out » );
exit( «Database Access Error. Please try again later.» );
>
mail ( «me@example.com» , ‘Error Monitor’ , ‘Execution Error’ , $out );
exit( «DANG! An execution error in the program has been sent to the webmaster.
If you don’t get an email from him soon, please call him.» );
>
?>

This produces an output like this

file=badmode.php, line=5, function=backTrace
file=login.php, line=209, function=require
file=midScreen.php, line=264, function=require
file=masterindex.php, line=161, function=require
file=production2.php, line=121, function=require
file=index.php, line=16, function=require

It should be noted that if an internal php function such as call_user_func in the backtrace, the ‘file’ and ‘line’ entries will not be set.

Most debug tracers will use these entries. You should place a check to see if the key exists in the array before using this function. Otherwise notices will be generated.

foreach ( $arrTrace as $arr )
<
if (!isset ( $arr [ ‘file’ ]))
<
$arr [ ‘file’ ] = ‘[PHP Kernel]’ ;
>

Источник

PHP для начинающих. Обработка ошибок +32

PHP


Рекомендация: подборка платных и бесплатных курсов Java — https://katalog-kursov.ru/

image

Не совершает ошибок только тот, кто ничего не делает, и мы тому пример — сидим и трудимся не покладая рук, читаем Хабр :)

В этой статье я поведу свой рассказа об ошибках в PHP, и о том как их обуздать.

Ошибки

Разновидности в семействе ошибок

Перед тем как приручать ошибки, я бы рекомендовал изучить каждый вид и отдельно обратить внимание на самых ярких представителей.

Чтобы ни одна ошибка не ушла незамеченной потребуется включить отслеживание всех ошибок с помощью функции error_reporting(), а с помощью директивы display_errors включить их отображение:

<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);

Фатальные ошибки

Самый грозный вид ошибок — фатальные, они могут возникнуть как при компиляции, так и при работе парсера или PHP-скрипта, выполнение скрипта при этом прерывается.

E_PARSE

Это ошибка появляется, когда вы допускаете грубую ошибку синтаксиса и интерпретатор PHP не понимает, что вы от него хотите, например если не закрыли фигурную или круглую скобочку:

<?php
/**
 * Parse error: syntax error, unexpected end of file
 */
{

Или написали на непонятном языке:

<?php
/**
 * Parse error: syntax error, unexpected '...' (T_STRING)
 */
Тут будет ошибка парсера

Лишние скобочки тоже встречаются, и не так важно круглые либо фигурные:

<?php
/**
 * Parse error: syntax error, unexpected '}'
 */
}

Отмечу один важный момент — код файла, в котором вы допустили parse error не будет выполнен, следовательно, если вы попытаетесь включить отображение ошибок в том же файле, где возникла ошибка парсера то это не сработает:

<?php
// этот код не сработает
error_reporting(E_ALL);
ini_set('display_errors', 1);

// т.к. вот тут
ошибка парсера

E_ERROR

Это ошибка появляется, когда PHP понял что вы хотите, но сделать сие не получилось ввиду ряда причин. Эта ошибка так же прерывает выполнение скрипта, при этом код до появления ошибки сработает:

Не был найден подключаемый файл:

/**
 * Fatal error: require_once(): Failed opening required 'not-exists.php' 
 * (include_path='.:/usr/share/php:/usr/share/pear')
 */
require_once 'not-exists.php';

Было брошено исключение (что это за зверь, расскажу немного погодя), но не было обработано:

/**
 * Fatal error: Uncaught exception 'Exception'
 */
throw new Exception();

При попытке вызвать несуществующий метод класса:

/**
 * Fatal error: Call to undefined method stdClass::notExists()
 */
$stdClass = new stdClass();
$stdClass->notExists();

Отсутствия свободной памяти (больше, чем прописано в директиве memory_limit) или ещё чего-нить подобного:

/**
 * Fatal Error: Allowed Memory Size
 */
$arr = array();

while (true) {
    $arr[] = str_pad(' ', 1024);
}

Очень часто встречается при чтении либо загрузки больших файлов, так что будьте внимательны с вопросом потребляемой памяти

Рекурсивный вызов функции. В данном примере он закончился на 256-ой итерации, ибо так прописано в настройках xdebug (да, данная ошибка может проявиться в таком виде только при включении xdebug расширения):

/**
 * Fatal error: Maximum function nesting level of '256' reached, aborting!
 */
function deep() {
    deep();
}
deep();

Не фатальные

Данный вид не прерывает выполнение скрипта, но именно их обычно находит тестировщик. Именно такие ошибки доставляют больше всего хлопот начинающим разработчикам.

E_WARNING

Частенько встречается, когда подключаешь файл с использованием include, а его не оказывается на сервере или вы ошиблись указывая путь к файлу:

/**
 * Warning: include_once(): Failed opening 'not-exists.php' for inclusion
 */
include_once 'not-exists.php';

Бывает, если используешь неправильный тип аргументов при вызове функций:

/**
 * Warning: join(): Invalid arguments passed
 */
join('string', 'string');

Их очень много, и перечислять все не имеет смысла…

E_NOTICE

Это самые распространенные ошибки, мало того, есть любители отключать вывод ошибок и клепают их целыми днями. Возникают при целом ряде тривиальных ошибок.

Когда обращаются к неопределенной переменной:

/**
 * Notice: Undefined variable: a
 */
echo $a;

Когда обращаются к несуществующему элементу массива:

/**
 * Notice: Undefined index: a
 */
$b = [];
$b['a'];

Когда обращаются к несуществующей константе:

/**
 * Notice: Use of undefined constant UNKNOWN_CONSTANT - assumed 'UNKNOWN_CONSTANT'
 */
echo UNKNOWN_CONSTANT;

Когда не конвертируют типы данных:

/**
 * Notice: Array to string conversion
 */
echo array();

Для избежания подобных ошибок — будьте внимательней, и если вам IDE подсказывает о чём-то — не игнорируйте её:

PHP E_NOTICE in PHPStorm

E_STRICT

Это ошибки, которые научат вас писать код правильно, чтобы не было стыдно, тем более IDE вам эти ошибки сразу показывает. Вот например, если вызвали не статический метод как статику, то код будет работать, но это как-то неправильно, и возможно появление серьёзных ошибок, если в дальнейшем метод класса будет изменён, и появится обращение к $this:

/**
 * Strict standards: Non-static method Strict::test() should not be called statically
 */
class Strict {
    public function test() {
        echo "Test";
    }
}

Strict::test();

Данный тип ошибок актуален для PHP версии 5.6, и практически все их выпилили из
7-ки. Почитать подробней можно в соответствующей RFC. Если кто знает где ещё остались данные ошибки, то напишите в комментариях

E_DEPRECATED

Так PHP будет ругаться, если вы используете устаревшие функции (т.е. те, что помечены как deprecated, и в следующем мажорном релизе их не будет):

/**
 * Deprecated: Function split() is deprecated
 */
// данная функция, удалена из PHP 7.0
// считается устаревшей с PHP 5.3
split(',', 'a,b');

В моём редакторе подобные функции будут зачёркнуты:

PHP E_DEPRECATED in PHPStorm

Пользовательские

Этот вид, которые «разводит» сам разработчик кода, я уже давно их не встречал, и не рекомендую вам ими злоупотреблять:

  • E_USER_ERROR — критическая ошибка
  • E_USER_WARNING — не критическая ошибка
  • E_USER_NOTICE — сообщения которые не являются ошибками

Отдельно стоит отметить E_USER_DEPRECATED — этот вид всё ещё используется очень часто для того, чтобы напомнить программисту, что метод или функция устарели и пора переписать код без использования оной. Для создания этой и подобных ошибок используется функция trigger_error():

/**
 * @deprecated Deprecated since version 1.2, to be removed in 2.0
 */
function generateToken() {
    trigger_error('Function `generateToken` is deprecated, use class `Token` instead', E_USER_DEPRECATED);
    // ...
    // code ...
    // ...
}

Теперь, когда вы познакомились с большинством видов и типов ошибок, пора озвучить небольшое пояснение по работе директивы display_errors:

  • если display_errors = on, то в случае ошибки браузер получит html c текстом ошибки и кодом 200
  • если же display_errors = off, то для фатальных ошибок код ответа будет 500 и результат не будет возвращён пользователю, для остальных ошибок — код будет работать неправильно, но никому об этом не расскажет

Приручение

Для работы с ошибками в PHP существует 3 функции:

  • set_error_handler() — устанавливает обработчик для ошибок, которые не обрывают работу скрипта (т.е. для не фатальных ошибок)
  • error_get_last() — получает информацию о последней ошибке
  • register_shutdown_function() — регистрирует обработчик который будет запущен при завершении работы скрипта. Данная функция не относится непосредственно к обработчикам ошибок, но зачастую используется именно для этого

Теперь немного подробностей об обработке ошибок с использованием set_error_handler(), в качестве аргументов данная функция принимает имя функции, на которую будет возложена миссия по обработке ошибок и типы ошибок которые будут отслеживаться. Обработчиком ошибок может так же быть методом класса, или анонимной функцией, главное, чтобы он принимал следующий список аргументов:

  • $errno — первый аргумент содержит тип ошибки в виде целого числа
  • $errstr — второй аргумент содержит сообщение об ошибке
  • $errfile — необязательный третий аргумент содержит имя файла, в котором произошла ошибка
  • $errline — необязательный четвертый аргумент содержит номер строки, в которой произошла ошибка
  • $errcontext — необязательный пятый аргумент содержит массив всех переменных, существующих в области видимости, где произошла ошибка

В случае если обработчик вернул true, то ошибка будет считаться обработанной и выполнение скрипта продолжится, иначе — будет вызван стандартный обработчик, который логирует ошибку и в зависимости от её типа продолжит выполнение скрипта или завершит его. Вот пример обработчика:

<?php
// включаем отображение всех ошибок, кроме E_NOTICE
error_reporting(E_ALL & ~E_NOTICE);
ini_set('display_errors', 1);

// наш обработчик ошибок
function myHandler($level, $message, $file, $line, $context) {
    // в зависимости от типа ошибки формируем заголовок сообщения
    switch ($level) {
        case E_WARNING:
            $type = 'Warning';
            break;
        case E_NOTICE:
            $type = 'Notice';
            break;
        default;
            // это не E_WARNING и не E_NOTICE
            // значит мы прекращаем обработку ошибки
            // далее обработка ложится на сам PHP
            return false;
    }
    // выводим текст ошибки
    echo "<h2>$type: $message</h2>";
    echo "<p><strong>File</strong>: $file:$line</p>";
    echo "<p><strong>Context</strong>: $". join(', $', 
    array_keys($context))."</p>";
    // сообщаем, что мы обработали ошибку, и дальнейшая обработка не требуется
    return true;
}

// регистрируем наш обработчик, он будет срабатывать на для всех типов ошибок
set_error_handler('myHandler', E_ALL);

У вас не получится назначить более одной функции для обработки ошибок, хотя очень бы хотелось регистрировать для каждого типа ошибок свой обработчик, но нет — пишите один обработчик, и всю логику отображения для каждого типа описывайте уже непосредственно в нём

С обработчиком, который написан выше есть одна существенная проблема — он не ловит фатальные ошибки, и при таких ошибках вместо сайта пользователи увидят лишь пустую страницу, либо, что ещё хуже, сообщение об ошибке. Дабы не допустить подобного сценария следует воспользоваться функцией register_shutdown_function() и с её помощью зарегистрировать функцию, которая всегда будет выполняться по окончанию работы скрипта:

function shutdown() {
    echo 'Этот текст будет всегда отображаться';
}
register_shutdown_function('shutdown');

Данная функция будет срабатывать всегда!

Но вернёмся к ошибкам, для отслеживания появления в коде ошибки воспользуемся функцией error_get_last(), с её помощью можно получить информацию о последней выявленной ошибке, а поскольку фатальные ошибки прерывают выполнение кода, то они всегда будут выполнять роль «последних»:

function shutdown() {
    $error = error_get_last();
    if (
        // если в коде была допущена ошибка
        is_array($error) &&
        // и это одна из фатальных ошибок
        in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])
     ) {
        // очищаем буфер вывода (о нём мы ещё поговорим в последующих статьях)
        while (ob_get_level()) {
            ob_end_clean();
        }
        // выводим описание проблемы
        echo "Сервер находится на техническом обслуживании, зайдите позже";
    }
}
register_shutdown_function('shutdown');

Хотел обратить внимание, что данный код хоть ещё и встречается для обработки ошибок, и вы возможно вы даже с ним столкнётесь, но он потерял актуальность начиная с 7-ой версии PHP. Что пришло на замену я расскажу чуть погодя.

Задание

Дополнить обработчик фатальных ошибок выводом исходного кода файла где была допущена ошибка, а так же добавьте подсветку синтаксиса выводимого кода.

О прожорливости

Проведём простой тест, и выясним — сколько драгоценных ресурсов кушает самая тривиальная ошибка:

/**
 * Этот код не вызывает ошибок
 */

// засекаем время выполнения скрипта
$time= microtime(true);

define('AAA', 'AAA');
$arr = [];
for ($i = 0; $i < 10000; $i++) {
    $arr[AAA] = $i;
}

printf('%f seconds <br/>', microtime(true) - $time);

В результате запуска данного скрипта у меня получился вот такой результат:

0.002867 seconds

Теперь добавим ошибку в цикле:

/**
 * Этот код содержит ошибку
 */

// засекаем время выполнения скрипта
$time= microtime(true);

$arr = [];
for ($i = 0; $i < 10000; $i++) {
    $arr[BBB] = $i; // тут используем константанту, которая у нас не объявлена
}

printf('%f seconds <br/>', microtime(true) - $time);

Результат ожидаемо хуже, и на порядок (даже на два порядка!):

0.263645 seconds

Вывод однозначен — ошибки в коде приводят к лишней прожорливости скриптов — так что во время разработки и тестирования приложения включайте отображение всех ошибок!

Тестирование проводил на различных версиях PHP и везде разница в десятки раз, так что пусть это будет ещё одним поводом для исправления всех ошибок в коде

Где собака зарыта

В PHP есть спец символ «@» — оператор подавления ошибок, его используют дабы не писать обработку ошибок, а положится на корректное поведение PHP в случае чего:

<?php
echo @UNKNOWN_CONSTANT;

При этом обработчик ошибок указанный в set_error_handler() всё равно будет вызван, а факт того, что к ошибке было применено подавление можно отследить вызвав функцию error_reporting() внутри обработчика, в этом случае она вернёт 0.

Если вы в такой способ подавляете ошибки, то это уменьшает нагрузку на процессор в сравнении с тем, если вы их просто скрываете (см. сравнительный тест выше), но в любом случае, подавление ошибок это зло

Задание

Проверьте, как влияет подавление ошибки с помощью @ на предыдущий пример с циклом.

Исключения

В эру PHP4 не было исключений (exceptions), всё было намного сложнее, и разработчики боролись с ошибками как могли, это было сражение не на жизнь, а на смерть… Окунуться в эту увлекательную историю противостояния можете в статье Исключительный код. Часть 1. Стоит ли её читать сейчас? Не могу дать однозначный ответ, лишь хочу заметить, что это поможет вам понять эволюцию языка, и раскроет всю прелесть исключений.

Исключения — исключительные событие в PHP, в отличии от ошибок не просто констатируют наличие проблемы, а требуют от программиста дополнительных действий по обработке каждого конкретного случая.

К примеру, скрипт должен сохранить какие-то данные в кеш файл, если что-то пошло не так (нет доступа на запись, нет места на диске), генерируется исключение соответствующего типа, а в обработчике исключений принимается решение — сохранить в другое место или сообщить пользователю о проблеме.

Исключение — это объект класса Exception либо одного из многих его наследников, содержит текст ошибки, статус, а также может содержать ссылку на другое исключение которое стало первопричиной данного. Модель исключений в PHP схожа с используемыми в других языках программирования. Исключение можно инициировать (как говорят, «бросить») при помощи оператора throw, и можно перехватить («поймать») оператором catch. Код генерирующий исключение, должен быть окружен блоком try, для того чтобы можно было перехватить исключение. Каждый блок try должен иметь как минимум один соответствующий ему блок catch или finally:

try {
    // код который может выбросить исключение
    if (random_int(0, 1)) {
        throw new Exception("One");
    }
    echo "Zero"
} catch (Exception $e) {
    // код который может обработать исключение
    echo $e->getMessage();
}

В каких случаях стоит применять исключения:

  • если в рамках одного метода/функции происходит несколько операций которые могут завершиться неудачей
  • если используемый вами фреймворк или библиотека декларируют их использование

Для иллюстрации первого сценария возьмём уже озвученный пример функции для записи данных в файл — помешать нам может очень много факторов, а для того, чтобы сообщить выше стоящему коду в чем именно была проблема необходимо создать и выбросить исключение:

$directory = __DIR__ . DIRECTORY_SEPARATOR . 'logs';

// директории может не быть
if (!is_dir($directory)) {
    throw new Exception('Directory `logs` is not exists');
}

// может не быть прав на запись в директорию
if (!is_writable($directory)) {
    throw new Exception('Directory `logs` is not writable');
}

// возможно кто-то уже создал файл, и закрыл к нему доступ
if (!$file = @fopen($directory . DIRECTORY_SEPARATOR . date('Y-m-d') . '.log', 'a+')) {
    throw new Exception('System can't create log file');
}

fputs($file, date("[H:i:s]") . " donen");
fclose($file);

Соответственно ловить данные исключения будем примерно так:

try {
    // код который пишет в файл
    // ...
} catch (Exception $e) {
    // выводим текст ошибки
    echo "Не получилось: ". $e->getMessage();
}

В данном примере приведен очень простой сценарий обработки исключений, когда у нас любая исключительная ситуация обрабатывается на один манер. Но зачастую, различные исключения требуют различного подхода к обработке, и тогда следует использовать коды исключений и задать иерархию исключений в приложении:

// исключения файловой системы
class FileSystemException extends Exception {}

// исключения связанные с директориями
class DirectoryException extends FileSystemException {
    // коды исключений
    const DIRECTORY_NOT_EXISTS =  1;
    const DIRECTORY_NOT_WRITABLE = 2;
}

// исключения связанные с файлами
class FileException extends FileSystemException {}

Теперь, если использовать эти исключения то можно получить следующий код:

try {
    // код который пишет в файл
    if (!is_dir($directory)) {
        throw new DirectoryException('Directory `logs` is not exists', DirectoryException::DIRECTORY_NOT_EXISTS);
    }

    if (!is_writable($directory)) {
        throw new DirectoryException('Directory `logs` is not writable', DirectoryException::DIRECTORY_NOT_WRITABLE);
    }

    if (!$file = @fopen($directory . DIRECTORY_SEPARATOR . date('Y-m-d') . '.log', 'a+')) {
        throw new FileException('System can't open log file');
    }

    fputs($file, date("[H:i:s]") . " donen");
    fclose($file);
} catch (DirectoryException $e) {
    echo "С директорией возникла проблема: ". $e->getMessage();
} catch (FileException $e) {
    echo "С файлом возникла проблема: ". $e->getMessage();
} catch (FileSystemException $e) {
    echo "Ошибка файловой системы: ". $e->getMessage();
} catch (Exception $e) {
    echo "Ошибка сервера: ". $e->getMessage();
}

Важно помнить, что Exception — это прежде всего исключительное событие, иными словами исключение из правил. Не нужно использовать их для обработки очевидных ошибок, к примеру, для валидации введённых пользователем данных (хотя тут не всё так однозначно). При этом обработчик исключений должен быть написан в том месте, где он будет способен его обработать. К примеру, обработчик для исключений вызванных недоступностью файла для записи должен быть в методе, который отвечает за выбор файла или методе его вызывающем, для того что бы он имел возможность выбрать другой файл или другую директорию.

Так, а что будет если не поймать исключение? Вы получите «Fatal Error: Uncaught exception …». Неприятно.

Чтобы избежать подобной ситуации следует использовать функцию set_exception_handler() и установить обработчик для исключений, которые брошены вне блока try-catch и не были обработаны. После вызова такого обработчика выполнение скрипта будет остановлено:

// в качестве обработчика событий
// будем использовать анонимную функцию
set_exception_handler(function($exception) {
    /** @var Exception $exception */
    echo $exception->getMessage(), "<br/>n";
    echo $exception->getFile(), ':', $exception->getLine(), "<br/>n";
    echo $exception->getTraceAsString(), "<br/>n";
});

Ещё расскажу про конструкцию с использованием блока finally — этот блок будет выполнен вне зависимости от того, было выброшено исключение или нет:

try {
    // код который может выбросить исключение
} catch (Exception $e) {
    // код который может обработать исключение
    // если конечно оно появится
} finally {
    // код, который будет выполнен при любом раскладе
}

Для понимания того, что это нам даёт приведу следующий пример использования блока finally:

try {
    // где-то глубоко внутри кода
    // соединение с базой данных
    $handler = mysqli_connect('localhost', 'root', '', 'test');

    try {
        // при работе с БД возникла исключительная ситуация
        // ...
        throw new Exception('DB error');
    } catch (Exception $e) {
        // исключение поймали, обработали на своём уровне
        // и должны его пробросить вверх, для дальнейшей обработки
        throw new Exception('Catch exception', 0, $e);
    } finally {
        // но, соединение с БД необходимо закрыть
        // будем делать это в блоке finally
        mysqli_close($handler);
    }

    // этот код не будет выполнен, если произойдёт исключение в коде выше
    echo "Ok";
} catch (Exception $e) {
    // ловим исключение, и выводим текст
    echo $e->getMessage();
    echo "<br/>";
    // выводим информацию о первоначальном исключении
    echo $e->getPrevious()->getMessage();
}

Т.е. запомните — блок finally будет выполнен даже в том случае, если вы в блоке catch пробрасываете исключение выше (собственно именно так он и задумывался).

Для вводной статьи информации в самый раз, кто жаждет ещё подробностей, то вы их найдёте в статье Исключительный код ;)

Задание

Написать свой обработчик исключений, с выводом текста файла где произошла ошибка, и всё это с подсветкой синтаксиса, так же не забудьте вывести trace в читаемом виде. Для ориентира — посмотрите как это круто выглядит у whoops.

PHP7 — всё не так, как было раньше

Так, вот вы сейчас всю информацию выше усвоили и теперь я буду грузить вас нововведениями в PHP7, т.е. я буду рассказывать о том, с чем вы будете сталкиваться работая над современным PHP проектом. Ранее я вам рассказывал и показывал на примерах какой костыль нужно соорудить, чтобы отлавливать критические ошибки, так вот — в PHP7 это решили исправить, но? как обычно? завязались на обратную совместимость кода, и получили хоть и универсальное решение, но оно далеко от идеала. А теперь по пунктам об изменениях:

  1. при возникновении фатальных ошибок типа E_ERROR или фатальных ошибок с возможностью обработки E_RECOVERABLE_ERROR PHP выбрасывает исключение
  2. эти исключения не наследуют класс Exception (помните я говорил об обратной совместимости, это всё ради неё)
  3. эти исключения наследуют класс Error
  4. оба класса Exception и Error реализуют интерфейс Throwable
  5. вы не можете реализовать интерфейс Throwable в своём коде

Интерфейс Throwable практически полностью повторяет нам Exception:

interface Throwable
{
    public function getMessage(): string;
    public function getCode(): int;
    public function getFile(): string;
    public function getLine(): int;
    public function getTrace(): array;
    public function getTraceAsString(): string;
    public function getPrevious(): Throwable;
    public function __toString(): string;
}

Сложно? Теперь на примерах, возьмём те, что были выше и слегка модернизируем:

try {
    // файл, который вызывает ошибку парсера
    include 'e_parse_include.php';
} catch (Error $e) {
    var_dump($e);
}

В результате ошибку поймаем и выведем:

object(ParseError)#1 (7) {
    ["message":protected] => string(48) "syntax error, unexpected 'будет' (T_STRING)"
    ["string":"Error":private] => string(0) ""
    ["code":protected] => int(0)
    ["file":protected] => string(49) "/www/education/error/e_parse_include.php"
    ["line":protected] => int(4)
    ["trace":"Error":private] => array(0) { }
    ["previous":"Error":private] => NULL
}

Как видите — поймали исключение ParseError, которое является наследником исключения Error, который реализует интерфейс Throwable, в доме который построил Джек. Ещё есть множество других исключений, но не буду мучать — для наглядности приведу иерархию исключений:

interface Throwable
|- Exception implements Throwable
|   |- ErrorException extends Exception
|   |- ... extends Exception
|   `- ... extends Exception
`- Error implements Throwable
    |- TypeError extends Error
    |- ParseError extends Error
    |- ArithmeticError extends Error
    |  `- DivisionByZeroError extends ArithmeticError
    `- AssertionError extends Error

И чуть-чуть деталей:

TypeError — для ошибок, когда тип аргументов функции не совпадает с передаваемым типом:

try {
    (function(int $one, int $two) {
        return;
    })('one', 'two');
} catch (TypeError $e) {
    echo $e->getMessage();
}

ArithmeticError — могут возникнуть при математических операциях, к примеру когда результат вычисления превышает лимит выделенный для целого числа:

try {
    1 << -1;
} catch (ArithmeticError $e) {
    echo $e->getMessage();
}

DivisionByZeroError — ошибка деления на ноль:

try {
    1 / 0;
} catch (ArithmeticError $e) {
    echo $e->getMessage();
}

AssertionError — редкий зверь, появляется когда условие заданное в assert() не выполняется:

ini_set('zend.assertions', 1);
ini_set('assert.exception', 1);

try {
    assert(1 === 0);
} catch (AssertionError $e) {
    echo $e->getMessage();
}

При настройках production-серверов, директивы zend.assertions и assert.exception отключают, и это правильно

Полный список предопределённых исключений вы найдёте в официальном мануале, там же иерархия SPL исключений.

Задание

Написать универсальный обработчик ошибок для PHP7, который будет отлавливать все возможные исключения.

При написании данного раздела были использованы материалы из статьи Throwable Exceptions and Errors in PHP 7.

Единообразие

— Там ошибки, тут исключения, а можно это всё как-то до кучи сгрести?

Да запросто, у нас же есть set_error_handler() и никто нам не запретит внутри оного обработчика бросить исключение:

// Бросаем исключение вместо ошибок
function errorHandler($severity, $message, $file = null, $line = null)
{
    // Кроме случаев, когда мы подавляем ошибки с помощью @
    if (error_reporting() === 0) {
        return false;
    }
    throw new ErrorException($message, 0, $severity, $file, $line);
}

// Будем обрабатывать все-все ошибки
set_error_handler('errorHandler', E_ALL);

Но данный подход с PHP7 избыточен, со всем теперь справляется Throwable:

try {
    /** ... **/
} catch (Throwable $e) {
    // отображение любых ошибок и исключений
    echo $e->getMessage();
}

Отладка

Иногда, для отладки кода, нужно отследить что происходило с переменной или объектом на определённом этапе, для этих целей есть функция debug_backtrace() и debug_print_backtrace() которые вернут историю вызовов функций/методов в обратном порядке:

<?php
function example() {
    echo '<pre>';
    debug_print_backtrace();
    echo '</pre>';
}

class ExampleClass {
    public static function method () {
        example();
    }
}

ExampleClass::method();

В результате выполнения функции debug_print_backtrace() будет выведен список вызовов приведших нас к данной точке:

#0  example() called at [/www/education/error/backtrace.php:10]
#1  ExampleClass::method() called at [/www/education/error/backtrace.php:14]

Проверить код на наличие синтаксических ошибок можно с помощью функции php_check_syntax() или же команды php -l [путь к файлу], но я не встречал использования оных.

Assert

Отдельно хочу рассказать о таком экзотическом звере как assert() в PHP. Собственно, этот кусочек можно рассматривать как мимикрию под контрактную методологию программирования, и дальше я расскажу вам как я никогда его не использовал :)

Функция assert() поменяла своё поведение при переходе от версии 5.6 к 7.0, и ещё сильней всё поменялось в версии 7.2, так что внимательней читайте changelog’и PHP ;)

Первый случай — это когда вам надо написать TODO прямо в коде, да так, чтобы точно не забыть реализовать заданный функционал:

// включаем asserts в php.ini
// zend.assertions=1
assert(false, "Remove it!");

В результате выполнения данного кода получим E_WARNING:

Warning: assert(): Remove it! failed

PHP7 можно переключить в режим exception, и вместо ошибки будет всегда появляться исключение AssertionError:

// переключаем в режим «исключений»
ini_set('assert.exception', 1);

assert(false, "Remove it!");

В результате ожидаемо получаем исключение AssertionError.

При необходимости, можно выбрасывать произвольное исключение:

assert(false, new Exception("Remove it!"));

Я бы рекомендовал использовать метки @TODO, современные IDE отлично с ними работают, и вам не нужно будет прикладывать дополнительные усилия и ресурсы для работы с ними, хотя с ними велик соблазн «забить»

Второй вариант использования — это создание некоего подобия TDD, но помните — это лишь подобие. Хотя, если сильно постараться, то можно получить забавный результат, который поможет в тестировании вашего кода:

// callback-функция для вывода информации в браузер
function backlog($script, $line, $code, $message) {
    echo $message;
}

// устанавливаем callback-функцию
assert_options(ASSERT_CALLBACK, 'backlog');

// отключаем вывод предупреждений
assert_options(ASSERT_WARNING,  false);

// пишем проверку и её описание
assert(sqr(4) === 16, 'When I send integer, function should return square of it');

// функция, которую проверяем
function sqr($a) {
    return; // она не работает
}

Третий вариант — некое подобие на контрактное программирование, когда вы описали правила использования своей библиотеки, но хотите точно убедится, что вас поняли правильно, и в случае чего сразу указать разработчику на ошибку (я вот даже не уверен, что правильно его понимаю, но пример кода вполне рабочий):

/**
 * Настройки соединения должны передаваться в следующем виде
 *
 *     [
 *         'host' => 'localhost',
 *         'port' => 3306,
 *         'name' => 'dbname',
 *         'user' => 'root',
 *         'pass' => ''
 *     ]
 *
 * @param $settings
 */
function setupDb ($settings) {
    // проверяем настройки
    assert(isset($settings['host']), 'Db `host` is required');
    assert(isset($settings['port']) && is_int($settings['port']), 'Db `port` is required, should be integer');
    assert(isset($settings['name']), 'Db `name` is required, should be integer');

    // соединяем с БД
    // ...
}

setupDb(['host' => 'localhost']);

Если вас заинтересовали контракты, то специально для вас у меня есть ссылочка на фреймворк PhpDeal.

Никогда не используйте assert() для проверки входных параметров, ведь фактически assert() интерпретирует первый параметр (ведёт себя как eval()), а это чревато PHP-инъекцией. И да, это правильное поведение, ведь если отключить assert’ы, то все передаваемые аргументы будут проигнорированы, а если делать как в примере выше, то код будет выполняться, а внутрь отключенного assert’a будет передан булевый результат выполнения. А, и это поменяли в PHP 7.2 :)

Если у вас есть живой опыт использования assert() — поделитесь со мной, буду благодарен. И да, вот вам ещё занимательно чтива по этой теме — PHP Assertions, с таким же вопросом в конце :)

В заключение

Я за вас напишу выводы из данной статьи:

  • Ошибкам бой — их не должно быть в вашем коде
  • Используйте исключения — работу с ними нужно правильно организовать и будет счастье
  • Assert — узнали о них, и хорошо

P.S.

Это репост из серии статей «PHP для начинающих»:

  • Сессия
  • Подключение файлов
  • Обработка ошибок

Если у вас есть замечания по материалу статьи, или возможно по форме, то описывайте в комментариях суть, и мы сделаем данный материал ещё лучше.

Спасибо Максиму Слесаренко за помощь в написании статьи.

Exception::getTrace

(PHP 5, PHP 7, PHP 8)

Exception::getTrace — Gets the stack trace

Description

Returns the Exception stack trace.

Parameters

This function has no parameters.

Return Values

Returns the Exception stack trace as an array .

Examples

Example #1 Exception::getTrace() example

function test () <
throw new Exception ;
>

try <
test ();
> catch( Exception $e ) <
var_dump ( $e -> getTrace ());
>
?>

The above example will output something similar to:

See Also

User Contributed Notes 5 notes

Two important points about this function which are not documented:

1) The trace does not include the file / line at which the exception is thrown; that entry is only recorded in the top-level getFile/Line methods.

2) Elements are returned in ‘closest-first’ order, e.g. if you have a script x which calls function y which calls function z which throws an exception, then the first trace element will be ‘Y’ and the second will be ‘X’.

If you are wanting to see the args within a stack trace on PHP 7.4, note that there is now a zend flag in the php.ini file that is default set to Off.

Set this flag to On and it will show the args again.

The order of the trace starts at the source of the exception and does not include main.
So for example:

function Bar () <
throw new Exception ;
>

try <
Foo ();
> catch( Exception $e ) <
var_dump ( $e -> getTrace ());
>
?>

Will output:

When calling getTrace(), there is also the name of the class in returned array:

throw new Exception ( ‘FATAL ERROR: bla bla. ‘ );

> catch( Exception $e ) <

var_dump ( $e -> getTrace ());

>
?>

Will show something like:

array(1) <
[0]=> array(6) <
[«file»]=> string(54) «/. /test.php»
[«line»]=> int(37)
[«function»]=> string(11) «__construct»
[«class»]=> string(4) «Test»
[«type»]=> string(2) «->»
[«args»]=> array(0) < >
>
>

You can use this function to format a exception:

function MakePrettyException ( Exception $e ) <
$trace = $e -> getTrace ();

$result = ‘Exception: «‘ ;
$result .= $e -> getMessage ();
$result .= ‘» @ ‘ ;
if( $trace [ 0 ][ ‘class’ ] != » ) <
$result .= $trace [ 0 ][ ‘class’ ];
$result .= ‘->’ ;
>
$result .= $trace [ 0 ][ ‘function’ ];
$result .= ‘();
‘ ;

> catch( Exception $e ) <

echo MakePrettyException ( $e );

Exception: «FATAL ERROR: bla bla. » @ Test->__construct();

Источник

debug_print_backtrace

(PHP 5, PHP 7, PHP 8)

debug_print_backtrace — Выводит стек вызовов функций

Описание

debug_print_backtrace() выводит стек вызовов функций. Выводит вызовы функций, имена включённых/требуемых файлов и другую информацию из функций ( eval() ).

Список параметров

Аргумент является битовой маской для следующих настроек:

Опции debug_print_backtrace()

DEBUG_BACKTRACE_IGNORE_ARGS Нужно ли исключить ключ «args», то есть списки аргументов всех функций/методов, чтобы уменьшить расход памяти.

Аргумент используется для ограничения количества вызовов функций, которые будут выведены. По умолчанию ( limit = 0 ) будет выведен весь стек вызовов.

Возвращаемые значения

Функция не возвращает значения после выполнения.

Примеры

Пример #1 Пример использования debug_print_backtrace()

function c () <
debug_print_backtrace ();
>

Результатом выполнения данного примера будет что-то подобное:

Смотрите также

User Contributed Notes 5 notes

Another way to manipulate and print a backtrace, without using output buffering:

// print backtrace, getting rid of repeated absolute path on each file
$e = new Exception ();
print_r ( str_replace ( ‘/path/to/code/’ , » , $e -> getTraceAsString ()));
?>

I like the output of debug_print_backtrace() but I sometimes want it as a string.

bortuzar’s solution to use output buffering is great, but I’d like to factorize that into a function. Doing that however always results in whatever function name I use appearing at the top of the stack which is redundant.

Below is my noddy (simple) solution. If you don’t care for renumbering the call stack, omit the second preg_replace().

function debug_string_backtrace () <
ob_start ();
debug_print_backtrace ();
$trace = ob_get_contents ();
ob_end_clean ();

// Remove first item from backtrace as it’s this function which
// is redundant.
$trace = preg_replace ( ‘/^#0s+’ . __FUNCTION__ . «[^n]*n/» , » , $trace , 1 );

// Renumber backtrace items.
$trace = preg_replace ( ‘/^#(d+)/me’ , ‘’#’ . ($1 — 1)’ , $trace );

If your show your error messages in HTML (with suitable safety using entities), this function won’t work nicely because it uses newlines for formatting.

Here is a function that works similarly, but using
tags. Insert it near the beginning of your program to add a stack to Warning output only, or modify it as you like:

// Here is code for error stack output in HTML:
function error_handler_callback($errno,$message,$file,$line,$context)
<
if ($errno === E_WARNING)
echo «Stack, innermost first:
«.nl2br((new Exception())->getTraceAsString());
return false; // to execute the regular error handler
>
set_error_handler(«error_handler_callback»);

Here’s a function that returns a string with the same information shown in debug_print_backtrace(), with the option to exclude a certain amount of traces (by altering the $traces_to_ignore argument).

I’ve done a couple of tests to ensure that it prints exactly the same information, but I might have missed something.

This solution is a nice workaround to get the debug_print_backtrace() information if you’re already using ob_start() in your PHP code.

function get_debug_print_backtrace ( $traces_to_ignore = 1 ) <
$traces = debug_backtrace ();
$ret = array();
foreach( $traces as $i => $call ) <
if ( $i $traces_to_ignore ) <
continue;
>

$object = » ;
if (isset( $call [ ‘class’ ])) <
$object = $call [ ‘class’ ]. $call [ ‘type’ ];
if ( is_array ( $call [ ‘args’ ])) <
foreach ( $call [ ‘args’ ] as & $arg ) <
get_arg ( $arg );
>
>
>

$ret [] = ‘#’ . str_pad ( $i — $traces_to_ignore , 3 , ‘ ‘ )
. $object . $call [ ‘function’ ]. ‘(‘ . implode ( ‘, ‘ , $call [ ‘args’ ])
. ‘) called at [‘ . $call [ ‘file’ ]. ‘:’ . $call [ ‘line’ ]. ‘]’ ;
>

return implode ( «n» , $ret );
>

function get_arg (& $arg ) <
if ( is_object ( $arg )) <
$arr = (array) $arg ;
$args = array();
foreach( $arr as $key => $value ) <
if ( strpos ( $key , chr ( 0 )) !== false ) <
$key = » ; // Private variable found
>
$args [] = ‘[‘ . $key . ‘] => ‘ . get_arg ( $value );
>

$arg = get_class ( $arg ) . ‘ Object (‘ . implode ( ‘,’ , $args ). ‘)’ ;
>
>
?>

This code will give you a simple horizontal stack trace to assist debugging:

class A <
public function testA () <
echo «

  • Class A.testA —-??» ;
    echo «
  • » . $this -> whoDidThat ();
    >
    public function whoDidThat () <
    $who = debug_backtrace ();
    $result = «» ;
    $count = 0 ;
    $last = count ( $who );
    foreach( $who as $k => $v ) <
    if ( $count ++ > 0 ) <
    $x = «» ;
    if ( $count > 2 ) <
    $x = «>» ;
    >
    $result = «[line» . $who [ $k ][ ‘line’ ]. «]» . $who [ $k ][ ‘class’ ]. «.» . $who [ $k ][ ‘function’ ]. $x . $result ;
    >
    >
    return $result ;
    >
    >
    class B extends A <
    public function testB () <
    echo «
  • Class B.testB» ;
    echo «
  • » . $this -> whoDidThat ();
    >
    public function testA () <
    echo «
  • Class testB.testA —- Y» ;
    echo «
  • » . $this -> whoDidThat ();
    >
    >
    class C <
    public function test () <
    echo » » ;
    $b =new B ();
    echo » Class C calling B.testA» ;
    $b -> testA ();
    >
    >

    $c =new C ();
    $c -> test ();
    echo debug_print_backtrace ();
    ?>

    When run you get

    Источник

    Exception::getTraceAsString

    (PHP 5, PHP 7, PHP 8)

    Exception::getTraceAsString — Gets the stack trace as a string

    Description

    Returns the Exception stack trace as a string.

    Parameters

    This function has no parameters.

    Return Values

    Returns the Exception stack trace as a string.

    Examples

    Example #1 Exception::getTraceAsString() example

    function test () <
    throw new Exception ;
    >

    try <
    test ();
    > catch( Exception $e ) <
    echo $e -> getTraceAsString ();
    >
    ?>

    The above example will output something similar to:

    See Also

    User Contributed Notes 2 notes

    Honestly, Exception::getTraceAsString() simply sucks, listing only the called method (below, for example, on line 89 function fail2() gets called, but there’s no information that you have the originator is fail1()). The fact that, in the example below, the exception gets thrown on line 78, is completely omitted from the trace and only available within the exception. Chained exceptions are not supported as well.

    Example:
    #0 /var/htdocs/websites/sbdevel/public/index.php(70): seabirdtestC->exc()
    #1 /var/htdocs/websites/sbdevel/public/index.php(85): seabirdtestC->doexc()
    #2 /var/htdocs/websites/sbdevel/public/index.php(89): seabirdtestfail2()
    #3 /var/htdocs/websites/sbdevel/public/index.php(93): seabirdtestfail1()
    #4

    jTraceEx() provides a much better java-like stack trace that includes support for chained exceptions:
    Exception: Thrown from class C
    at seabird.test.C.exc(index.php:78)
    at seabird.test.C.doexc(index.php:70)
    at seabird.test.fail2(index.php:85)
    at seabird.test.fail1(index.php:89)
    at (main)(index.php:93)
    Caused by: Exception: Thrown from class B
    at seabird.test.B.exc(index.php:64)
    at seabird.test.C.exc(index.php:75)
    . 4 more
    Caused by: Exception: Thrown from class A
    at seabird.test.A.exc(index.php:46)
    at seabird.test.B.exc(index.php:61)
    . 5 more

    (see at the end for the example code)

    /**
    * jTraceEx() — provide a Java style exception trace
    * @param $exception
    * @param $seen — array passed to recursive calls to accumulate trace lines already seen
    * leave as NULL when calling this function
    * @return array of strings, one entry per trace line
    */
    function jTraceEx ( $e , $seen = null ) <
    $starter = $seen ? ‘Caused by: ‘ : » ;
    $result = array();
    if (! $seen ) $seen = array();
    $trace = $e -> getTrace ();
    $prev = $e -> getPrevious ();
    $result [] = sprintf ( ‘%s%s: %s’ , $starter , get_class ( $e ), $e -> getMessage ());
    $file = $e -> getFile ();
    $line = $e -> getLine ();
    while ( true ) <
    $current = » $file : $line » ;
    if ( is_array ( $seen ) && in_array ( $current , $seen )) <
    $result [] = sprintf ( ‘ . %d more’ , count ( $trace )+ 1 );
    break;
    >
    $result [] = sprintf ( ‘ at %s%s%s(%s%s%s)’ ,
    count ( $trace ) && array_key_exists ( ‘class’ , $trace [ 0 ]) ? str_replace ( ‘\’ , ‘.’ , $trace [ 0 ][ ‘class’ ]) : » ,
    count ( $trace ) && array_key_exists ( ‘class’ , $trace [ 0 ]) && array_key_exists ( ‘function’ , $trace [ 0 ]) ? ‘.’ : » ,
    count ( $trace ) && array_key_exists ( ‘function’ , $trace [ 0 ]) ? str_replace ( ‘\’ , ‘.’ , $trace [ 0 ][ ‘function’ ]) : ‘(main)’ ,
    $line === null ? $file : basename ( $file ),
    $line === null ? » : ‘:’ ,
    $line === null ? » : $line );
    if ( is_array ( $seen ))
    $seen [] = » $file : $line » ;
    if (! count ( $trace ))
    break;
    $file = array_key_exists ( ‘file’ , $trace [ 0 ]) ? $trace [ 0 ][ ‘file’ ] : ‘Unknown Source’ ;
    $line = array_key_exists ( ‘file’ , $trace [ 0 ]) && array_key_exists ( ‘line’ , $trace [ 0 ]) && $trace [ 0 ][ ‘line’ ] ? $trace [ 0 ][ ‘line’ ] : null ;
    array_shift ( $trace );
    >
    $result = join ( «n» , $result );
    if ( $prev )
    $result .= «n» . jTraceEx ( $prev , $seen );

    return $result ;
    >
    ?>

    Here’s the example code:
    class A <
    public function exc () <
    throw new Exception ( ‘Thrown from class A’ ); // >
    >

    class B <
    public function exc () <
    try <
    $a = new A ;
    $a -> exc (); // >
    catch( Exception $e1 ) <
    throw new Exception ( ‘Thrown from class B’ , 0 , $e1 ); // >
    >
    >
    class C <
    public function doexc () <
    $this -> exc (); // >
    public function exc () <
    try <
    $b = new B ;
    $b -> exc (); // >
    catch( Exception $e1 ) <
    throw new Exception ( ‘Thrown from class C’ , 0 , $e1 ); // >
    >
    >

    function fail2 () <
    $c = new C ;
    $c -> doexc (); // >

    function fail1 () <
    fail2 (); // >

    try <
    fail1 (); // >
    catch( Exception $e ) <
    echo jTraceEx ( $e );
    >
    ?>

    Источник

    set_exception_handler

    (PHP 5, PHP 7, PHP 8)

    set_exception_handler — Задаёт пользовательский обработчик исключений

    Описание

    Задаёт обработчик по умолчанию для случаев, когда исключение выброшено вне блока try/catch. После вызова callback выполнение будет остановлено.

    Список параметров

    Функция, вызываемая при возникновении неперехваченного исключения. Эта функция-обработчик должна принимать один параметр, которым будет объект выброшенного исключения Throwable . И Error и Exception реализуют интерфейс Throwable . Сигнатура обработчика:

    В качестве этого аргумента можно передать null . В этом случае обработчик вернётся к своему изначальному состоянию.

    Возвращаемые значения

    Возвращает ранее определённый обработчик исключений или null в случае ошибки. Если предыдущих обработчиков определено не было, то также возвращается null .

    Примеры

    Пример #1 Пример использования set_exception_handler()

    function exception_handler ( Throwable $exception ) <
    echo «Неперехваченное исключение: » , $exception -> getMessage (), «n» ;
    >

    throw new Exception ( ‘Неперехваченное исключение’ );
    echo «Не выполненоn» ;
    ?>

    Смотрите также

    • restore_exception_handler() — Восстанавливает предыдущий обработчик исключений
    • restore_error_handler() — Восстанавливает предыдущий обработчик ошибок
    • error_reporting() — Задаёт, какие ошибки PHP попадут в отчёт
    • Исключения PHP 5

    User Contributed Notes 18 notes

    Things you should be aware of:

    An exception handler handles exceptions that were not caught before. It is the nature of an exception that it discontinues execution of your program — since it declares an exceptional situation in which the program cannot continue (except you catch it).

    Since it has not been catched your code signals it is not being aware of the situation and cant go on.

    This implies: returning to the script is simply impossible when the exception handler has already been called, since an uncaught exception is not a notice. use your own debug- or notice-log-system for things like that.

    Furthermore: While is is still possible to call functions from your script, since the exception handler has already been called exceptions bubbling from that piece of code won’t trigger the exception handler again. php will die without leaving any information apart form «uncaught exception with unknown stack frame». So if you call functions from your script, make sure that you catch any exceptions that possibly occur via try..catch inside the exception handler.

    For those of you who misinterpreted the essential meaning of the exception handler: it’s only use is to handle the abortion of your script gracefully, e.g. in a project like facebook or wikipedia: render a nice error page, eventually hiding information which shall not leak into the public (instead you may want to write to your log or mail the sys-admin or stuff like that).

    In other words: Redirecting all php-errors form an error-handler using exceptions — including notices — is a very dumb idea, if you do not intend having your script aborted everytime you didn’t set a variable (for example).

    As of PHP 7.4, an exception thrown within the user-defined shutdown function can be caught by the user-defined exception handler.

    (
    function( $level , $error , $file , $line ) <
    if( 0 === error_reporting ()) <
    return false ;
    >
    throw new ErrorException ( $error , — 1 , $level , $file , $line );
    >,
    E_ALL
    );

    register_shutdown_function (function() <
    $error = error_get_last ();
    if( $error ) <
    throw new ErrorException ( $error [ ‘message’ ], — 1 , $error [ ‘type’ ], $error [ ‘file’ ], $error [ ‘line’ ]);
    >
    >);

    set_exception_handler (function( $exception ) <
    // . more code .
    >);

    If you want a class instance to handle the exception, this is how you do it :

    class example <
    public function __construct () <
    @ set_exception_handler (array( $this , ‘exception_handler’ ));
    throw new Exception ( ‘DOH!!’ );
    >

    public function exception_handler ( $exception ) <
    print «Exception Caught: » . $exception -> getMessage () . «n» ;
    >
    >

    $example = new example ;

    ?>

    See the first post (Sean’s) for a static example. As Sean points out, the exception_handler function must be declared public.

    A behaviour not documented or discussed enough, yet pretty common is that is that if an exception is thrown from the global exception handler then a fatal error occurs (Exception thrown without a stack frame). That is, if you define your own global exception handler by calling set_exception_handler() and you throw an exception from inside it then this fatal error occurs. It is only natural though, as the callback defined by set_exception_handler() is only called on uncaught (unhandled) exceptions so if you throw one from there then you get this fatal error as there is no exception handler left (you override the php internal one by calling set_exception_handler()), hence no stack frame for it.

    function myExceptionHandler ( Exception $ex )
    <
    throw $ex ;
    >

    throw new Exception ( «This should cause a fatal error and this message will be lost» );

    ?>

    Will cause a Fatal error: Exception thrown without a stack frame

    If you skip/comment the set_exception_handler(«. «) line then the internal PHP global handler will catch the exception and output the exception message and trace (as string) to the browser, allowing you to at least see the exception message.

    While it is a very good idea to always define your own global exception handler by using the set_exception_handler() function, you should pay attention and never throw an exception from it (or if you do then catch it).

    Finally, every serious coder should use an IDE with debugging capabilities. Tracking down an error like this becomes a trivial matter by using simple debugging «Step into» commands (I for one recommend Zend IDE v5.2 at the moment of this writing). I have seen numerous messages on the internet with people wondering why this message pops up.

    p.s. Other causes for this error which are somehow unrelated to this is when you throw an exception from a destructor (the reasons behind that are similar though, the global handler might no longer exist due to the php engine shutting the page down).

    On GNU/Linux, When an exception handler is called, PHP will end with exit status code 0 instead of 255.

    You can change the exit status code with an exit() call at the end of your custom error handler.

    If you’re handling sensitive data and you don’t want exceptions logging details such as variable contents when you throw them, you may find yourself frustratedly looking for the bits and pieces that make up a normal stack trace output, so you can retain its legibility but just alter a few things. In that case, this may help you:

    function exceptionHandler ( $exception ) <

    // these are our templates
    $traceline = «#%s %s(%s): %s(%s)» ;
    $msg = «PHP Fatal error: Uncaught exception ‘%s’ with message ‘%s’ in %s:%snStack trace:n%sn thrown in %s on line %s» ;

    // alter your trace as you please, here
    $trace = $exception -> getTrace ();
    foreach ( $trace as $key => $stackPoint ) <
    // I’m converting arguments to their type
    // (prevents passwords from ever getting logged as anything other than ‘string’)
    $trace [ $key ][ ‘args’ ] = array_map ( ‘gettype’ , $trace [ $key ][ ‘args’ ]);
    >

    // build your tracelines
    $result = array();
    foreach ( $trace as $key => $stackPoint ) <
    $result [] = sprintf (
    $traceline ,
    $key ,
    $stackPoint [ ‘file’ ],
    $stackPoint [ ‘line’ ],
    $stackPoint [ ‘function’ ],
    implode ( ‘, ‘ , $stackPoint [ ‘args’ ])
    );
    >
    // trace always ends with


    $result [] = ‘#’ . ++ $key . ‘
    ‘ ;

    // write tracelines into main template
    $msg = sprintf (
    $msg ,
    get_class ( $exception ),
    $exception -> getMessage (),
    $exception -> getFile (),
    $exception -> getLine (),
    implode ( «n» , $result ),
    $exception -> getFile (),
    $exception -> getLine ()
    );

    // log or echo as you please
    error_log ( $msg );
    >

    ?>

    If you’re not a fan of sprintf() or the duplicate $exception->getFile() and $exception->getLine() calls you can of course replace that as you like — consider this a mere compilation of the parts.

    Hey all, i’ve just started to use the exception suite instead of the normal PHP error suite. For those of you looking for an object orientated way to do this without looking down at Glen and Sean’s examples (Lesson 1: ALWAYS read the logs!), here you go:

    class NewException extends Exception
    <
    public function __construct ( $message , $code = NULL )
    <
    parent :: __construct ( $message , $code );
    >

    public function __toString ()
    <
    return «Code: » . $this -> getCode () . «
    Message: » . htmlentities ( $this -> getMessage ());
    >

    public function getException ()
    <
    print $this ; // This will print the return from the above method __toString()
    >

    public static function getStaticException ( $exception )
    <
    $exception -> getException (); // $exception is an instance of this class
    >
    >

    set_exception_handler (array( «NewException» , «getStaticException» ));
    throw new NewException ( «Catch me. » , 69 );

    ?>

    Let me know if i’m missing something obvious as I left my glasses at home and I just came back from the Melbourne cup (If I won then I wouldn’t be at work still!).

    Your exception handler is configured to be the handler for all exceptions, yet if a basic ‘Exception’ is thrown, your static method will error because ‘Exception’s do not have ‘getException’. Because of this I don’t see a real purpose to making the uncaught handler a class that extends Exception.

    I do like the idea of using static methods of a general Exception handling class.

    class ExceptionHandler <
    public static function printException ( Exception $e )
    <
    print ‘Uncaught ‘ . get_class ( $e ). ‘, code: ‘ . $e -> getCode () . «
    Message: » . htmlentities ( $e -> getMessage ()). «n» ;
    >

    public static function handleException ( Exception $e )
    <
    self :: printException ( $e );
    >
    >

    set_exception_handler (array( «ExceptionHandler» , «handleException» ));

    class NewException extends Exception <>
    try <
    throw new NewException ( «Catch me once» , 1 );
    > catch ( Exception $e ) <
    ExceptionHandler :: handleException ( $e );
    >

    throw new Exception ( «Catch me twice» , 2 );
    ?>

    Gives:
    Uncaught NewException, code: 1
    Message: Catch me once
    Uncaught Exception, code: 2
    Message: Catch me twice

    There are much more interesting things that can be done like reformating and optionally displaying or emailing them. But this class acts a nice container for those functions.

    Using the ‘set_exception_handler’ function within a class, the defined ‘exception_handler’ method must be declared as ‘public’ (preferrable ‘public static’ if you use the «array(‘example’, ‘exception_handler’)» syntax).

    class example <
    public function __construct () <
    @ set_exception_handler (array( ‘example’ , ‘exception_handler’ ));
    throw new Exception ( ‘DOH!!’ );
    >

    public static function exception_handler ( $exception ) <
    print «Exception Caught: » . $exception -> getMessage () . «n» ;
    >
    >

    $example = new example ;

    echo «Not Executedn» ;
    ?>

    Declaring the ‘exception_handler’ function as ‘private’ causes a FATAL ERROR.

    [derick: red. updated statement about static a bit]

    For those of you wanting to convert PHP errors to ErrorExceptions (useful), but frustrated with the script being halted on every E_NOTICE et al. In your error handler, simply create the ErrorException, and then either throw it (script halted), or pass the object directly (script continues) to your exception handler function.

    const EXIT_ON_ALL_PHP_ERRORS = false ; // or true

    function proc_error ( $errno , $errstr , $errfile , $errline )
    <
    $e = new ErrorException ( $errstr , 0 , $errno , $errfile , $errline );

    if ( EXIT_ON_ALL_PHP_ERRORS ) <
    throw $e ; // This will halt your script.
    > else <
    proc_exception ( $e ); // This will let it continue.
    >
    >

    set_error_handler ( «proc_error» );
    set_exception_handler ( «proc_exception» );
    ?>

    You could further customize the error severity level (from $errno, match bitmasks with error level constants) at which the script is halted or allowed to continue. The above simply allows passthru for PHP errors’ default behavior.

    If your exception handler is receiving both error ErrorExceptions (with severity level etc.) and other types of uncaught Exceptions, then use a if ( $e instanceof ErrorException ) < // . >?> condition to deal with the variance. I had to further wrap it into a try < $errno = $e ->getSeverity (); > catch ( Exception $x ) < $errno = 0 >. ?> , because some EE-s were mysteriously lacking the severity level property (yet to dig in and find out why).

    Thanks to mastabog we know that throwing an exception within the exception handler will trigger a fatal error and a debugging nightmare. To avoid throwing an exception within there should be easy.

    However, if you use a custom error handler to convert errors to ErrorExceptions suddenly there are a multitude of new ways to accidentally throw exceptions within the exception handler.

    function error_handler ( $code , $message , $file , $line )
    <
    if ( 0 == error_reporting ())
    <
    return;
    >
    throw new ErrorException ( $message , 0 , $code , $file , $line );
    >
    function exception_handler ( $e )
    <
    // . normal exception stuff goes here
    print $undefined ; // This is the underlying problem
    >
    set_error_handler ( «error_handler» );
    set_exception_handler ( «exception_handler» );
    throw new Exception ( «Just invoking the exception handler» );
    ?>
    Output: Fatal error: Exception thrown without a stack frame in Unknown on line 0

    The best way I have found to avoid this is to wrap up everything in the exception handler in a try/catch block.
    function exception_handler ( $e )
    <
    try
    <
    // . normal exception stuff goes here
    print $undefined ; // This is the underlying problem
    >
    catch ( Exception $e )
    <
    print get_class ( $e ). » thrown within the exception handler. Message: » . $e -> getMessage (). » on line » . $e -> getLine ();
    >
    >
    ?>
    Output: ErrorException thrown within the exception handler. Message: Undefined variable: undefined on line 14

    This speeds up debugging and offers some scalability to any other exceptions accidentally thrown within the exception handler.

    Another solution is to restore the error handler at the beginning of the exception handler. While this is a silver bullet in terms of avoiding the ErrorExceptions, debugging messages then rely on the error_reporting() level and the display_errors directive. Why mention this? It might be preferable for production code since we care more about hiding errors from users than convenient debugging messages.

    Источник

  • Ошибка PHP

    Это лучший отчет об ошибках для PHP, написанный на PHP. Никаких дополнительных расширений не требуется!

    Тривиально использовать, когда все ошибки отображаются в браузере для обычных запросов AJAXy (в приостановленном состоянии). Затем все ошибки предоставляют вам обратную трассировку и контекст кода по всей трассировке стека, включая аргументы функций, переменные сервера.

    Все, что вам нужно сделать, это включить один единственный файл и вызвать функцию (в начиная с вашего кода), например

    require('php_error.php');
    php_errorreportErrors();
    

    Смотрите скриншоты:

    PHP Error | Improve Error Reporting for PHP - screenshot of backtrace
    PHP Error | Improve Error Reporting for PHP - screenshot of backtrace
    PHP Error | Improve Error Reporting for PHP - screenshot of backtrace

    Домашняя страница: http://phperror.net/

    GitHub: https://github.com/JosephLenton/PHP-Error

    Моя вилка (с дополнительными исправлениями): https://github.com/kenorb-contrib/PHP-Error

    Отладка PHP класс

    Полный класс отладчика PHP с поддержкой исключений, ошибок, предупреждений (от пользователя), строки кода и флаги выделения.

    Пример использования:

     <?php
            include( dirname(dirname(__FILE__))  . '/src/Debug.php' );
            //Catch all
            Debug::register();
    
            //Generate an errors
            if( this_function_does_not_exists( ) )
            {
                return false;
            }
        ?>
    

    Обработка ошибок в PHP

    В приведенном ниже примере показана обработка внутренних исключений путем запуска ошибок и обработки их с помощью определяемой пользователем функции:

    Более короткий путь (PHP):

    <?php
    function e($number, $msg, $file, $line, $vars) {
       print_r(debug_backtrace());
       die();
    }
    set_error_handler('e');
    

    Более длинный путь (PHP):

    // set to the user defined error handler
    $old_error_handler = set_error_handler("myErrorHandler");
    
    // error handler function
    function myErrorHandler($errno, $errstr, $errfile, $errline)
    {
        if (!(error_reporting() & $errno)) {
            // This error code is not included in error_reporting
            return;
        }
    
        switch ($errno) {
        case E_USER_ERROR:
            echo "<b>My ERROR</b> [$errno] $errstr<br />n";
            echo "  Fatal error on line $errline in file $errfile";
            echo ", PHP " . PHP_VERSION . " (" . PHP_OS . ")<br />n";
            echo "Aborting...<br />n";
            var_dump(debug_backtrace());
            exit(1);
            break;
    
        case E_USER_WARNING:
            echo "<b>My WARNING</b> [$errno] $errstr<br />n";
            break;
    
        case E_USER_NOTICE:
            echo "<b>My NOTICE</b> [$errno] $errstr<br />n";
            break;
    
        default:
            echo "Unknown error type: [$errno] $errstr<br />n";
            break;
        }
    
        /* Don't execute PHP internal error handler */
        return true;
    }
    

    См.: http://www.php.net/manual/en/function.set-error-handler.php

    Примечание: Одновременно может быть только одно исключение ошибки. Когда вы вызываете функцию set_error_handler(), она возвращает имя старого обработчика ошибок. Вы можете сохранить это и вызвать его самостоятельно из своего обработчика ошибок, что позволит вам иметь несколько обработчиков ошибок.


    Xdebug

    Для более продвинутого решения вы можете использовать расширение xdebug для PHP.

    По умолчанию при загрузке XDebug он должен автоматически показывать вам обратный путь в случае любой фатальной ошибки. Или вы отслеживаете в файле (xdebug.auto_trace), чтобы имейте очень большую обратную трассировку всего запроса или выполните профилирование (xdebug.profiler_enable) или другие настройки . Если файл трассировки слишком велик, вы можете использовать xdebug_start_trace() и xdebug_stop_trace() для сброса частичной трассировки.

    Установка

    Использование PECL:

    pecl install xdebug
    

    В Linux:

    sudo apt-get install php5-xdebug
    

    На Mac (с доморощенным):

    brew tap josegonzalez/php
    brew search xdebug
    php53-xdebug
    

    Пример конфигурации шахты:

    [xdebug]
    
    ; Extensions
    extension=xdebug.so
    ; zend_extension="/YOUR_PATH/php/extensions/no-debug-non-zts-20090626/xdebug.so"
    ; zend_extension="/Applications/MAMP/bin/php/php5.3.20/lib/php/extensions/no-debug-non-zts-20090626/xdebug.so" ; MAMP
    
    ; Data
    xdebug.show_exception_trace=1       ; bool: Show a stack trace whenever an exception is raised.
    xdebug.collect_vars = 1             ; bool: Gather information about which variables are used in a certain scope.
    xdebug.show_local_vars=1            ; int: Generate stack dumps in error situations.
    xdebug.collect_assignments=1        ; bool: Controls whether Xdebug should add variable assignments to function traces.
    xdebug.collect_params=4             ; int1-4: Collect the parameters passed to functions when a function call is recorded.
    xdebug.collect_return=1             ; bool: Write the return value of function calls to the trace files.
    xdebug.var_display_max_children=256 ; int: Amount of array children and object's properties are shown.
    xdebug.var_display_max_data=1024    ; int: Max string length that is shown when variables are displayed.
    xdebug.var_display_max_depth=3      ; int: How many nested levels of array/object elements are displayed.
    xdebug.show_mem_delta=0             ; int: Show the difference in memory usage between function calls.
    
    ; Trace
    xdebug.auto_trace=0                 ; bool: The tracing of function calls will be enabled just before the script is run.
    xdebug.trace_output_dir="/var/log/xdebug" ; string: Directory where the tracing files will be written to.
    xdebug.trace_output_name="%H%R-%s-%t"     ; string: Name of the file that is used to dump traces into.
    
    ; Profiler
    xdebug.profiler_enable=0            ; bool: Profiler which creates files read by KCacheGrind.
    xdebug.profiler_output_dir="/var/log/xdebug"  ; string: Directory where the profiler output will be written to.
    xdebug.profiler_output_name="%H%R-%s-%t"      ; string: Name of the file that is used to dump traces into.
    xdebug.profiler_append=0            ; bool: Files will not be overwritten when a new request would map to the same file.
    
    ; CLI
    xdebug.cli_color=1                  ; bool: Color var_dumps and stack traces output when in CLI mode.
    
    ; Remote debugging
    xdebug.remote_enable=off            ; bool: Try to contact a debug client which is listening on the host and port.
    xdebug.remote_autostart=off         ; bool: Start a remote debugging session even GET/POST/COOKIE variable is not present.
    xdebug.remote_handler=dbgp          ; select: php3/gdb/dbgp: The DBGp protocol is the only supported protocol.
    xdebug.remote_host=localhost        ; string: Host/ip where the debug client is running.
    xdebug.remote_port=9000             ; integer: The port to which Xdebug tries to connect on the remote host.
    xdebug.remote_mode=req              ; select(req,jit): Selects when a debug connection is initiated.
    xdebug.idekey="xdebug-cli"          ; string: IDE Key Xdebug which should pass on to the DBGp debugger handler.
    xdebug.remote_log="/var/log/xdebug.log" ; string: Filename to a file to which all remote debugger communications are logged.
    

    Drupal 6 и 7

    С помощью Devel включено:

    /**
     * Implements hook_watchdog().
     */
    function foo_watchdog($log_entry) {
      if ($log_entry['type'] == 'php' && $log_entry['severity'] <= WATCHDOG_WARNING) {
        function_exists('dd') && dd(debug_backtrace());
      }
    }
    

    Вышеуказанная функция будет регистрировать обратные следы каждой ошибки во временном файле (/tmp/drupal_debug.txt по умолчанию).

    Или найдите файл с помощью: drush eval "echo file_directory_temp() . '/drupal_debug.txt'.

    Без включенной разработки используйте подход старой школы: var_dump(debug_backtrace()); вместо dd().

    Не совершает ошибок только тот, кто ничего не делает, и мы тому пример — сидим и трудимся не покладая рук, читаем Хабр :)

    В этой статье я поведу свой рассказа об ошибках в PHP, и о том как их обуздать.

    Перед тем как приручать ошибки, я бы рекомендовал изучить каждый вид и отдельно обратить внимание на самых ярких представителей.

    Чтобы ни одна ошибка не ушла незамеченной потребуется включить отслеживание всех ошибок с помощью функции error_reporting(), а с помощью директивы display_errors включить их отображение:

    Самый грозный вид ошибок — фатальные, они могут возникнуть как при компиляции, так и при работе парсера или PHP-скрипта, выполнение скрипта при этом прерывается.

    Это ошибка появляется, когда вы допускаете грубую ошибку синтаксиса и интерпретатор PHP не понимает, что вы от него хотите, например если не закрыли фигурную или круглую скобочку:

    Отмечу один важный момент — код файла, в котором вы допустили parse error не будет выполнен, следовательно, если вы попытаетесь включить отображение ошибок в том же файле, где возникла ошибка парсера то это не сработает:

    Это ошибка появляется, когда PHP понял что вы хотите, но сделать сие не получилось ввиду ряда причин. Эта ошибка так же прерывает выполнение скрипта, при этом код до появления ошибки сработает:

    Было брошено исключение (что это за зверь, расскажу немного погодя), но не было обработано:

    Отсутствия свободной памяти (больше, чем прописано в директиве memory_limit) или ещё чего-нить подобного:

    Рекурсивный вызов функции. В данном примере он закончился на 256-ой итерации, ибо так прописано в настройках xdebug (да, данная ошибка может проявиться в таком виде только при включении xdebug расширения):

    Данный вид не прерывает выполнение скрипта, но именно их обычно находит тестировщик. Именно такие ошибки доставляют больше всего хлопот начинающим разработчикам.

    Частенько встречается, когда подключаешь файл с использованием include, а его не оказывается на сервере или вы ошиблись указывая путь к файлу:

    Это самые распространенные ошибки, мало того, есть любители отключать вывод ошибок и клепают их целыми днями. Возникают при целом ряде тривиальных ошибок.

    Для избежания подобных ошибок — будьте внимательней, и если вам IDE подсказывает о чём-то — не игнорируйте её:

    Это ошибки, которые научат вас писать код правильно, чтобы не было стыдно, тем более IDE вам эти ошибки сразу показывает. Вот например, если вызвали не статический метод как статику, то код будет работать, но это как-то неправильно, и возможно появление серьёзных ошибок, если в дальнейшем метод класса будет изменён, и появится обращение к $this:

    Данный тип ошибок актуален для PHP версии 5.6, и практически все их выпилили из
    7-ки. Почитать подробней можно в соответствующей RFC. Если кто знает где ещё остались данные ошибки, то напишите в комментариях</blockquote E_DEPRECATED

    Так PHP будет ругаться, если вы используете устаревшие функции (т.е. те, что помечены как deprecated, и в следующем мажорном релизе их не будет):

    /**
     * Deprecated: Function split() is deprecated
     */
    // данная функция, удалена из PHP 7.0
    // считается устаревшей с PHP 5.3
    split(',', 'a,b');
    

    В моём редакторе подобные функции будут зачёркнуты:

    PHP E_DEPRECATED in PHPStorm

    Пользовательские

    Этот вид, которые «разводит» сам разработчик кода, я уже давно их не встречал, и не рекомендую вам ими злоупотреблять:

    • E_USER_ERROR — критическая ошибка
    • E_USER_WARNING — не критическая ошибка
    • E_USER_NOTICE — сообщения которые не являются ошибками

    Отдельно стоит отметить E_USER_DEPRECATED — этот вид всё ещё используется очень часто для того, чтобы напомнить программисту, что метод или функция устарели и пора переписать код без использования оной. Для создания этой и подобных ошибок используется функция trigger_error():

    /**
     * @deprecated Deprecated since version 1.2, to be removed in 2.0
     */
    function generateToken() {
        trigger_error('Function `generateToken` is deprecated, use class `Token` instead', E_USER_DEPRECATED);
        // ...
        // code ...
        // ...
    }
    

    Теперь, когда вы познакомились с большинством видов и типов ошибок, пора озвучить небольшое пояснение по работе директивы display_errors:

    • если display_errors = on, то в случае ошибки браузер получит html c текстом ошибки и кодом 200
    • если же display_errors = off, то для фатальных ошибок код ответа будет 500 и результат не будет возвращён пользователю, для остальных ошибок — код будет работать неправильно, но никому об этом не расскажет

    Приручение

    Для работы с ошибками в PHP существует 3 функции:

    • set_error_handler() — устанавливает обработчик для ошибок, которые не обрывают работу скрипта (т.е. для не фатальных ошибок)
    • error_get_last() — получает информацию о последней ошибке
    • register_shutdown_function() — регистрирует обработчик который будет запущен при завершении работы скрипта. Данная функция не относится непосредственно к обработчикам ошибок, но зачастую используется именно для этого

    Теперь немного подробностей об обработке ошибок с использованием set_error_handler(), в качестве аргументов данная функция принимает имя функции, на которую будет возложена миссия по обработке ошибок и типы ошибок которые будут отслеживаться. Обработчиком ошибок может так же быть методом класса, или анонимной функцией, главное, чтобы он принимал следующий список аргументов:

    • $errno — первый аргумент содержит тип ошибки в виде целого числа
    • $errstr — второй аргумент содержит сообщение об ошибке
    • $errfile — необязательный третий аргумент содержит имя файла, в котором произошла ошибка
    • $errline — необязательный четвертый аргумент содержит номер строки, в которой произошла ошибка
    • $errcontext — необязательный пятый аргумент содержит массив всех переменных, существующих в области видимости, где произошла ошибка

    В случае если обработчик вернул true, то ошибка будет считаться обработанной и выполнение скрипта продолжится, иначе — будет вызван стандартный обработчик, который логирует ошибку и в зависимости от её типа продолжит выполнение скрипта или завершит его. Вот пример обработчика:

    <?php
    // включаем отображение всех ошибок, кроме E_NOTICE
    error_reporting(E_ALL & ~E_NOTICE);
    ini_set('display_errors', 1);
    
    // наш обработчик ошибок
    function myHandler($level, $message, $file, $line, $context) {
        // в зависимости от типа ошибки формируем заголовок сообщения
        switch ($level) {
            case E_WARNING:
                $type = 'Warning';
                break;
            case E_NOTICE:
                $type = 'Notice';
                break;
            default;
                // это не E_WARNING и не E_NOTICE
                // значит мы прекращаем обработку ошибки
                // далее обработка ложится на сам PHP
                return false;
        }
        // выводим текст ошибки
        echo "<h2>$type: $message</h2>";
        echo "<p><strong>File</strong>: $file:$line</p>";
        echo "<p><strong>Context</strong>: $". join(', $', 
        array_keys($context))."</p>";
        // сообщаем, что мы обработали ошибку, и дальнейшая обработка не требуется
        return true;
    }
    
    // регистрируем наш обработчик, он будет срабатывать на для всех типов ошибок
    set_error_handler('myHandler', E_ALL);
    

    У вас не получится назначить более одной функции для обработки ошибок, хотя очень бы хотелось регистрировать для каждого типа ошибок свой обработчик, но нет — пишите один обработчик, и всю логику отображения для каждого типа описывайте уже непосредственно в нём

    С обработчиком, который написан выше есть одна существенная проблема — он не ловит фатальные ошибки, и при таких ошибках вместо сайта пользователи увидят лишь пустую страницу, либо, что ещё хуже, сообщение об ошибке. Дабы не допустить подобного сценария следует воспользоваться функцией register_shutdown_function() и с её помощью зарегистрировать функцию, которая всегда будет выполняться по окончанию работы скрипта:

    function shutdown() {
        echo 'Этот текст будет всегда отображаться';
    }
    register_shutdown_function('shutdown');
    

    Данная функция будет срабатывать всегда!

    Но вернёмся к ошибкам, для отслеживания появления в коде ошибки воспользуемся функцией error_get_last(), с её помощью можно получить информацию о последней выявленной ошибке, а поскольку фатальные ошибки прерывают выполнение кода, то они всегда будут выполнять роль «последних»:

    function shutdown() {
        $error = error_get_last();
        if (
            // если в коде была допущена ошибка
            is_array($error) &&
            // и это одна из фатальных ошибок
            in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])
         ) {
            // очищаем буфер вывода (о нём мы ещё поговорим в последующих статьях)
            while (ob_get_level()) {
                ob_end_clean();
            }
            // выводим описание проблемы
            echo "Сервер находится на техническом обслуживании, зайдите позже";
        }
    }
    register_shutdown_function('shutdown');

    Хотел обратить внимание, что данный код хоть ещё и встречается для обработки ошибок, и вы возможно вы даже с ним столкнётесь, но он потерял актуальность начиная с 7-ой версии PHP. Что пришло на замену я расскажу чуть погодя.

    Задание

    Дополнить обработчик фатальных ошибок выводом исходного кода файла где была допущена ошибка, а так же добавьте подсветку синтаксиса выводимого кода.

    О прожорливости

    Проведём простой тест, и выясним — сколько драгоценных ресурсов кушает самая тривиальная ошибка:

    /**
     * Этот код не вызывает ошибок
     */
    
    // засекаем время выполнения скрипта
    $time= microtime(true);
    
    define('AAA', 'AAA');
    $arr = [];
    for ($i = 0; $i < 10000; $i++) {
        $arr[AAA] = $i;
    }
    
    printf('%f seconds <br/>', microtime(true) - $time);
    

    В результате запуска данного скрипта у меня получился вот такой результат:

    0.002867 seconds
    

    Теперь добавим ошибку в цикле:

    /**
     * Этот код содержит ошибку
     */
    
    // засекаем время выполнения скрипта
    $time= microtime(true);
    
    $arr = [];
    for ($i = 0; $i < 10000; $i++) {
        $arr[BBB] = $i; // тут используем константанту, которая у нас не объявлена
    }
    
    printf('%f seconds <br/>', microtime(true) - $time);
    

    Результат ожидаемо хуже, и на порядок (даже на два порядка!):

    0.263645 seconds
    

    Вывод однозначен — ошибки в коде приводят к лишней прожорливости скриптов — так что во время разработки и тестирования приложения включайте отображение всех ошибок!

    Тестирование проводил на различных версиях PHP и везде разница в десятки раз, так что пусть это будет ещё одним поводом для исправления всех ошибок в коде

    Где собака зарыта

    В PHP есть спец символ «@» — оператор подавления ошибок, его используют дабы не писать обработку ошибок, а положится на корректное поведение PHP в случае чего:

    <?php
    echo @UNKNOWN_CONSTANT;
    

    При этом обработчик ошибок указанный в set_error_handler() всё равно будет вызван, а факт того, что к ошибке было применено подавление можно отследить вызвав функцию error_reporting() внутри обработчика, в этом случае она вернёт 0.

    Если вы в такой способ подавляете ошибки, то это уменьшает нагрузку на процессор в сравнении с тем, если вы их просто скрываете (см. сравнительный тест выше), но в любом случае, подавление ошибок это зло

    Задание

    Проверьте, как влияет подавление ошибки с помощью @ на предыдущий пример с циклом.

    Исключения

    В эру PHP4 не было исключений (exceptions), всё было намного сложнее, и разработчики боролись с ошибками как могли, это было сражение не на жизнь, а на смерть… Окунуться в эту увлекательную историю противостояния можете в статье Исключительный код. Часть 1. Стоит ли её читать сейчас? Не могу дать однозначный ответ, лишь хочу заметить, что это поможет вам понять эволюцию языка, и раскроет всю прелесть исключений.

    Исключения — исключительные событие в PHP, в отличии от ошибок не просто констатируют наличие проблемы, а требуют от программиста дополнительных действий по обработке каждого конкретного случая.

    К примеру, скрипт должен сохранить какие-то данные в кеш файл, если что-то пошло не так (нет доступа на запись, нет места на диске), генерируется исключение соответствующего типа, а в обработчике исключений принимается решение — сохранить в другое место или сообщить пользователю о проблеме.

    Исключение — это объект класса Exception либо одного из многих его наследников, содержит текст ошибки, статус, а также может содержать ссылку на другое исключение которое стало первопричиной данного. Модель исключений в PHP схожа с используемыми в других языках программирования. Исключение можно инициировать (как говорят, «бросить») при помощи оператора throw, и можно перехватить («поймать») оператором catch. Код генерирующий исключение, должен быть окружен блоком try, для того чтобы можно было перехватить исключение. Каждый блок try должен иметь как минимум один соответствующий ему блок catch или finally:

    try {
        // код который может выбросить исключение
        if (random_int(0, 1)) {
            throw new Exception("One");
        }
        echo "Zero"
    } catch (Exception $e) {
        // код который может обработать исключение
        echo $e->getMessage();
    }
    

    В каких случаях стоит применять исключения:

    • если в рамках одного метода/функции происходит несколько операций которые могут завершиться неудачей
    • если используемый вами фреймворк или библиотека декларируют их использование

    Для иллюстрации первого сценария возьмём уже озвученный пример функции для записи данных в файл — помешать нам может очень много факторов, а для того, чтобы сообщить выше стоящему коду в чем именно была проблема необходимо создать и выбросить исключение:

    $directory = __DIR__ . DIRECTORY_SEPARATOR . 'logs';
    
    // директории может не быть
    if (!is_dir($directory)) {
        throw new Exception('Directory `logs` is not exists');
    }
    
    // может не быть прав на запись в директорию
    if (!is_writable($directory)) {
        throw new Exception('Directory `logs` is not writable');
    }
    
    // возможно кто-то уже создал файл, и закрыл к нему доступ
    if (!$file = @fopen($directory . DIRECTORY_SEPARATOR . date('Y-m-d') . '.log', 'a+')) {
        throw new Exception('System can't create log file');
    }
    
    fputs($file, date("[H:i:s]") . " donen");
    fclose($file);
    

    Соответственно ловить данные исключения будем примерно так:

    try {
        // код который пишет в файл
        // ...
    } catch (Exception $e) {
        // выводим текст ошибки
        echo "Не получилось: ". $e->getMessage();
    }
    

    В данном примере приведен очень простой сценарий обработки исключений, когда у нас любая исключительная ситуация обрабатывается на один манер. Но зачастую, различные исключения требуют различного подхода к обработке, и тогда следует использовать коды исключений и задать иерархию исключений в приложении:

    // исключения файловой системы
    class FileSystemException extends Exception {}
    
    // исключения связанные с директориями
    class DirectoryException extends FileSystemException {
        // коды исключений
        const DIRECTORY_NOT_EXISTS =  1;
        const DIRECTORY_NOT_WRITABLE = 2;
    }
    
    // исключения связанные с файлами
    class FileException extends FileSystemException {}
    

    Теперь, если использовать эти исключения то можно получить следующий код:

    try {
        // код который пишет в файл
        if (!is_dir($directory)) {
            throw new DirectoryException('Directory `logs` is not exists', DirectoryException::DIRECTORY_NOT_EXISTS);
        }
    
        if (!is_writable($directory)) {
            throw new DirectoryException('Directory `logs` is not writable', DirectoryException::DIRECTORY_NOT_WRITABLE);
        }
    
        if (!$file = @fopen($directory . DIRECTORY_SEPARATOR . date('Y-m-d') . '.log', 'a+')) {
            throw new FileException('System can't open log file');
        }
    
        fputs($file, date("[H:i:s]") . " donen");
        fclose($file);
    } catch (DirectoryException $e) {
        echo "С директорией возникла проблема: ". $e->getMessage();
    } catch (FileException $e) {
        echo "С файлом возникла проблема: ". $e->getMessage();
    } catch (FileSystemException $e) {
        echo "Ошибка файловой системы: ". $e->getMessage();
    } catch (Exception $e) {
        echo "Ошибка сервера: ". $e->getMessage();
    }
    

    Важно помнить, что Exception — это прежде всего исключительное событие, иными словами исключение из правил. Не нужно использовать их для обработки очевидных ошибок, к примеру, для валидации введённых пользователем данных (хотя тут не всё так однозначно). При этом обработчик исключений должен быть написан в том месте, где он будет способен его обработать. К примеру, обработчик для исключений вызванных недоступностью файла для записи должен быть в методе, который отвечает за выбор файла или методе его вызывающем, для того что бы он имел возможность выбрать другой файл или другую директорию.

    Так, а что будет если не поймать исключение? Вы получите «Fatal Error: Uncaught exception …». Неприятно.

    Чтобы избежать подобной ситуации следует использовать функцию set_exception_handler() и установить обработчик для исключений, которые брошены вне блока try-catch и не были обработаны. После вызова такого обработчика выполнение скрипта будет остановлено:

    // в качестве обработчика событий
    // будем использовать анонимную функцию
    set_exception_handler(function($exception) {
        /** @var Exception $exception */
        echo $exception->getMessage(), "<br/>n";
        echo $exception->getFile(), ':', $exception->getLine(), "<br/>n";
        echo $exception->getTraceAsString(), "<br/>n";
    });
    

    Ещё расскажу про конструкцию с использованием блока finally — этот блок будет выполнен вне зависимости от того, было выброшено исключение или нет:

    try {
        // код который может выбросить исключение
    } catch (Exception $e) {
        // код который может обработать исключение
        // если конечно оно появится
    } finally {
        // код, который будет выполнен при любом раскладе
    }
    

    Для понимания того, что это нам даёт приведу следующий пример использования блока finally:

    try {
        // где-то глубоко внутри кода
        // соединение с базой данных
        $handler = mysqli_connect('localhost', 'root', '', 'test');
    
        try {
            // при работе с БД возникла исключительная ситуация
            // ...
            throw new Exception('DB error');
        } catch (Exception $e) {
            // исключение поймали, обработали на своём уровне
            // и должны его пробросить вверх, для дальнейшей обработки
            throw new Exception('Catch exception', 0, $e);
        } finally {
            // но, соединение с БД необходимо закрыть
            // будем делать это в блоке finally
            mysqli_close($handler);
        }
    
        // этот код не будет выполнен, если произойдёт исключение в коде выше
        echo "Ok";
    } catch (Exception $e) {
        // ловим исключение, и выводим текст
        echo $e->getMessage();
        echo "<br/>";
        // выводим информацию о первоначальном исключении
        echo $e->getPrevious()->getMessage();
    }
    

    Т.е. запомните — блок finally будет выполнен даже в том случае, если вы в блоке catch пробрасываете исключение выше (собственно именно так он и задумывался).

    Для вводной статьи информации в самый раз, кто жаждет ещё подробностей, то вы их найдёте в статье Исключительный код ;)

    Задание

    Написать свой обработчик исключений, с выводом текста файла где произошла ошибка, и всё это с подсветкой синтаксиса, так же не забудьте вывести trace в читаемом виде. Для ориентира — посмотрите как это круто выглядит у whoops.

    PHP7 — всё не так, как было раньше

    Так, вот вы сейчас всю информацию выше усвоили и теперь я буду грузить вас нововведениями в PHP7, т.е. я буду рассказывать о том, с чем вы будете сталкиваться работая над современным PHP проектом. Ранее я вам рассказывал и показывал на примерах какой костыль нужно соорудить, чтобы отлавливать критические ошибки, так вот — в PHP7 это решили исправить, но? как обычно? завязались на обратную совместимость кода, и получили хоть и универсальное решение, но оно далеко от идеала. А теперь по пунктам об изменениях:

    1. при возникновении фатальных ошибок типа E_ERROR или фатальных ошибок с возможностью обработки E_RECOVERABLE_ERROR PHP выбрасывает исключение
    2. эти исключения не наследуют класс Exception (помните я говорил об обратной совместимости, это всё ради неё)
    3. эти исключения наследуют класс Error
    4. оба класса Exception и Error реализуют интерфейс Throwable
    5. вы не можете реализовать интерфейс Throwable в своём коде

    Интерфейс Throwable практически полностью повторяет нам Exception:

    interface Throwable
    {
        public function getMessage(): string;
        public function getCode(): int;
        public function getFile(): string;
        public function getLine(): int;
        public function getTrace(): array;
        public function getTraceAsString(): string;
        public function getPrevious(): Throwable;
        public function __toString(): string;
    }
    

    Сложно? Теперь на примерах, возьмём те, что были выше и слегка модернизируем:

    try {
        // файл, который вызывает ошибку парсера
        include 'e_parse_include.php';
    } catch (Error $e) {
        var_dump($e);
    }
    

    В результате ошибку поймаем и выведем:

    object(ParseError)#1 (7) {
        ["message":protected] => string(48) "syntax error, unexpected 'будет' (T_STRING)"
        ["string":"Error":private] => string(0) ""
        ["code":protected] => int(0)
        ["file":protected] => string(49) "/www/education/error/e_parse_include.php"
        ["line":protected] => int(4)
        ["trace":"Error":private] => array(0) { }
        ["previous":"Error":private] => NULL
    }
    

    Как видите — поймали исключение ParseError, которое является наследником исключения Error, который реализует интерфейс Throwable, в доме который построил Джек. Ещё есть множество других исключений, но не буду мучать — для наглядности приведу иерархию исключений:

    interface Throwable
    |- Exception implements Throwable
    |   |- ErrorException extends Exception
    |   |- ... extends Exception
    |   `- ... extends Exception
    `- Error implements Throwable
        |- TypeError extends Error
        |- ParseError extends Error
        |- ArithmeticError extends Error
        |  `- DivisionByZeroError extends ArithmeticError
        `- AssertionError extends Error
    

    И чуть-чуть деталей:

    TypeError — для ошибок, когда тип аргументов функции не совпадает с передаваемым типом:

    try {
        (function(int $one, int $two) {
            return;
        })('one', 'two');
    } catch (TypeError $e) {
        echo $e->getMessage();
    }
    

    ArithmeticError — могут возникнуть при математических операциях, к примеру когда результат вычисления превышает лимит выделенный для целого числа:

    try {
        1 << -1;
    } catch (ArithmeticError $e) {
        echo $e->getMessage();
    }
    

    DivisionByZeroError — ошибка деления на ноль:

    try {
        1 / 0;
    } catch (ArithmeticError $e) {
        echo $e->getMessage();
    }
    

    AssertionError — редкий зверь, появляется когда условие заданное в assert() не выполняется:

    ini_set('zend.assertions', 1);
    ini_set('assert.exception', 1);
    
    try {
        assert(1 === 0);
    } catch (AssertionError $e) {
        echo $e->getMessage();
    }
    

    При настройках production-серверов, директивы zend.assertions и assert.exception отключают, и это правильно

    Полный список предопределённых исключений вы найдёте в официальном мануале, там же иерархия SPL исключений.

    Задание

    Написать универсальный обработчик ошибок для PHP7, который будет отлавливать все возможные исключения.

    При написании данного раздела были использованы материалы из статьи Throwable Exceptions and Errors in PHP 7.

    Единообразие

    — Там ошибки, тут исключения, а можно это всё как-то до кучи сгрести?

    Да запросто, у нас же есть set_error_handler() и никто нам не запретит внутри оного обработчика бросить исключение:

    // Бросаем исключение вместо ошибок
    function errorHandler($severity, $message, $file = null, $line = null)
    {
        // Кроме случаев, когда мы подавляем ошибки с помощью @
        if (error_reporting() === 0) {
            return false;
        }
        throw new ErrorException($message, 0, $severity, $file, $line);
    }
    
    // Будем обрабатывать все-все ошибки
    set_error_handler('errorHandler', E_ALL);
    

    Но данный подход с PHP7 избыточен, со всем теперь справляется Throwable:

    try {
        /** ... **/
    } catch (Throwable $e) {
        // отображение любых ошибок и исключений
        echo $e->getMessage();
    }
    

    Отладка

    Иногда, для отладки кода, нужно отследить что происходило с переменной или объектом на определённом этапе, для этих целей есть функция debug_backtrace() и debug_print_backtrace() которые вернут историю вызовов функций/методов в обратном порядке:

    <?php
    function example() {
        echo '<pre>';
        debug_print_backtrace();
        echo '</pre>';
    }
    
    class ExampleClass {
        public static function method () {
            example();
        }
    }
    
    ExampleClass::method();
    

    В результате выполнения функции debug_print_backtrace() будет выведен список вызовов приведших нас к данной точке:

    #0  example() called at [/www/education/error/backtrace.php:10]
    #1  ExampleClass::method() called at [/www/education/error/backtrace.php:14]
    

    Проверить код на наличие синтаксических ошибок можно с помощью функции php_check_syntax() или же команды php -l [путь к файлу], но я не встречал использования оных.

    Assert

    Отдельно хочу рассказать о таком экзотическом звере как assert() в PHP. Собственно, этот кусочек можно рассматривать как мимикрию под контрактную методологию программирования, и дальше я расскажу вам как я никогда его не использовал :)

    Функция assert() поменяла своё поведение при переходе от версии 5.6 к 7.0, и ещё сильней всё поменялось в версии 7.2, так что внимательней читайте changelog’и PHP ;)

    Первый случай — это когда вам надо написать TODO прямо в коде, да так, чтобы точно не забыть реализовать заданный функционал:

    // включаем asserts в php.ini
    // zend.assertions=1
    assert(false, "Remove it!");
    

    В результате выполнения данного кода получим E_WARNING:

    Warning: assert(): Remove it! failed

    PHP7 можно переключить в режим exception, и вместо ошибки будет всегда появляться исключение AssertionError:

    // переключаем в режим «исключений»
    ini_set('assert.exception', 1);
    
    assert(false, "Remove it!");
    

    В результате ожидаемо получаем исключение AssertionError.

    При необходимости, можно выбрасывать произвольное исключение:

    assert(false, new Exception("Remove it!"));
    

    Я бы рекомендовал использовать метки @TODO, современные IDE отлично с ними работают, и вам не нужно будет прикладывать дополнительные усилия и ресурсы для работы с ними, хотя с ними велик соблазн «забить»

    Второй вариант использования — это создание некоего подобия TDD, но помните — это лишь подобие. Хотя, если сильно постараться, то можно получить забавный результат, который поможет в тестировании вашего кода:

    // callback-функция для вывода информации в браузер
    function backlog($script, $line, $code, $message) {
        echo $message;
    }
    
    // устанавливаем callback-функцию
    assert_options(ASSERT_CALLBACK, 'backlog');
    
    // отключаем вывод предупреждений
    assert_options(ASSERT_WARNING,  false);
    
    // пишем проверку и её описание
    assert(sqr(4) === 16, 'When I send integer, function should return square of it');
    
    // функция, которую проверяем
    function sqr($a) {
        return; // она не работает
    }
    

    Третий теоретический вариант — это подобие на контрактное программирование, когда вы описали правила использования своей библиотеки, но хотите точно убедится, что вас поняли правильно, и в случае чего сразу указать разработчику на ошибку (я вот даже не уверен, что правильно его понимаю, но пример кода вполне рабочий):

    /**
     * Настройки соединения должны передаваться в следующем виде
     *
     *     [
     *         'host' => 'localhost',
     *         'port' => 3306,
     *         'name' => 'dbname',
     *         'user' => 'root',
     *         'pass' => ''
     *     ]
     *
     * @param $settings
     */
    function setupDb ($settings) {
        // проверяем настройки
        assert(isset($settings['host']), 'Db `host` is required');
        assert(isset($settings['port']) && is_int($settings['port']), 'Db `port` is required, should be integer');
        assert(isset($settings['name']), 'Db `name` is required, should be integer');
    
        // соединяем с БД
        // ...
    }
    
    setupDb(['host' => 'localhost']);
    

    Если вас заинтересовали контракты, то специально для вас у меня есть ссылочка на фреймворк PhpDeal.

    Никогда не используйте assert() для проверки входных параметров, ведь фактически assert() интерпретирует первый параметр (ведёт себя как eval()), а это чревато PHP-инъекцией. И да, это правильное поведение, т.к. просто отключив assert’ы всё что передаётся внутрь будет проигнорировано, а если делать как в примере выше, то код будет выполняться, а внутрь отключенного assert’a будет передан булевый результат выполнения. А, и это поменяли в PHP 7.2 :)

    Если у вас есть живой опыт использования assert() — поделитесь со мной, буду благодарен. И да, вот вам ещё занимательно чтива по этой теме — PHP Assertions, с таким же вопросом в конце :)

    В заключение

    Я за вас напишу выводы из данной статьи:

    • Ошибкам бой — их не должно быть в вашем коде
    • Используйте исключения — работу с ними нужно правильно организовать и будет счастье
    • Assert — узнали о них, и хорошо

    P.S.

    Это репост из серии статей «PHP для начинающих»:

    • Сессия
    • Подключение файлов
    • Обработка ошибок

    Если у вас есть замечания по материалу статьи, или возможно по форме, то описывайте в комментариях суть, и мы сделаем данный материал ещё лучше.

    Спасибо Максиму Слесаренко за помощь в написании статьи.

    Пытаться отлаживать PHP, используя сообщения об ошибках по умолчанию только для текущей строки, ужасно. Как я могу заставить PHP создавать обратную трассировку (трассировку стека) при возникновении ошибок?

    Xdebug печатает таблицу backtrace об ошибках, и вам не нужно писать какой-либо PHP-код для его реализации.

    Недостатком является то, что вы должны установить его как расширение PHP.

    Мой скрипт для установки обработчика ошибок, который создает обратную трассировку:

     <?php function process_error_backtrace($errno, $errstr, $errfile, $errline, $errcontext) { if(!(error_reporting() & $errno)) return; switch($errno) { case E_WARNING : case E_USER_WARNING : case E_STRICT : case E_NOTICE : case E_USER_NOTICE : $type = 'warning'; $fatal = false; break; default : $type = 'fatal error'; $fatal = true; break; } $trace = array_reverse(debug_backtrace()); array_pop($trace); if(php_sapi_name() == 'cli') { echo 'Backtrace from ' . $type . ' '' . $errstr . '' at ' . $errfile . ' ' . $errline . ':' . "n"; foreach($trace as $item) echo ' ' . (isset($item['file']) ? $item['file'] : '<unknown file>') . ' ' . (isset($item['line']) ? $item['line'] : '<unknown line>') . ' calling ' . $item['function'] . '()' . "n"; } else { echo '<p class="error_backtrace">' . "n"; echo ' Backtrace from ' . $type . ' '' . $errstr . '' at ' . $errfile . ' ' . $errline . ':' . "n"; echo ' <ol>' . "n"; foreach($trace as $item) echo ' <li>' . (isset($item['file']) ? $item['file'] : '<unknown file>') . ' ' . (isset($item['line']) ? $item['line'] : '<unknown line>') . ' calling ' . $item['function'] . '()</li>' . "n"; echo ' </ol>' . "n"; echo '</p>' . "n"; } if(ini_get('log_errors')) { $items = array(); foreach($trace as $item) $items[] = (isset($item['file']) ? $item['file'] : '<unknown file>') . ' ' . (isset($item['line']) ? $item['line'] : '<unknown line>') . ' calling ' . $item['function'] . '()'; $message = 'Backtrace from ' . $type . ' '' . $errstr . '' at ' . $errfile . ' ' . $errline . ': ' . join(' | ', $items); error_log($message); } if($fatal) exit(1); } set_error_handler('process_error_backtrace'); ?> со <?php function process_error_backtrace($errno, $errstr, $errfile, $errline, $errcontext) { if(!(error_reporting() & $errno)) return; switch($errno) { case E_WARNING : case E_USER_WARNING : case E_STRICT : case E_NOTICE : case E_USER_NOTICE : $type = 'warning'; $fatal = false; break; default : $type = 'fatal error'; $fatal = true; break; } $trace = array_reverse(debug_backtrace()); array_pop($trace); if(php_sapi_name() == 'cli') { echo 'Backtrace from ' . $type . ' '' . $errstr . '' at ' . $errfile . ' ' . $errline . ':' . "n"; foreach($trace as $item) echo ' ' . (isset($item['file']) ? $item['file'] : '<unknown file>') . ' ' . (isset($item['line']) ? $item['line'] : '<unknown line>') . ' calling ' . $item['function'] . '()' . "n"; } else { echo '<p class="error_backtrace">' . "n"; echo ' Backtrace from ' . $type . ' '' . $errstr . '' at ' . $errfile . ' ' . $errline . ':' . "n"; echo ' <ol>' . "n"; foreach($trace as $item) echo ' <li>' . (isset($item['file']) ? $item['file'] : '<unknown file>') . ' ' . (isset($item['line']) ? $item['line'] : '<unknown line>') . ' calling ' . $item['function'] . '()</li>' . "n"; echo ' </ol>' . "n"; echo '</p>' . "n"; } if(ini_get('log_errors')) { $items = array(); foreach($trace as $item) $items[] = (isset($item['file']) ? $item['file'] : '<unknown file>') . ' ' . (isset($item['line']) ? $item['line'] : '<unknown line>') . ' calling ' . $item['function'] . '()'; $message = 'Backtrace from ' . $type . ' '' . $errstr . '' at ' . $errfile . ' ' . $errline . ': ' . join(' | ', $items); error_log($message); } if($fatal) exit(1); } set_error_handler('process_error_backtrace'); ?> 

    Предостережение: бессильно повлиять на различные «Ошибки PHP Fatal» , поскольку Zend в своей мудрости решил, что они будут игнорировать set_error_handler() . Таким образом, вы по-прежнему получаете бесполезные ошибки с окончательным местоположением.

    Ошибка PHP

    Это лучший отчет об ошибках для PHP, написанный на PHP. Никаких дополнительных расширений не требуется!

    Тривиально использовать, где все ошибки отображаются в браузере для обычных запросов AJAXy (в состоянии паузы). Затем все ошибки предоставляют вам обратную трассировку и контекст кода во всей трассировке стека, включая аргументы функции, переменные сервера.

    Все, что вам нужно сделать, это включить один файл и вызвать функцию (в начале кода), например

     require('php_error.php'); php_errorreportErrors(); 

    Смотрите скриншоты:

    Ошибка PHP | Улучшение отчетов об ошибках для PHP - снимок экрана backtrace Ошибка PHP | Улучшение отчетов об ошибках для PHP - снимок экрана backtrace Ошибка PHP | Улучшение отчетов об ошибках для PHP - снимок экрана backtrace

    Домашняя страница: http://phperror.net/

    GitHub: https://github.com/JosephLenton/PHP-Error

    Моя вилка (с дополнительными исправлениями): https://github.com/kenorb-contrib/PHP-Error

    Отладка PHP- класса

    Полный класс отладчика PHP с поддержкой Exception, Errors, Alerts (от пользователя), строки кода и флаги выделения.

    Пример использования:

      <?php include( dirname(dirname(__FILE__)) . '/src/Debug.php' ); //Catch all Debug::register(); //Generate an errors if( this_function_does_not_exists( ) ) { return false; } ?> 

    Обработка ошибок в PHP

    В приведенном ниже примере показана обработка внутренних исключений путем запуска ошибок и обработки их с помощью определенной пользователем функции:

    Более короткий путь (PHP):

     <?php function e($number, $msg, $file, $line, $vars) { print_r(debug_backtrace()); die(); } set_error_handler('e'); 

    Более длинный путь (PHP):

     // set to the user defined error handler $old_error_handler = set_error_handler("myErrorHandler"); // error handler function function myErrorHandler($errno, $errstr, $errfile, $errline) { if (!(error_reporting() & $errno)) { // This error code is not included in error_reporting return; } switch ($errno) { case E_USER_ERROR: echo "<b>My ERROR</b> [$errno] $errstr<br />n"; echo " Fatal error on line $errline in file $errfile"; echo ", PHP " . PHP_VERSION . " (" . PHP_OS . ")<br />n"; echo "Aborting...<br />n"; var_dump(debug_backtrace()); exit(1); break; case E_USER_WARNING: echo "<b>My WARNING</b> [$errno] $errstr<br />n"; break; case E_USER_NOTICE: echo "<b>My NOTICE</b> [$errno] $errstr<br />n"; break; default: echo "Unknown error type: [$errno] $errstr<br />n"; break; } /* Don't execute PHP internal error handler */ return true; } 

    См .: http://www.php.net/manual/en/function.set-error-handler.php

    Примечание. За один раз можно исключить только одну ошибку. Когда вы вызываете функцию set_error_handler (), она возвращает имя старого обработчика ошибок. Вы можете сохранить это и вызвать его самостоятельно из обработчика ошибок, что позволит вам иметь несколько обработчиков ошибок.


    XDebug

    Для более продвинутого решения вы можете использовать расширение XDebug для PHP.

    По умолчанию при загрузке XDebug он должен автоматически отображать обратную трассировку в случае любой фатальной ошибки. Или вы трассируете в файл (xdebug.auto_trace), чтобы иметь очень большую обратную трассировку всего запроса или выполнить профилирование (xdebug.profiler_enable) или другие настройки . Если файл трассировки слишком велик, вы можете использовать xdebug_start_trace () и xdebug_stop_trace () для сброса частичной трассировки.

    Монтаж

    Использование PECL:

     pecl install xdebug 

    В Linux:

     sudo apt-get install php5-xdebug 

    На Mac (с Homebrew):

     brew tap josegonzalez/php brew search xdebug php53-xdebug 

    Пример конфигурации шахты:

     [xdebug] ; Extensions extension=xdebug.so ; zend_extension="/YOUR_PATH/php/extensions/no-debug-non-zts-20090626/xdebug.so" ; zend_extension="/Applications/MAMP/bin/php/php5.3.20/lib/php/extensions/no-debug-non-zts-20090626/xdebug.so" ; MAMP ; Data xdebug.show_exception_trace=1 ; bool: Show a stack trace whenever an exception is raised. xdebug.collect_vars = 1 ; bool: Gather information about which variables are used in a certain scope. xdebug.show_local_vars=1 ; int: Generate stack dumps in error situations. xdebug.collect_assignments=1 ; bool: Controls whether Xdebug should add variable assignments to function traces. xdebug.collect_params=4 ; int1-4: Collect the parameters passed to functions when a function call is recorded. xdebug.collect_return=1 ; bool: Write the return value of function calls to the trace files. xdebug.var_display_max_children=256 ; int: Amount of array children and object's properties are shown. xdebug.var_display_max_data=1024 ; int: Max string length that is shown when variables are displayed. xdebug.var_display_max_depth=3 ; int: How many nested levels of array/object elements are displayed. xdebug.show_mem_delta=0 ; int: Show the difference in memory usage between function calls. ; Trace xdebug.auto_trace=0 ; bool: The tracing of function calls will be enabled just before the script is run. xdebug.trace_output_dir="/var/log/xdebug" ; string: Directory where the tracing files will be written to. xdebug.trace_output_name="%H%R-%s-%t" ; string: Name of the file that is used to dump traces into. ; Profiler xdebug.profiler_enable=0 ; bool: Profiler which creates files read by KCacheGrind. xdebug.profiler_output_dir="/var/log/xdebug" ; string: Directory where the profiler output will be written to. xdebug.profiler_output_name="%H%R-%s-%t" ; string: Name of the file that is used to dump traces into. xdebug.profiler_append=0 ; bool: Files will not be overwritten when a new request would map to the same file. ; CLI xdebug.cli_color=1 ; bool: Color var_dumps and stack traces output when in CLI mode. ; Remote debugging xdebug.remote_enable=off ; bool: Try to contact a debug client which is listening on the host and port. xdebug.remote_autostart=off ; bool: Start a remote debugging session even GET/POST/COOKIE variable is not present. xdebug.remote_handler=dbgp ; select: php3/gdb/dbgp: The DBGp protocol is the only supported protocol. xdebug.remote_host=localhost ; string: Host/ip where the debug client is running. xdebug.remote_port=9000 ; integer: The port to which Xdebug tries to connect on the remote host. xdebug.remote_mode=req ; select(req,jit): Selects when a debug connection is initiated. xdebug.idekey="xdebug-cli" ; string: IDE Key Xdebug which should pass on to the DBGp debugger handler. xdebug.remote_log="/var/log/xdebug.log" ; string: Filename to a file to which all remote debugger communications are logged. 

    Drupal 6 & 7

    С включенным Devel:

     /** * Implements hook_watchdog(). */ function foo_watchdog($log_entry) { if ($log_entry['type'] == 'php' && $log_entry['severity'] <= WATCHDOG_WARNING) { function_exists('dd') && dd(debug_backtrace()); } } 

    Выше функция будет регистрировать обратные трассы для каждой ошибки во временном файле ( /tmp/drupal_debug.txt по умолчанию).

    Или найдите файл через: drush eval "echo file_directory_temp() . '/drupal_debug.txt' .

    Без включенного var_dump(debug_backtrace()); используйте подход старой школы: var_dump(debug_backtrace()); вместо dd() .

    Я просто попытался установить переменную сеанса, содержащую содержимое debug_backtrace () в строке нарушения, а затем распечатать ее с помощью register_shutdown_function (). Работал как шарм.

    Вы можете использовать debug_backtrace

    В качестве расширений php debug есть Xdebug и PHP DBG . У каждого есть свои преимущества и недостатки.

    Ошибка PHP даст вам трассировку стека для ваших ошибок и намного красивее, чем xDebug.

    Он также будет работать и для аякс-запросов.

     $backtrace = debug_backtrace(); 

    я написал небольшую статью о возврате назад

    Вот как вы это делаете:

     set_error_handler(function($errorType){ if(error_reporting() & $errorType){ ?><pre><? debug_print_backtrace(); ?></pre><? } }) ; 

    Он требует PHP 5.3+, поскольку он использует закрытие. Если вам нужна более низкая поддержка PHP, просто конвертируйте ее в обычную функцию.

    set_error_handler() + debug_backtrace() + debug_print_backtrace() в PHP5

    Если вы не можете установить отладчик, тогда используйте эту функцию, соприкасающуюся с фатальной ошибкой, чтобы получить «фатальный стек». Проверьте приведенный ниже код и пример, который лучше объясняет, как его использовать:

     // Give an extra parameter to the filename // to save multiple log files function _fatalog_($extra = false) { static $last_extra; // CHANGE THIS TO: A writeable filepath in your system... $filepath = '/var/www/html/sites/default/files/fatal-'.($extra === false ? $last_extra : $extra).'.log'; if ($extra===false) { unlink($filepath); } else { // we write a log file with the debug info file_put_contents($filepath, json_encode(debug_backtrace())); // saving last extra parameter for future unlink... if possible... $last_extra = $extra; } } 

    Вот пример того, как его использовать:

     // A function which will produce a fatal error function fatal_example() { _fatalog_(time()); // writing the log $some_fatal_code = array()/3; // fatality! _fatalog_(); // if we get here then delete last file log } 

    Наконец, чтобы прочитать содержимое журнала …

     var_dump(json_decode(file_get_contents('/path/to-the-fatal.log'))); 

    Надеюсь, это поможет!

    PHP DeBugger также выполняет обратную трассировку, аналогичную PHP-ошибке с большим количеством опций.
    Если вы хотите, вы можете легко сделать свой собственный с помощью set_error_handler и debug_backtrace

     set_error_handler ($error_handler, error_reporting); /** * @var int $errno the error number * @var string $errstr the error message * @var string $errfile the error file * @var int $errline the line of the error */ $error_handler = function($errno, $errstr, $errfile, $errline){ $trace = debug_backtrace(); array_shift($backtrace);//remove the stack about this handler foreach($trace as $k => $v){ //parse your backtrace } } 

    Также обратите внимание, что для внутренних стеков в backtrace некоторые из ключей не будут установлены. Обязательно проверьте, существует ли ключ до того, как вы что-то сделаете с ним, если у вас есть все ошибки 🙂

    Hello @kartik,

    My script for installing an error handler that produces a backtrace:

    <?php
    function process_error_backtrace($errno, $errstr, $errfile, $errline, $errcontext) {
        if(!(error_reporting() & $errno))
            return;
        switch($errno) {
        case E_WARNING      :
        case E_USER_WARNING :
        case E_STRICT       :
        case E_NOTICE       :
        case E_USER_NOTICE  :
            $type = 'warning';
            $fatal = false;
            break;
        default             :
            $type = 'fatal error';
            $fatal = true;
            break;
        }
        $trace = array_reverse(debug_backtrace());
        array_pop($trace);
        if(php_sapi_name() == 'cli') {
            echo 'Backtrace from ' . $type . ' '' . $errstr . '' at ' . $errfile . ' ' . $errline . ':' . "n";
            foreach($trace as $item)
                echo '  ' . (isset($item['file']) ? $item['file'] : '<unknown file>') . ' ' . (isset($item['line']) ? $item['line'] : '<unknown line>') . ' calling ' . $item['function'] . '()' . "n";
        } else {
            echo '<p class="error_backtrace">' . "n";
            echo '  Backtrace from ' . $type . ' '' . $errstr . '' at ' . $errfile . ' ' . $errline . ':' . "n";
            echo '  <ol>' . "n";
            foreach($trace as $item)
                echo '    <li>' . (isset($item['file']) ? $item['file'] : '<unknown file>') . ' ' . (isset($item['line']) ? $item['line'] : '<unknown line>') . ' calling ' . $item['function'] . '()</li>' . "n";
            echo '  </ol>' . "n";
            echo '</p>' . "n";
        }
        if(ini_get('log_errors')) {
            $items = array();
            foreach($trace as $item)
                $items[] = (isset($item['file']) ? $item['file'] : '<unknown file>') . ' ' . (isset($item['line']) ? $item['line'] : '<unknown line>') . ' calling ' . $item['function'] . '()';
            $message = 'Backtrace from ' . $type . ' '' . $errstr . '' at ' . $errfile . ' ' . $errline . ': ' . join(' | ', $items);
            error_log($message);
        }
        if($fatal)
            exit(1);
    }
    
    set_error_handler('process_error_backtrace');
    ?>

    Hope it helps!!

    Понравилась статья? Поделить с друзьями:
  • Backlight error sony
  • Backing up to debug only private cache как исправить
  • Background disk merge failed to complete general access denied error 0x80070005
  • Backend tech error мтс банк
  • Backend internal error exception during ir lowering