Authentication Services
Command Line Specific Extensions
Compression and Archive Extensions
Cryptography Extensions
Database Extensions
Date and Time Related Extensions
File System Related Extensions
Human Language and Character Encoding Support
Image Processing and Generation
Mail Related Extensions
Mathematical Extensions
Non-Text MIME Output
Process Control Extensions
Other Basic Extensions
Other Services
Search Engine Extensions
Server Specific Extensions
Session Extensions
Text Processing
Variable and Type Related Extensions
Web Services
Windows Only Extensions
XML Manipulation
GUI Extensions
Keyboard Shortcuts
?
This help
Next menu item
Previous menu item
Previous man page
Next man page
Scroll to bottom
Scroll to top
Goto homepage
Goto search
(current page)
Focus search box
debug_backtrace
(
int
$options
=
DEBUG_BACKTRACE_PROVIDE_OBJECT
,
int
$limit
= 0
):
array
debug_backtrace()
产生一条 PHP 的回溯跟踪(backtrace)。
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
());
$trace
=
array_reverse
(
$trace
);
array_shift
(
$trace
);
array_pop
(
$trace
);
$length
=
count
(
$trace
);
$result
= array();
for (
$i
=
0
;
$i
<
$length
;
$i
++)
{
$result
[] = (
$i
+
1
) .
')'
.
substr
(
$trace
[
$i
],
strpos
(
$trace
[
$i
],
' '
));
}
return
"\t"
.
implode
(
"\n\t"
,
$result
);
}
?>
Example output:
1) /var/www/test/test.php(15): SomeClass->__construct()
2) /var/www/test/SomeClass.class.php(36): SomeClass->callSomething()
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"
);
?>
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().
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
)
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
());
$la
=
0
;
$lb
=
count
(
$trace
);
while (
$la
<
$lb
){
$trace
[
$la
][
'file'
];
$trace
[
$la
][
'line'
];
$trace
[
$la
][
'args'
];
$trace
[
$la
][
'function'
];
$ba
=
0
;
$bb
=
count
(
$trace
[
$la
][
'args'
]);
while (
$ba
<
$bb
){
$trace
[
$la
][
'args'
][
$ba
] =
"EDITING A REFERENCE/POINTER"
;
$ba
++;
}
unset(
$bb
);
unset(
$ba
);
$la
++;
}
unset(
$lb
);
unset(
$la
);
?>
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
){
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
);
?>
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>';
}
}
}
I want to point out that debug_backtrace() in new versions of php can detect recursion // circular references .. avoiding memory consumption.
Example:
<?php
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:
<?php
if (
count
(
debug_backtrace
(
FALSE
,
1
)) ==
0
)
{
}
?>
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.
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> <font color=\"red\">{$a[\'line\']}</font> <font color=\"green\">{$a[\'function\']} ()</font> -- ". 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).
<?php
function
get_caller_method
()
{
$traces
=
debug_backtrace
();
if (isset(
$traces
[
2
]))
{
return
$traces
[
2
][
'function'
];
}
return
null
;
}
?>
<?
// 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."> initialize\n";
$start_time = time() + microtime();
$start_code_line = $code_line;
return 0;
}
printf("debug %s> code-lines: %d-%d time: %.4f mem: %d KB\n", $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();
?>
Here is a function to cleanly output the debug_backtrace to the error_log
<?php
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:
<?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.
Another variation formatting backtrace.
Parameter $ignore to ignore the extra calls.
<?php
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
;
}
?>
need no Xdebug or dbg.so on server, return more detailed message:
diyism_trace.php:
<?php
define
(
TRACES_MODE
,
'TEXTAREA'
);
$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(
'/Array\s+\(/'
,
'/\[(\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(
")\n\n"
=>
")\n"
));
$rtn
=
strtr
(
$rtn
, array(
"'\n"
=>
"',\n"
,
")\n"
=>
"),\n"
));
$rtn
=
preg_replace
(array(
'/\n +/e'
), array(
'strtr(\'\0\', 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'
]);
$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'
])
{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
);
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
;
}
?>
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.
<?php
function
dump
(
$var
) {
$result
=
var_export
(
$var
,
true
);
$loc
=
whereCalled
();
return
"\n<pre>Dump:
$loc
\n
$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.
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.
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;
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
function
var_format
(
$v
)
{
return (
str_replace
(array(
"\n"
,
" "
,
"array"
),
array(
"<br>"
,
" "
,
" <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;
$trace
.=
str_repeat
(
" "
,++
$sp
);
$trace
.=
"file=
$file
, line=
$line
, function=
$function
<br>"
;
}
$out
.=
"<br>"
.
backTrace
();
if(
substr
(
$info
,
0
,
4
)==
"XXX "
)
{
$out
=
str_replace
(
"<br>"
,
"\n"
,
$out
);
$out
=
str_replace
(
" "
,
" "
,
$out
);
mail
(
"[email protected]"
,
"Database Execution Error for user "
.
$REMOTE_ADDR
,
"
$out
"
);
exit(
"Database Access Error. Please try again later."
);
}
mail
(
"[email protected]"
,
'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.
<?php
$arrTrace
=
debug_backtrace
();
foreach (
$arrTrace
as
$arr
)
{
if (!isset (
$arr
[
'file'
]))
{
$arr
[
'file'
] =
'[PHP Kernel]'
;
}
if (!isset (
$arr
[
'line'
]))
{
$arr
[
'line'
] =
''
;
}
}
?>
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.
<?php
function
print_var
(
$var
)
{
if(
is_string
(
$var
) )
return(
'"'
.
str_replace
( array(
"\x00"
,
"\x0a"
,
"\x0d"
,
"\x1a"
,
"\x09"
), array(
'\0'
,
'\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
) );
}
function
trace
(
$msg
)
{
echo
"<pre>\n"
;
$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
,
"param2\n"
, array(
1
=>
"a\n"
,
"b\n"
=>
2
),
false
);
?>
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
();
?>
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
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
);
?>
Trust me, there are some reasons for why you might want to do this :)
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'."\r\n";
return;
}
//echo '<pre>';
echo "\r\n".'----------------'."\r\n";
echo 'Debug backtrace:'."\r\n";
echo '----------------'."\r\n";
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 "\r\n";
}
//echo '</pre>';
exit;
}
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.
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(" line ".$bt[$i]["line"]."<br />");
echo(" function called: ".$bt[$i]["function"]);
if($bt[$i]["args"])
{
echo("<br /> 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 />");
}
}
Surprizingly, debug_backtrace() cannot aquire arguments from the function that is used as the second or later argument of a function.
<?php
function
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
"
;
}
b
(
a
(
"First a"
),
a
(
"Second a"
),
a
(
"Third a"
));
?>
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 .= ' ';
$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;
}
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 .= ' ';
$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;
}
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
;
}
date_default_timezone_set
(
'Europe/Bucharest'
);
$date
= new
DateTime
(
'2010-01-28'
);
LogTrace
(
$date
);
?>
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