Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does a class extension or Interface work?

Tags:

php

class

opcode

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.
like image 656
Baba Avatar asked Mar 28 '13 17:03

Baba


People also ask

How does a class use an interface?

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.

What happens when a class extends an interface?

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.

How does extending classes work in Java?

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.

Can a class extend and interface?

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.


2 Answers

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:

  1. ZEND_DECLARE_CLASS - this is loading your class definition
  2. ZEND_ADD_INTERFACE - this fetches JsonSerializable and adds that to your class defintion
  3. 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.

like image 107
bubba Avatar answered Sep 29 '22 04:09

bubba


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:

  1. has no interfaces or traits attached to it
  2. is not abstract
  3. either does not extend any classes or extends only the class that is already known to the engine
  4. is declared as top statement (i.e. not inside condition, function, etc.)

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.

like image 35
StasM Avatar answered Sep 29 '22 03:09

StasM