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."; } } ```