> On 9/8/22 19:06, Nicolas Grekas wrote:
> >  From what I understand, there are two things in the RFC:
> >
> >     1. a proposal to wrap any throwables thrown during unserialization
> >     inside an UnserializationFailedException
>
> Correct.
>
> >     2. a discussion to figure out a way to turn these notices+warnings
> into
> >     exceptions.
>
> Partly correct:
>
> The RFC primarily proposes unifying the existing non-Exception errors
> (which are currently split between E_WARNING and E_NOTICE with no
> apparent pattern). To *what* exactly (E_WARNING or some Exception) they
> are unified is up for discussion/vote.
>

Thanks for clarifying. Our accepted release process policy
<https://wiki.php.net/rfc/releaseprocess> says "Backward compatibility must
be respected with the same major releases". I think it's critical to stick
to this policy as much as possible and also to plan for the smoothest
upgrade path when preparing the next major (deprecations FTW.)
That's what I have in mind when I review the RFC and where I don't see the
match yet. I know we're only at the beginning of the discussion and we're
still looking for what would work best.



> > About 1., I read that you think "this is not considered an issue", but to
> > me, changing the kind of exceptions that a piece of code might throw is a
> > non negligible BC break. There needs to be serious justification for it.
> I
> > tried looking for one, but I'm not convinced there is one: replacing the
> > existing catch(Throwable) by catch(UnserializationFailedException) won't
> > provide much added value to userland, if any. And "reliably include more
> > than just the call unserialize() in the try" is not worth the BC break
> IMHO.
>
> unserialize() is a generic function that will also call arbitrary
> callbacks. You already have no guarantees whatsoever about the kind of
> exception that is thrown from it. I am unable to think of a situation
> where the input data is well-defined enough to reliably throw a specific
> type of exception, but not well-defined enough to successfully unserialize.
>
> So on the contrary wrapping any Exceptions with a well-defined Exception
> allows you to rely on a specific and stable Exception to catch in the
> first place.
>
> As you specifically mention catch(Throwable), I'd like to point out that
> this will continue to work, because UnserializationFailedException
> implements Throwable.
>

You might have misunderstood my point so let me give two examples:

Example 1.
set_error_handler(fn ($t, $m) => throw new ErrorException($m));
try {
   $ser = serialize($value);
catch (ErrorException $e) {
  // deal with $e
finally {
  restore_error_handler();
}

Example 2.
try {
   $ser = @serialize($value);
catch (Exception $e) {
  // deal with $e but don't catch Error on purpose, to let them bubble down
}

Changing serialize to wrap any throwables into an
UnserializationFailedException would break both examples. That's what I
mean when I say this breaks BC. As any BC-break, this might cause big
troubles for the community and this should be avoided. The release process
policy I mentioned above is a wise document.



> > About 2., unserialize() accepts a second argument to give it options. Did
> > you consider adding a 'throw_on_error' option to opt-in into the behavior
> > you're looking for? I think that would be a nice replacement for the
> > current logics based on set_error_handler(). If we want to make PHP 9
> throw
> > by default, we could then decide to deprecate *not* passing this option.
>
> I did not consider this when writing the RFC. I now considered it, and I
> do not believe adding a flag to control this a good thing.
>
> 1. "No one" is going to set that flag, because it requires additional
> effort. I strongly believe that the easiest solution should also the
> correct solution for the majority of use cases. The flag fails this
> "test", because the correct solution should be "don't fail silently".
>

Unless we deprecate calling the method without the argument as I suggested.
I agree, it might not be ideal to ask the community to rewrite every call
to serialize($v) to serialize($v, ['throw_on_error' => true/false]) but
that's still way better than a BC break.
Maybe there's another way that doesn't break BC. I'm looking forward to one.

2. If you actually want to set that option in e.g. a library, then you
> break compatibility with older PHP versions for no real gain. If you go
> all the way and remember to add that extra flag, then you can also write
> an unserialize wrapper that does the set_error_handler dance I've shown
> in the introduction of the RFC. Similar effort to adding the flag, but
> better compatibility.
>

I think my proposal provides BC with older versions of php: passing extra
(unused) options is allowed and polyfilling UnserializationFailedException
is trivial, so one could set_error_handler(fn (...) => throw new
UnserializationFailedException(...)) and be both BC and FC.

About writing a wrapper, that's what we all do right now, each with our own
wrapping logic. You're looking for unifying those logics and I support that
goal.

But this leads to another idea : what about adding a new function, let's
name it unserialize2() for now, and deprecate unserialize()? That'd provide
the upgrade path we need.



> 3. It does literally nothing for users that use a throwing error
> handler, which to my understanding includes the vast majority of
> framework code.
>

I'm not sure what you mean here. The flag would allow removing those error
handlers, which is what you want to achieve in the end I believe (well,
except when BC with older versions is desired of course, see my previous
comment.)



> 4. Even for users that do not use a throwing error handler, omitting the
> option literally does nothing, because unserialize() might already throw
> depending on what the unserialize handlers of unserialized objects do.
>

Sure. I don't see how that's a problem. Throwing is what almost any lines
of PHP code can do. We're used to dealing with that and I don't see why
serialize() needs special care. I might have missed the point you want to
make here though.



> > Lastly I'd like to add a 3. to your proposal, because there is one more
> > thing that makes unserialization annoying: the unserialize_callback_func
> > <
> https://www.php.net/manual/var.configuration.php#ini.unserialize-callback-func
> >
> > ini setting. It would be great to be able to pass a 'callback_func'
> option
> > to unserialize to provide it, instead of calling ini_set() as we have to
> > quite often right now.
> >
> > Would that make sense to you?
> >
>
> TIL about that ini setting. Can you clarify where this callback comes in
> helpful? What can it do for you what your autoloader can't do? I've
> attempted to search GitHub to find out about the use cases, but almost
> all of the results are copies of php-src that match the .phpt tests.
>

Sure: that callback is called when PHP tries to create an object of class
Foo but the autoloader fails to load said class. When there is no handler,
PHP creates a __PHP_Incomplete_Class object, but I regularly use this
callback to throw instead when I don't want those strange incomplete
objects (note that I also have seen use cases where generating a
__PHP_Incomplete_Class *is* the desired behavior.)


However as of now I do not believe that it is appropriate to include in
> my RFC, because it is only indirectly related to error handling. I'd
> like to keep the RFC focused. This makes it easier for readers to
> understand the proposal, allowing voters to make an educated vote and
> serving as longer-term documentation if the vote passes.
>

I proposed that because in many situations unserializing a payload to
something that contains __PHP_Incomplete_Class objects is not desired and
should be leveled up to an error instead. That's the relation with error
handling and thus with the RFC.

Nicolas

Reply via email to