Have come across this so many times and am not sure why so it got me curious. Some classes work before they are declared and others don't;
Example 1
$test = new TestClass(); // top of class class TestClass { function __construct() { var_dump(__METHOD__); } }
Output
string 'TestClass::__construct' (length=22)
Example 2
When a class extends another class or implements any interface
$test = new TestClass(); // top of class class TestClass implements JsonSerializable { function __construct() { var_dump(__METHOD__); } public function jsonSerialize() { return json_encode(rand(1, 10)); } }
Output
Fatal error: Class 'TestClass' not found
Example 3
Let's try the same class above but change the position
class TestClass implements JsonSerializable { function __construct() { var_dump(__METHOD__); } public function jsonSerialize() { return json_encode(rand(1, 10)); } } $test = new TestClass(); // move this from top to bottom
Output
string 'TestClass::__construct' (length=22)
Example 4 ( I also tested with class_exists )
var_dump(class_exists("TestClass")); //true class TestClass { function __construct() { var_dump(__METHOD__); } public function jsonSerialize() { return null; } } var_dump(class_exists("TestClass")); //true
as soon as it implements JsonSerializable
( Or any other)
var_dump(class_exists("TestClass")); //false class TestClass implements JsonSerializable { function __construct() { var_dump(__METHOD__); } public function jsonSerialize() { return null; } } var_dump(class_exists("TestClass")); //true
Also Checked Opcodes without
JsonSerializable
line # * op fetch ext return operands --------------------------------------------------------------------------------- 3 0 > SEND_VAL 'TestClass' 1 DO_FCALL 1 $0 'class_exists' 2 SEND_VAR_NO_REF 6 $0 3 DO_FCALL 1 'var_dump' 4 4 NOP 14 5 > RETURN 1
Also Checked Opcodes with
JsonSerializable
line # * op fetch ext return operands --------------------------------------------------------------------------------- 3 0 > SEND_VAL 'TestClass' 1 DO_FCALL 1 $0 'class_exists' 2 SEND_VAR_NO_REF 6 $0 3 DO_FCALL 1 'var_dump' 4 4 ZEND_DECLARE_CLASS $2 '%00testclass%2Fin%2FaDRGC0x7f563932f041', 'testclass' 5 ZEND_ADD_INTERFACE $2, 'JsonSerializable' 13 6 ZEND_VERIFY_ABSTRACT_CLASS $2 14 7 > RETURN 1
Question
Example 3
worked because the class was declared before its initiated but why would Example 1
work in the first place ?Opcodes
was supposed to make things clear but just made it more complex because class_exists
was called before TestClass
but the reverse is the case.To declare a class that implements an interface, you include an implements clause in the class declaration. Your class can implement more than one interface, so the implements keyword is followed by a comma-separated list of the interfaces implemented by the class.
Extending Interfaces An interface can extend another interface in the same way that a class can extend another class. The extends keyword is used to extend an interface, and the child interface inherits the methods of the parent interface. The following Sports interface is extended by Hockey and Football interfaces.
Definition and Usage The extends keyword extends a class (indicates that a class is inherited from another class). In Java, it is possible to inherit attributes and methods from one class to another. We group the "inheritance concept" into two categories: subclass (child) - the class that inherits from another class.
A class can't extend an interface because inheriting from a class ( extends ), and implementing an interface ( implements ) are two different concepts. Hence, they use different keywords.
I can not find a write up on PHP class definitions; however, I imagine it is precisely the same as the User-defined functions which your experiments indicate.
Functions need not be defined before they are referenced, except when a function is conditionally defined as shown in the two examples below. When a function is defined in a conditional manner; its definition must be processed prior to being called.
<?php $makefoo = true; /* We can't call foo() from here since it doesn't exist yet, but we can call bar() */ bar(); if ($makefoo) { function foo() { echo "I don't exist until program execution reaches me.\n"; } } /* Now we can safely call foo() since $makefoo evaluated to true */ if ($makefoo) foo(); function bar() { echo "I exist immediately upon program start.\n"; } ?>
This is true for classes as well:
JsonSerializable
.The class is made conditional by either implementing an interface or extending another class from another file (require
). I'm calling it conditional because the definition now relies upon another definition.
Imagine the PHP interpreter takes a first look at the code in this file. It sees a non-conditional class and/or function, so it goes ahead and loads them in memory. It sees a few conditional ones and skips over them.
Then the Interpreter begins to parse the page for execution. In example 4, it gets to the class_exists("TestClass")
instruction, checks memory, and says nope, I don't have that. If doesn't have it because it was conditional. It continues executing the instructions, see the conditional class and executes the instructions to actually load the class into memory.
Then it drops down to the last class_exists("TestClass")
and sees that the class does indeed exist in memory.
In reading your opcodes, the TestClass
doesn't get called before class_exist
. What you see is the SEND_VAL which is sending the value TestClass so that it is in memory for the next line, which actually calls DO_FCALL on class_exists
You can then see how it is handling the class definition itself:
It is that second piece ZEND_ADD_INTERFACE that appears to prevent the PHP Engine from merely loading the class on the initial peak at it.
If you desire a more detailed discussion of how the PHP Interpreter Compiles and Executes the code in these scenarios, I suggest taking a look at @StasM answer to this question, he provides an excellent overview of it in greater depth than this answer goes.
I think we answered all of your questions.
Best Practice: Place each of your classes in it's own file and then autoload them as needed, as @StasM states in his answer, use a sensible file naming and autoloading strategy - e.g. PSR-0 or something similar. When you do this, you no longer have to be concerned with the order of the Engine loading them, it just handles that for you automatically.
The basic premise is that for class to be used it has to be defined, i.e. known to the engine. This can never be changed - if you need an object of some class, the PHP engine needs to know what the class is.
However, the moment where the engine gains such knowledge can be different. First of all, consuming of the PHP code by the engine consists of two separate processes - compilation and execution. On compilation stage, the engine converts PHP code as you know it to the set of opcodes (which you are already familiar with), on the second stage the engine goes through the opcodes as processor would go through instructions in memory, and executes them.
One of the opcodes is the opcode that defines a new class, which is usually inserted in the same place where the class definition is in the source.
However, when the compiler encounters class definition, it may be able to enter the class into the list of the classes known to the engine before executing any code. This is called "early binding". This can happen if the compiler decides that it already has all the information it needs to create a class definition, and there's no reason to defer the class creation until the actual runtime. Currently, the engine does this only if the class:
This behavior can also be modified by compiler options, but those are available only to extensions like APC so should not be a matter of much concern to you unless you are going to develop APC or similar extension.
This also means this would be OK:
class B extends A {} class A { }
but this would not be:
class C extends B {} class B extends A {} class A { }
Since A would be early bound, and thus available for B's definition, but B would be defined only in line 2 and thus unavailable for line 1's definition of C.
In your case, when your class implemented the interface, it was not early bound, and thus became known to the engine at the point when "class" statement was reached. When it was simple class without interfaces, it was early bound and thus became known to the engine as soon as the file compilation was finished (you can see this point as one before the first statement in the file).
In order not to bother with all these weird details of the engine, I would support the recommendation of the previous answer - if your script is small, just declare classes before usage. If you have bigger application, define your classes in individual files, and have sensible file naming and autoloading strategy - e.g. PSR-0 or something similar, as suitable in your case.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With