Throwing exceptions from __toString()
is currently forbidden and will result in a fatal error.
This makes it dangerous to call arbitrary code inside __toString()
and makes its use as a
general API problematic. This RFC aims to remove this restriction.
The rationale for the current behavior is that string conversions are performed in many places throughout the engine and standard library, and not all places are prepared to deal with exceptions “correctly”, in the sense that the exception is handled as early as possible.
This limitation is ultimately futile from a technical perspective, because exceptions during string conversion can still be triggered by an error handler that converts recoverable errors to exceptions:
set_error_handler(function() { throw new Exception(); }); try { (string) new stdClass; } catch (Exception $e) { echo "(string) threw an exception...\n"; }
In fact, Symfony uses this loophole to work around the current limitation. Unfortunately this relies on the $errcontext
parameter, which is going away in PHP 8.
Despite this, the sentiment in past discussions on this topic has been to not relax this restriction until we have performed a comprehensive audit of string conversions across the codebase. This has been done in the attached implementation pull request.
Allow throwing exceptions from __toString()
, which will behave as usual. Do not trigger a fatal
error anymore.
Additionally convert the “could not be converted to string” and “__toString() must return a string
value” recoverable fatal errors into proper Error
exceptions, in line with the error policy
established in PHP 7.
Extension authors who would like to ensure that they handle exceptions from string conversions gracefully, should take the following guidelines into account:
zval_get_string()
, convert_to_string()
and friends generate an exception, they will still produce a string. This string is guaranteed to be interned. This means that it is not necessary to release it, but it possible to do so. You can pick whichever option is more convenient in context.if (EG(exception))
check:zend_string *str = zval_get_string(val); if (EG(exception)) { // Possibly free other resources here. return; }
// Like zval_get_string() but returns NULL on conversion failure. zend_string *str = zval_try_get_string(val); if (!str) { // Possibly free other resources here. return; } // Main code. zend_string_release(str); // Like zval_get_tmp_string() but returns NULL on conversion failure. zend_string *tmp, *str = zval_try_get_tmp_string(val, &tmp); if (!str) { // Possibly free other resources here. return; } // Main code. zend_tmp_string_release(tmp); // Like convert_to_string() but returns a boolean indicating conversion success/failure. if (!try_convert_to_string(val)) { // Possibly free other resources here. return; } // Main code.
try_convert_to_string()
will not modify the original value in case of conversion failure. For this reason it is safer to use it, instead of convert_to_string()
and an exception check.
The conversion from recoverable fatal errors to Error
exceptions is technically BC breaking.
Voting started 2019-05-22 and ends 2019-06-05.