How The PHP Stack Frame Works
Introduction
The PHP stack frame isn't as straight-forward as you might think. This caught us out when building FilterBacktrace
.
What's In A Stack Frame
Here's a stack dump generated by calling debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)
:
array(11) {
[0] =>
array(5) {
'file' =>
string(98) "/Users/stuart/Devel/ganbarodigital/php-mv-defensive/tests/V1/Exceptions/BadRequirementDataTest.php"
'line' =>
int(142)
'function' =>
string(22) "newFromRequirementData"
'class' =>
string(57) "GanbaroDigital\Defensive\V1\Exceptions\BadRequirementData"
'type' =>
string(2) "::"
}
[1] =>
array(3) {
'function' =>
string(35) "testCanCreateFromBadRequirementData"
'class' =>
string(65) "GanbaroDigitalTest\Defensive\V1\Exceptions\BadRequirementDataTest"
'type' =>
string(2) "->"
}
[2] =>
array(5) {
'file' =>
string(101) "/Users/stuart/Devel/ganbarodigital/php-mv-defensive/vendor/phpunit/phpunit/src/Framework/TestCase.php"
'line' =>
int(909)
'function' =>
string(10) "invokeArgs"
'class' =>
string(16) "ReflectionMethod"
'type' =>
string(2) "->"
}
[3] =>
array(5) {
'file' =>
string(101) "/Users/stuart/Devel/ganbarodigital/php-mv-defensive/vendor/phpunit/phpunit/src/Framework/TestCase.php"
'line' =>
int(768)
'function' =>
string(7) "runTest"
'class' =>
string(26) "PHPUnit_Framework_TestCase"
'type' =>
string(2) "->"
}
[4] =>
array(5) {
'file' =>
string(103) "/Users/stuart/Devel/ganbarodigital/php-mv-defensive/vendor/phpunit/phpunit/src/Framework/TestResult.php"
'line' =>
int(612)
'function' =>
string(7) "runBare"
'class' =>
string(26) "PHPUnit_Framework_TestCase"
'type' =>
string(2) "->"
}
[5] =>
array(5) {
'file' =>
string(101) "/Users/stuart/Devel/ganbarodigital/php-mv-defensive/vendor/phpunit/phpunit/src/Framework/TestCase.php"
'line' =>
int(724)
'function' =>
string(3) "run"
'class' =>
string(28) "PHPUnit_Framework_TestResult"
'type' =>
string(2) "->"
}
[6] =>
array(5) {
'file' =>
string(102) "/Users/stuart/Devel/ganbarodigital/php-mv-defensive/vendor/phpunit/phpunit/src/Framework/TestSuite.php"
'line' =>
int(747)
'function' =>
string(3) "run"
'class' =>
string(26) "PHPUnit_Framework_TestCase"
'type' =>
string(2) "->"
}
[7] =>
array(5) {
'file' =>
string(100) "/Users/stuart/Devel/ganbarodigital/php-mv-defensive/vendor/phpunit/phpunit/src/TextUI/TestRunner.php"
'line' =>
int(440)
'function' =>
string(3) "run"
'class' =>
string(27) "PHPUnit_Framework_TestSuite"
'type' =>
string(2) "->"
}
[8] =>
array(5) {
'file' =>
string(97) "/Users/stuart/Devel/ganbarodigital/php-mv-defensive/vendor/phpunit/phpunit/src/TextUI/Command.php"
'line' =>
int(149)
'function' =>
string(5) "doRun"
'class' =>
string(25) "PHPUnit_TextUI_TestRunner"
'type' =>
string(2) "->"
}
[9] =>
array(5) {
'file' =>
string(97) "/Users/stuart/Devel/ganbarodigital/php-mv-defensive/vendor/phpunit/phpunit/src/TextUI/Command.php"
'line' =>
int(100)
'function' =>
string(3) "run"
'class' =>
string(22) "PHPUnit_TextUI_Command"
'type' =>
string(2) "->"
}
[10] =>
array(5) {
'file' =>
string(82) "/Users/stuart/Devel/ganbarodigital/php-mv-defensive/vendor/phpunit/phpunit/phpunit"
'line' =>
int(47)
'function' =>
string(4) "main"
'class' =>
string(22) "PHPUnit_TextUI_Command"
'type' =>
string(2) "::"
}
}
Each stack frame can contain any of these details:
Detail | Description |
---|---|
file | A PHP file that has been executed |
line | The line number in 'file' that is being executed |
class | A class that is being called |
function | The method on 'class' that is being called (if 'class' has a value) |
function | A global function that is being called (if 'class' has no value or is not set) |
type | How 'function' was called (:: for a static call, -> for a call on an object, not set if 'class' is not set) |
At first glance, that looks very straight-forward. Let's isolate a single stack frame:
Detail | Value |
---|---|
file | /Users/stuart/Devel/ganbarodigital/php-mv-defensive/vendor/phpunit/phpunit/src/TextUI/TestRunner.php |
line | 440 |
class | PHPUnit_Framework_TestSuite |
function | run |
Hang on a moment. According to that, line 440 of TestRunner.php is part of the run()
method of PHPUnit_Framework_TestSuite
. Surely that's a typo?
No, it isn't. Here's the stack frame that I built that table from:
[7] =>
array(5) {
'file' =>
string(100) "/Users/stuart/Devel/ganbarodigital/php-mv-defensive/vendor/phpunit/phpunit/src/TextUI/TestRunner.php"
'line' =>
int(440)
'function' =>
string(3) "run"
'class' =>
string(27) "PHPUnit_Framework_TestSuite"
'type' =>
string(2) "->"
}
What's going on?
Stack Frames Contain Staggered Data
In any PHP stack frame, file
and line
correctly refer to the code that is executing. class
, function
and type
(if present) refer to the code that file
and line
is calling - and not the code in file
and line line
as you might be expecting.
You can clearly see that in the following two stack frames:
[7] =>
array(5) {
'file' =>
string(100) "/Users/stuart/Devel/ganbarodigital/php-mv-defensive/vendor/phpunit/phpunit/src/TextUI/TestRunner.php"
'line' =>
int(440)
'function' =>
string(3) "run"
'class' =>
string(27) "PHPUnit_Framework_TestSuite"
'type' =>
string(2) "->"
}
[8] =>
array(5) {
'file' =>
string(97) "/Users/stuart/Devel/ganbarodigital/php-mv-defensive/vendor/phpunit/phpunit/src/TextUI/Command.php"
'line' =>
int(149)
'function' =>
string(5) "doRun"
'class' =>
string(25) "PHPUnit_TextUI_TestRunner"
'type' =>
string(2) "->"
}
How To Work Out Actual Caller Details
To work out a full set of caller details, do the following:
- take the
class
,function
andtype
values from a stack frame - take the
file
andline
values from the preceeding stack frame
In our example above, if you combine class
, function
and type
from stack frame 8 with file
and line
from stack frame 7, you get this:
Detail | Value |
---|---|
file | /Users/stuart/Devel/ganbarodigital/php-mv-defensive/vendor/phpunit/phpunit/src/TextUI/TestRunner.php |
line | 440 |
class | PHPUnit_TextUI_TestRunner |
function | doRun |
type | -> |
... which looks far more credible :)
Automatically Handled For You
Our FilterBacktrace
class automatically handles this for you. All of our other classes in the TraceInspectors
namespace use FilterBacktrace
to understand the PHP stack.
As long as you use our classes, you won't have to worry about this PHP behaviour in your own code.