Why does PHP output different error messages for these two cases?

php

Solution

I think it'll all make sense to you when you see the opcodes Zend Engine generates for each example (it does for me anyway).

Example 1:

compiled vars:  !0 = $t
line     # *  op                           fetch          ext  return  operands
---------------------------------------------------------------------------------
   4     0  >   EXT_STMT                                                 
         1      ZEND_FETCH_CLASS                                 :0      'Test'
         2      EXT_FCALL_BEGIN                                          
         3      NEW                                              $1      :0
         4      DO_FCALL_BY_NAME                              0          
         5      EXT_FCALL_END                                            
         6      ASSIGN                                                   !0, $1
   6     7      EXT_STMT                                                 
         8      ZEND_INIT_METHOD_CALL                                    !0, 'foo'
         9      EXT_FCALL_BEGIN                                          
        10      DO_FCALL_BY_NAME                              0          
        11      EXT_FCALL_END                                            
   7    12      EXT_STMT                                                 
        13      ZEND_FETCH_CLASS                                 :6      'FakeInvalidClass'
        14      ZEND_DECLARE_INHERITED_CLASS                     $7      '%00test%2Fhome%2Fflacroix%2Ftest.php0x7f756fea4055', 'test'
  12    15    > RETURN                                                   1

As you can see #1 gets the `Test` class, which in turn goes to #13 to get the `FakeInvalidClass` (see the `return :6` there). Since the latter is not defined, #13 fails and return to #1, which also fails because `Test` is left undefined.

Both of them (#13 and #1) will call `zend_error` (as evidenced in the PHP source), but `zend_error` has global state (i.e.: errors are not stacked) so any subsequent call will overwrite the error message with the new one. So, in pseudo code:

ZEND_FETCH_CLASS('Test')
    ZEND_FETCH_CLASS('FakeInvalidClass')
        zend_error('Class FakeInvalidClass not found')
        return
    zend_error('Class Test not found')
    return

Example 2:

compiled vars:  !0 = $t
line     # *  op                           fetch          ext  return  operands
---------------------------------------------------------------------------------
   4     0  >   EXT_STMT                                                 
         1      ZEND_FETCH_CLASS                                 :0      'FakeInvalidClass'
         2      ZEND_DECLARE_INHERITED_CLASS                     $1      '%00test%2Fhome%2Fflacroix%2Ftest2.php0x7fe2c1461038', 'test'
  10     3      EXT_STMT                                                 
         4      ZEND_FETCH_CLASS                                 :2      'Test'
         5      EXT_FCALL_BEGIN                                          
         6      NEW                                              $3      :2
         7      DO_FCALL_BY_NAME                              0          
         8      EXT_FCALL_END                                            
         9      ASSIGN                                                   !0, $3
  12    10      EXT_STMT                                                 
        11      ZEND_INIT_METHOD_CALL                                    !0, 'foo'
        12      EXT_FCALL_BEGIN                                          
        13      DO_FCALL_BY_NAME                              0          
        14      EXT_FCALL_END                                            
  13    15    > RETURN                                                   1

Here #1 is a `ZEND_FETCH_CLASS 'FakeInvalidClass'` code, but the class doesn't exist, so it returns a `FakeInvalidClass not found` message, as it should.

Example 3:

compiled vars:  !0 = $t
line     # *  op                           fetch          ext  return  operands
---------------------------------------------------------------------------------
   4     0  >   EXT_STMT                                                 
         1      ZEND_FETCH_CLASS                                 :0      'Test'
         2      EXT_FCALL_BEGIN                                          
         3      NEW                                              $1      :0
         4      DO_FCALL_BY_NAME                              0          
         5      EXT_FCALL_END                                            
         6      ASSIGN                                                   !0, $1
   6     7      EXT_STMT                                                 
         8      ZEND_INIT_METHOD_CALL                                    !0, 'foo'
         9      EXT_FCALL_BEGIN                                          
        10      DO_FCALL_BY_NAME                              0          
        11      EXT_FCALL_END                                            
   8    12      EXT_STMT                                                 
        13      NOP                                                      
  13    14    > RETURN                                                   1

Zend gets a `ZEND_FETCH_CLASS 'Test'` code and execute normally.

This is explained by the fact that PHP will parse first-level classes it encounters in the code before it executes it. When you create a class definition that extends another class or instantiate an object, a `ZEND_FETCH_CLASS` opcode will be inserted for that class at that point in the code. It's actually lazy-initialization.

This is also evidenced by the fact that this works perfectly:

<?php

exit;

class Test extends FakeInvalidClass  {
    public function foo(){
        echo "arrived in foo.";
    }
}

Conclusion:

The different error messages are explained by the different parameters to the `ZEND_FETCH_CLASS` opcode.

Now, if you wonder why ZE generates opcodes like that, it's probably a design choice, it's probably easier to maintain. But to be honest, I have no idea.

Problem

It may not be the best practice, but in PHP we are allowed to define classes at the bottom of files However, ``` $t = new Test(); $t->foo(); class Test extends FakeInvalidClass { public function foo(){ echo "arrived in foo."; } } ``` Produces error message: `Fatal error: Class 'Test' not found in /mysite/test.php on line 4` That's odd... class 'Test' is defined at the bottom of the file, PHP should have failed because `FakeInvalidClass` was not found, not `Test` ``` <?php // test.php class Test extends FakeInvalidClass { public function foo(){ echo "arrived in foo."; } } $t = new Test(); $t->foo(); ``` Produces a more human readable error `Fatal error: Class 'FakeInvalidClass' not found in /mysite/test.php on line 4` For reference, this works just fine: ``` <?php // test.php $t = new Test(); $t->foo(); class Test { public function foo(){ echo "arrived in foo."; } } ```

Original source