Note: This RFC has been superseded by the engine exceptions proposal.
One of the most common fatal errors in PHP is the “call to a member function of a non-object” type. This occurs whenever a method is called on anything other than an object (usually null), e.g.:
// ...when getAction() returns null: $this->getAction()->invoke();
One situation in which fatal errors are problematic is if you want to run PHP as a webserver itself. For a long story on why you would want to do that in the first place, see http://marcjschmidt.de/blog/2014/02/08/php-high-performance.html.
Other situtations are described in the Engine Exceptions RFC.
This proposal's essence is to turns fatal errors generated from calls to methods on a non-object into E_RECOVERABLE_ERROR
s.
set_error_handler(function($code, $message) { var_dump($code, $message); }); $x= null; var_dump($x->method()); echo "Alive\n";
The above produces the following output:
int(4096) string(50) "Call to a member function method() on a non-object" NULL Alive
This behavior is consistent with how type hints work. Framework authors can turn this into exceptions if they wish.
The following error handler could be embedded into frameworks:
set_error_handler(function($code, $message) { if (0 === strncmp('Call', $message, 4)) { throw new BadMethodCallException($message); } else if (0 === strncmp('Argument', $message, 8)) { throw new InvalidArgumentException($message); } else { trigger_error($message, E_USER_ERROR); } }, E_RECOVERABLE_ERROR); $x= null; try { $x->method(); } catch (BadMethodCallException $e) { echo "Caught expected ", $e->getMessage(), "!\n"; } echo "Alive\n";
This could be a way for people preferring not to use exceptions and instead to exit the script directly, but get a stacktrace instead of just the fatal error message:
set_error_handler(function($code, $message) { echo "*** Error #$code: $message\n"; debug_print_backtrace(); exit(0xFF); }, E_RECOVERABLE_ERROR); $m= new some_db_model(); $row= $m->find(42); // null, deleted concurrently $row->delete();
This proposal doesn't go as far as the controversial RFC RFC: Engine exceptions.
Taken this code:
function a($comparator) { $result= $comparator->compare(1, 2); // ... }
You can unwind this to something like:
function a($comparator) { 1: ZEND_INIT_METHOD_CALL $comparator 'compare' 2: ZEND_SEND_VAL 1 3: ZEND_SEND_VAL 2 4: ZEND_DO_FCALL_BY_NAME 5: ZEND_ASSIGN $result // ... }
The handling on checking whether the method is callable happends inside
the opline #1 (ZEND_INIT_METHOD_CALL
). The opcode handler for it
checks its first argument (here: $comparator
) whether it is an object
or not. In case it's not, the following happens:
FCALL_BY_NAME
opcode. This is comparable to jumping forward just as, e.g., the ZEND_JMP
instruction would.ZVAL_NULL()
.ASSIGN
opcodeThis RFC is backwards compatible with previous PHP releases.
There is no impact on any SAPI.
No impact.
No effect, before, the script terminated.
This RFC targets PHP 5.7 or PHP 6.0, whichever comes first.
This RFC modifies the PHP language behaviour and therefore requires a two-third majority of votes.
There is a pull request available over at GitHub which includes tests. Feedback welcome!
Ideas for future work include:
Voting started 2014-06-29 and ended 2014-07-30.