Background: I'm building an automated test framework for a PHP application, and I need a way to efficiently "stub out" classes which encapsulate communication with external systems. For example, when testing class X that uses DB wrapper class Y, I would like to be able to "swap in" a "fake" version of class Y while running automated tests on class X (this way I don't have to do full setup + teardown of the state of the real DB as part of the test).
Problem: PHP allows "conditional includes", which means basically that include/require directives are handled as part of processing the "main" logic of a file, e.g.:
if (condition) {
require_once('path/to/file');
}
The problem is that I can't figure out what happens when the "main" logic of the included file calls "return". Are all of the objects (defines, classes, functions, etc.) in the included file imported into the file which calls include/require? Or does processing stop with the return?
Example: Consider these three files:
A.inc
define('MOCK_Z', true);
require_once('Z.inc');
class Z {
public function foo() {
print "This is foo() from a local version of class Z.
";
}
}
$a = new Z();
$a->foo();
B.inc
define('MOCK_Z', true);
require_once('Z.inc');
$a = new Z();
$a->foo();
Z.inc
if (defined ('MOCK_Z')) {
return true;
}
class Z {
function foo() {
print "This is foo() from the original version of class Z.
";
}
}
I observe the following behavior:
$ php A.inc
> This is foo() from a local version of class Z.
$ php B.inc
> This is foo() from the original version of class Z.
Why This is Strange: If require_once() included all of the defined code objects, then "php A.inc" ought to complain with a message like
Fatal error: Cannot redeclare class Z
And if require_once() included only the defined code objects up to "return", then "php B.inc" ought to complain with a message like:
Fatal error: Class 'Z' not found
Question: Can anyone explain exactly what PHP is doing, here? It actually matters to me because I need a robust idiom for handling includes for "mocked" classes.