Let’s suppose we’ve got two php files, a.php and b.php Here’s content of file a.php:
<?php // content of a.php
class A {
}
And here’s the content of file b.php
<?php // content of b.php
include dirname(__FILE__) . "/a.php";
echo "A: ", class_exists("A") ? "exists" : "doesn’t exist", "\n";
echo "B: ", class_exists("B") ? "exists" : "doesn’t exist", "\n";
echo "BA (before): ", class_exists("BA") ? "exists" : "doesn’t exist", "\n";
echo "BB: ", class_exists("BB") ? "exists" : "doesn’t exist", "\n";
class B {
}
class BA extends A {
}
class BB extends B {
}
echo "BA (after): ", class_exists("BA") ? "exists" : "doesn’t exist", "\n";
If you launch the b.php script you have this output:
A: exists
B: exists
BA (before): doesn’t exist
BB: exists
BA (after): exists
Why does the BA class exist only after the class definition? And why does the other classes exist even before their definition? Which is the difference? I’d expect to have a common behavior in both cases... Is there a way I could use the BA class even before its definition?
Thank you
Michele
Disclaimer: I don't claim to understand the inner workings of Zend. The following is my interpretation of the PHP source, fueled in great part by educated guesses. Even though I am fully confident in the conclusion, the terminology or details might be off. I 'd love to hear from anyone with experience in Zend internals on the matter.
The investigation
From the PHP parser we can see that when a class declaration is encountered the
zend_do_early_binding
function is called. Here is the code that handles the declaration of derived classes:This code immediately calls
zend_lookup_class
to see if the parent class exists in the symbol table... and then diverges depending on whether the parent is found or not.Let's first see what it does if the parent class is found:
Going over to
do_bind_inherited_class
, we see that the last argument (which in this call is1
) is calledcompile_time
. This sounds interesting. What does it do with this argument?Okay... so it reads the parent and derived class names either from a static (from the PHP user's perspective) or dynamic context, depending on the
compile_time
status. It then tries to find the class entry ("ce") in the class table, and if it is not found then... it returns without doing anything in compile time, but emits a fatal error at runtime.This sounds enormously important. Let's go back to
zend_do_early_binding
. What does it do if the parent class is not found?It seems that it is generating opcodes that will trigger a call to
do_bind_inherited_class
again -- but this time, the value ofcompile_time
will be0
(false).Finally, what about the implementation of the
class_exists
PHP function? Looking at the source shows this snippet:Great! This
class_table
variable is the sameclass_table
that gets involved in thedo_bind_inherited_class
call we saw earlier! So the return value ofclass_exists
depends on whether an entry for the class has already been inserted intoclass_table
bydo_bind_inherited_class
.The conclusions
The Zend compiler does not act on
include
directives at compile time (even if the filename is hardcoded).If it did, then there would be no reason to emit a class redeclaration fatal error based on the
compile_time
flag not being set; the error could be emitted unconditionally.When the compiler encounters a derived class declaration where the base class has not been declared in the same script file, it pushes the act of registering the class in its internal data structures to runtime.
This is evident from the last code snippet above, which sets up a
ZEND_DECLARE_INHERITED_CLASS_DELAYED
opcode to register the class when the script is executed. At that point thecompile_time
flag will befalse
and behavior will be subtly different.The return value of
class_exists
depends on whether the class has already been registered.Since this happens in different ways at compile time and at run time, the behavior of
class_exists
is also different:class_exists
returnsfalse
, instantiating gives a fatal error)This simply as to do with PHP handle class in included files
include dirname(__FILE__) . "/a.php";
BB
exists because it extendsB
which was define in the same file.BA
does not exist because PHP did not parseA
online it is calledBoth would work return the same result
Using
class BA extends B
Or Defining
class A
and usingclass BA extends A
Output
Conclusion
FORM PHP DOC
I think extended classes are covered in what PHP doc says, this can be treated as BUG that needs to be corrected but for the main time include your class before you call or use them