Hi internals,
We currently forbid throwing exceptions from __toString() and instead
convert them into fatal errors. The rationale for this behavior is that
string conversions are performed in many places throughout the engine and
standard library, and not all places performing these conversions will deal
with exceptions correctly (in the sense of "as soon as possible").
While this limitation is ultimately futile, because exceptions during
string conversion can still be triggered by error handlers that convert
warnings to exceptions, the sentiment in past discussions on this topic has
been to not relax this restriction until we have audited string conversions
across the codebase for exception safety. I have now done this, with the
patch available at https://github.com/php/php-src/pull/3887.
The PR
- allows exceptions to be thrown from __toString().
- converts the "could not be converted to string" and "__toString() must
return a string value" recoverable fatal errors into proper Error
exceptions. - audits uses of zval_get_string(), convert_to_string() and friends across
the codebase and adds exception checks where necessary.
For extension authors, the guideline is:
-
If zval_get_string() / convert_to_string() generate an exception, they
will always produce an interned string. This means that the result does not
need to be freed in case of an exception (but freeing it is also fine -- do
whatever is more convenient). -
The return value will be an empty string if an object to string
conversion fails, and "Array" if an array is converted to string and the
resulting notice is promoted to an exception by an error handler. (This
behavior is as before.) -
As usual, whether an exception has been thrown may be checked using if
(EG(exception)). A typical example would be:zend_string *str = zval_get_string(val);
if (EG(exception)) {
return; // Possibly do some cleanup here.
} -
While checking every single string conversion certainly puts you on the
safe side, leaving out these checks will usually only result in some
unneeded computation and possibly redundant warnings. The main thing you
should watch out for are operations modifying persistent structures such as
databases.
I would like to land this change in PHP 7.4. Are there any objections to
that? I'm skipping an RFC for this, because the change here is pretty much
a purely technical matter, but I can put up an RFC if it turns out to be
controversial.
Regards,
Nikita
PS: If I remember correctly https://wiki.php.net/rfc/array-to-string was
accepted but never implemented due to the inability to throw from string
conversions. We should probably revisit this for PHP 8.0.