Hi All,
A while ago, I discovered the awkward fact that PHP exceptions can only
sometimes be serialized, because they contain a stack trace which might
involve non-serializable objects.
In my case, one of the function calls in the trace happened to have been
passed a SimpleXMLElement as an argument; similarly, anything passed a
closure will render the trace unserializable. A quick example is here:
http://codepad.viper-7.com/A6U91A
When I mentioned this in SO chat, the general response was "yeah, don't
do that". Which leads me to my first suggestion: (1) the base Exception
class should always prevent serialization, in the same way that it
always prevents cloning. This would prevent people like me writing code
to do it, and only finding out later that it is a Bad Idea.
(As background, my use case was some code that used serialized objects
to pass call-backs and their responses between processes in a kind of
"fake threading"; I wanted exceptions to bubble up to the parent
"thread" just as though the callback had been executed locally.)
On the other hand, most of the Exception, including its trace can
actually be serialized - an actual call to a closure is simply given the
function name '{closure}', and the object on which a method was called
is stored only as its class name, like debug_backtrace without
DEBUG_BACKTRACE_PROVIDE_OBJECT. The only part that is at risk of causing
serialization errors is the 'args' element in each stack trace entry.
This leads to two further possibilities:
(2) Don't capture arguments in the trace of an exception, similar to
debug_backtrace with DEBUG_BACKTRACE_IGNORE_ARGS
set. This would
technically be a BC break, but I'm not sure how much real code would
care. It's impossible to reconstruct details of the calls fully without
the PROVIDE_OBJECT behaviour anyway.
This would also have the advantage of making destructors fire more
predictably - at the moment, an exception can bubble past a block of
code with a deliberately scoped object (e.g. an RAII-style lock token),
and if that object happens to be an argument within the Exception's
trace, it survives until the Exception itself is destructed, because it
has a reference inside the Exception's trace. Again, arguably you
shouldn't be relying on this or keeping Exception objects around for
long anyway, but it's kind of unexpected behaviour.
Alternatively (3), serializing an Exception could discard the 'args'
keys in the trace, but retain the rest of the information; or (4) a
method could be made to explicitly discard these and mark the object
safe for serialization. This lets code that relies on that informaion
continue unchanged, while allowing the information needed for most use
cases to be serialized as safely as any other object.
It seems to me that any one of these options would be better than the
current unpredictable behaviour, but I'm more than willing for someone
to point out the flaw in my reasoning.
Regards,
Rowan Collins
[IMSoP]
(2) Don't capture arguments in the trace of an exception, similar to
debug_backtrace withDEBUG_BACKTRACE_IGNORE_ARGS
set. This would
technically be a BC break, but I'm not sure how much real code would
care. It's impossible to reconstruct details of the calls fully
without the PROVIDE_OBJECT behaviour anyway.This would also have the advantage of making destructors fire more
predictably - at the moment, an exception can bubble past a block of
code with a deliberately scoped object (e.g. an RAII-style lock
token), and if that object happens to be an argument within the
Exception's trace, it survives until the Exception itself is
destructed, because it has a reference inside the Exception's trace.
Again, arguably you shouldn't be relying on this or keeping
Exception objects around for long anyway, but it's kind of
unexpected behaviour.
Is it possible to make the trace options be overridable by the
Exception subclass? That would provide a migration path for any code
that did rely on the args being available. And I think our app could
benefit from having closure objects available for logging, especially
if it is possible to get line numbers.
-- Tim Starling
Tim Starling wrote (on 08/10/2013):
Is it possible to make the trace options be overridable by the
Exception subclass? That would provide a migration path for any code
that did rely on the args being available. And I think our app could
benefit from having closure objects available for logging, especially
if it is possible to get line numbers.-- Tim Starling
The problem with that is that you can't control what exceptions are
thrown by third-party libraries (or even some of the newer core
extensions), or force them to inherit from your base. If they turn off
the argument capture, the information will already be unavailable when
you catch the exception, and if they turn it on, you will still need a
function to strip it after the fact.
Obviously you can discard the third-party exception and throw your own,
but normally you would chain yours onto it, retaining whatever
information it originally contained.
--
Rowan Collins
[IMSoP]