Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

php

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.";
    }
}
like image 208
Michael Butler Avatar asked Apr 12 '12 14:04

Michael Butler


2 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.

like image 76
netcoder Avatar answered Sep 22 '22 03:09

netcoder


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.

like image 26
ANisus Avatar answered Sep 24 '22 03:09

ANisus