How does a class extension or Interface work?

class, opcode, php

Solution

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:

- Example 1 works because the class is not conditional on anything else.

- Example 2 fails because the class is conditional upon `JsonSerializable`.

- Example 3 works because the class is correctly defined prior to being called.

- Example 4 gets false the first time because the class is conditional but succeeds later because the class has been loaded.

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:

- ZEND_DECLARE_CLASS - this is loading your class definition

- ZEND_ADD_INTERFACE - this fetches JsonSerializable and adds that to your class defintion

- ZEND_VERIFY_ABSTRACT_CLASS - this verifies everything is sane.

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.

Problem

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 - I know `Example 3` worked because the class was declared before its initiated but why would `Example 1` work in the first place ? - How does this entire process of extending or interface work in PHP to make one valid and the other invalid? - What Exactly is happening in Example 4? - `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.

Original source