Open brentkelly opened 2 years ago
Perhaps unserialize itself should wrap the unserialization in a try/finally so it can ensure the context is exited before passing any exceptions on
eg in functions.php
/**
* Unserialize
*
* @param string $data
* @param array|null $options
* @return mixed
*/
function unserialize($data, array $options = null)
{
SerializableClosure::enterContext();
try {
$data = ($options === null || \PHP_MAJOR_VERSION < 7)
? \unserialize($data)
: \unserialize($data, $options);
SerializableClosure::unwrapClosures($data);
}
finally {
SerializableClosure::exitContext();
}
return $data;
}
(BTW, I had to check for myself that finally
runs here before passing that exception on in the absence of a catch : I can confirm it does behave as expected and run the exitContext in that case before then passing that exception on as-per normal. For some reason in all my years of PHP of never really come across that requirement before and the docs are vague on the subject
)
I have the following use case:
$foo
may contain data that is either serialized or unserialized.$foo
is now in a consistent, unserialized state.Best I can tell there is no official way to detect if something can be unserialized. So the way I had implemented was in a seemingly-innocent try/catch block:
Now
$bar
is always unserialized.However this now causes
\Opis\Closure
to enter essentially a broken state if thetry
block fails. As soon as that occurs, thelocks
depth used in\Opis\Closure\SerializableContext::enterContext
will now never reach zero, causing all future serializations to happen in the same context. There is no way to forcibly reset the context state either.This creates some extremely random bugs - in my situation certain tests late in the test suite fail because when attempting to serialize an object instance using
\Opis\Closure\serialize
,\Opis\Closure\SerializableClosure::wrapClosures
updates the instance by reference to become simply booleantrue
. This happens because we are attempting to serialize inside the context of a previous serialization ... but only if specific other tests earlier in the suite were run first. The tests are bleeding into each other due to\Opis\Closure
's essentially global state.In a wider use case, its not unforeseeable that someone would try unserializing in a much wider try/catch block than my example above. If it falls over and their code execution continues, it is going to produce some very bizarre & hard-to-track-down bugs.
So best I can tell I'm stuck with the current implementation. For now I've just created a class that extends
\Opis\Closure\SerializableClosure
and provides aclean
method:Now I can clean up as required:
Now it cleans up after itself & allows future serialization.
I'm mainly just reporting this in case its of use to you guys - but a couple of suggestions for your consideration:
clean
method, or some other way to restore to a clean state / forcibly exit the current context as I've done above