Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When does PHP need to see a class before it is created?

Tags:

php

PHP doesn't need forward declarations.

$core = new Core();
class Core {

Works fine

$test = new ParamSet(new IntParam(1));
echo($test->asString());

interface ParamType {
    /*snipped*/
}

class IntParam implements ParamType {
    /*snipped*/
}

class ParamSet implements ParamType {
    /*snipped*/
}

does not, I get:

Fatal error: Class 'ParamSet' not found in

No idea why, I've included the hierarchy incase it is important. Moving the $test below the definitions makes it work.

like image 227
Alec Teal Avatar asked Apr 14 '14 08:04

Alec Teal


Video Answer


2 Answers

It was treated as a bug (was bug when this post was sent, but later marked as not a bug - by the way, thanks to this question. that "bug" was closed).

The thing is - even class_exists() (i.e. standard indicator feature) will show that it actually doesn't exist:

//false, true
var_dump(class_exists('ParamSet'), class_exists('IntParam'));

interface ParamType {
    /*snipped*/
}

class IntParam  {
    /*snipped*/
}

class ParamSet implements ParamType {
    /*snipped*/
}

Solution - yes, create your class instances after definition on interface - and - even - after class definition.

Important update

Actually, while bugs.php.net said that it's a bug, after chat with PHP internals - I've realized that it isn't a bug (well, definitely not in sense of "bug"). It's kind of intended behavior.

And the actual reason is: implementing an interface turns class declaration to a conditional declaration. So that will be evaluated at run time and not during interpretation stage. This is why class_exists() and other stuff won't determine class definition in such case. So while solution for this will remain same (i.e. instantiate after declaration) - it's important to update that I wasn't correct about all reasons of such behavior.

like image 69
Alma Do Avatar answered Oct 06 '22 11:10

Alma Do


Alec, the nub of your question relates to how PHP implements binding of class constants, properties and methods, etc. together with inheritance. The PHP source is compiled on demand at a source file (or string in the case of eval class instructions) quantum into a intermediate code known as op_arrays, and it is this op_array from that is executed at runtime by the PHP interpreter.

The issue at hand is where and when PHP does this binding for a given class or function. (Function binding works the same way, so where I refer to class below read this as class or function interchangeably). Essentially there are two options:

  • The first is to bind the class at the point of compile, in which case it will be globally visible immediately after the include (or equivalent) instruction which requested the compile -- or more specifically after execution of the ZEND_INCLUDE_OR_EVAL opcode that this statement compiles into.

  • The second -- know as late binding -- is at the point in the execution sequence of the class statement.

This second needs a little more explanation. If this is needed, then the PHP interpreter compiles the class using a special mangled name and registers this mangled name; the mangled name is by design outside the range of valid symbol class names and thus can't be directly callable, and hence is invisible at a source application level. The compiler then generates a DECLARE_CLASS opcode at the location of the class statement within the source stream to rebind the mangled name to the true class name. Once this DECLARE_CLASS opcode is executed, the class is then available to the application.

Some thing will always force late binding, for example if you have loaded and enable OPcache, this extension sets an internal Zend flag to force late binding for all classes. Another example is if you have the declaration in a conditionally executed statement such as within a function or in an if block, e.g.:

if (!function_exists('fred') {
    function fred ($arg) {
       ...
    }
 }

If you think about classes which implement interfaces, the interface defines a set of methods that any class which implements the interface must implement. The PHP runtime system cannot enforce this requirement unless the interface is bound before any classes which implement it.


Now to your supplied examples: Core is bound at compile time in this this case so the class is already available to the following new. However ParamSet is late bound and asDECLARE_CLASS for ParamSet the point of execution of the new statement, the class is still unbound at the latter, thus raising the error. This is a feature of the Language and is clear as mud if you don't understand this rules. My simple suggestion is:

Always code assuming that classes and function are late bound.

This convention does not harm if PHP binds at compile time, but the converse -- that is to assume compile time binding -- can cause errors depending on the runtime configuration.

like image 2
TerryE Avatar answered Oct 06 '22 09:10

TerryE