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

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

Posted by: admin July 12, 2020 Leave a comment

Questions:

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.";
    }
}
How to&Answers:

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.

Answer:

The logical conclusion is that if you try to create an object of a class, it will continue to scan the rest of the file for this class declaration. If it fails to ‘parse’ the class (due to the fact that you extend an unexisting class), it will return to the line where you tried to create the object and tell you that the class doesn’t exist.

So why not giving you an error when not finding the class that you extend?

Well, PHP doesn’t know if you later on will include a file which would contain FakeInvalidClass, and it would be wrong to say at line 4 that Fatal error: Class 'FakeInvalidClass' not found.

Edit:
However, you can’t include FakeInvalidClass later on since Test will already have been parsed. So, it still remains that PHP will give you the error on the line it fails to execute. But a more informative error message would have been to stack them:

Class 'Test' not found (Class 'FakeInvalidClass' not found in /mysite/test.php on line 8) in /mysite/test.php on line 4

But PHP doesn’t stack errors.

Answer:

Just cause in your first case, the class “Test” isn’t load. In the second case, the class is load but the extend is invalid

Answer:

Case 1:
you try to create an object of the class Test so when it tries to create an object the extension from the base class FakeInvalid class hence the error in creating Test class saying it isnt found.

Case 2: it first tries to extend the base class and doesnt find it so gives that error saying FakeInvalidClass not found.