On Sat, 16 Apr 2022 at 12:17, Rowan Tommins <rowan.coll...@gmail.com> wrote:

> On 8 April 2022 18:34:52 BST, Craig Francis <cr...@craigfrancis.co.uk>
> wrote:
> >I've written a new draft RFC to address the NULL coercion problems:
> >
> >https://wiki.php.net/rfc/null_coercion_consistency
>
>
> I'm sympathetic to the general problem you're trying to solve, but I'm not
> convinced by the argument that this is about consistency, because user
> defined functions have been consistently rejecting null for non-nullable
> parameters since PHP 7.0, and even before that for arrays and objects.



Thanks for looking at this Rowan.

I am open to discussing different solutions (this is my second attempt),
and any suggestions are welcome.

In regards to consistency, while I think my RFC does that, it's not my main
motivation for creating this RFC - which is to address the backwards
compatibility issues.

But to expand on consistency, I appreciate user defined functions (since
7.0) didn't coerce NULL, and I do want internal/user defined functions to
be consistent with each other (why I agree with the spirit of the original
RFC), it's just odd that string/int/float/bool can be coerced, but NULL
cannot be coerced in this context and can be in other contexts, e.g.

```
$i = 0;
$n = NULL;

if ($i == $n) { // Fine
}

print($i); // Fine
print($n); // Fine
printf('%s', $i); // Fine
printf('%s', $n); // Fine
echo 'ConCat ' . $i . $n; // Fine
echo htmlspecialchars($i); // Fine
echo htmlspecialchars($n . ''); // Fine
echo htmlspecialchars($n); // Bad???
```



Consistency is a good argument for small changes that eliminate unusual
> edge cases, but I think far-reaching changes should be able to stand on
> their own merits - regardless of whether it's consistent, do we want the
> language to work this way?
>


Good question, while I mention consistency, my focus is on backwards
compatibility, and how PHP has accepted NULL to these ~335 parameters
since, well, forever... my fear is that the upgrade to PHP 9 will be so
difficult (as noted with the lack of tooling to solve this), I'll be
auditing PHP code that's stuck on 8.x for many years to come.

https://twitter.com/mwop/status/1441044164880355342



To me, the main defence of type coercion is that PHP operates in a world
> full of strings - URLs are strings, HTTP requests are strings, and a lot of
> API responses are strings - so making it easy to work with strings like
> '42' as numbers makes a lot of sense. It's not clear to me that this
> extends to null.
>


Yep, PHP does work in a world of strings, and for simplicity
string coercion works fairly well... but I think I can include NULL in that
definition, because HTTP requests do not guarantee values have been
provided, where PHP (via filter_input) and frameworks (Laravel, Symfony,
CakePHP, CodeIgniter, etc), have used NULL to note when a value has not
been provided... and many developers have simply not cared about that
distinction (e.g. when passing to htmlspecialchars, trim, strlen,
preg_match, etc).



I think a large part of the problem here is that null can mean many
> different things in different contexts - "unknown", "not provided",
> "invalid input", "default", "not applicable", etc.
>
> These differences are subtle, but lead to different expectations of
> behaviour:
>
> - Treat null as a specific case with its own meaning, distinct from any
> other valid value. This is what explicitly nullable parameters and union
> types allow.
> - Treat null the same as any other out of range value, and raise an error.
> This is what happens in user-defined functions in PHP, and in built-in
> functions expecting non-scalar arguments. Compare also out of range actions
> like division by zero.
> - Treat null as a special state that propagates through expressions,
> because any operation with an unknown input has an unknown output. This is
> the approach to null taken by SQL, and by IEEE floating point with NaN. It
> is also the basis of the ?-> operator, and of things like Optional.map in
> Swift.
> - Treat null as a generic default value which can be filled in implicitly
> based on requirements. This is the interpretation currently taken by
> internal functions for scalar arguments, and what you are proposing to make
> standard. It is also, as you point out, the way PHP treats null in some
> other contexts, such as many operators.
>


Thank you, that's a really good explanation.

The developers I work with would assume the last definition, in the same
way they can pass in an integer to `urlencode()`, where they just don't
think about it being implicitly coerced into a string, it just works
(TM)... that's not to say the `strict_types=1` style of checking, and NULL
being treated as an invalid value, doesn't provide value for some
developers (but as shown in the stats, they are in the minority).



Interestingly, one of your examples mentions filter_input, which takes the
> "propagate" approach, and htmlspecialchars, which doesn't. It would often
> be more useful to retain the information that a value is null than to have
> it silently converted to an empty string as a side-effect of some other
> operation.



Those are interesting points.

I think `filter_input()` is a bit different, as it's a source of data (not
exactly propagating); whereas all of the other functions, they either
process that data (and return a sting, and do not propagate) or work with
it (e.g. preg_match and strcmp returning a bool, strlen returning an
integer, etc).

Retaining NULL is interesting, but it would be a new thing...



Perhaps it would be useful to have some function-call equivalent of the ?->
> operator. I'm not sure what this would look like for normal function calls,
> but it would be easy to add if we had a pipe operator, e.g.:
>
> If this was equivalent to htmlspecialchars($foo)
> $foo |> htmlspecialchars(...)
>
> Then this could be equivalent to ($foo === null ? null :
> htmlspecialchars($foo))
> $foo ?|> htmlspecialchars(...)
>


... adding that syntax might be useful in some contexts (when the developer
wants to make NULL easier to propagate); but that would be a new feature,
and I don't think it solves the backwards compatibility issue we currently
face, and what I'm trying to address in this RFC.



I'm not set against this RFC, but I'm not quite convinced by the case it
> makes, and think there may still be other options to explore.
>


I'm open to any suggestions, I just want to avoid the situation where Fatal
Type Errors happen seemingly randomly (as in, someone who has never used
type checks before, and they use NULL without any thought about it).

I'm even open to the idea of someone coming up with a tool to auto-update
existing code, but so far I've only heard people say that is the way it
should be done (with absolutely no detail on how it would even begin to
work).

I also cannot explain why NULL should be rejected, other than for those
developers who see NULL as an invalid value and also use
`strict_types=1`... as in, when a team of developers spends a few hundred
hours adding strval() everywhere, what does that actually achieve? what do
they tell the client? does it make the code easier to read? does it avoid
any bugs? or is it only for 8.1 compatibility?

Anyway, thanks again Rowan, I really appreciate your thoughts on this.

Craig

Reply via email to