On 11/03/2025 11:54, Gina P. Banyard wrote:
It also means that we need to do *multiple* passes, on the same code path, 
resulting in somewhat of a code churn and possibly not using a common 
abstraction.
Considering that we didn't even have the time to properly remove some 
deprecations from PHP 7 with the PHP 8.0.0 release, nor convert all relevant 
E_WARNINGs to Value/Type errors, I expect that we would be missing some of them 
again when PHP 9 comes around.


I wonder if in some cases, we could automate this within the source code - e.g. with a macro like WARNING_OR_ERROR(message, first_version_for_error) which checks a compile-time constant for the current version. I realise that still requires code cleanup later, but it means we can't miss our chance when the appropriate version does come around.


and this is ignoring the fact that PHP does not follow semver


This is true, but only in the sense that something under the MIT license isn't under the BSD license. Officially, PHP's release policy is extremely similar to SemVer.

https://semver.org/ says:

> Major version X (X.y.z | X > 0) MUST be incremented if any backward incompatible changes are introduced to the public API. It MAY also include minor and patch level changes

Meanwhile https://github.com/php/policies/blob/main/release-process.rst says:

> Major Version Number
>  x.y.z to x+1.0.0
>   -  Backward compatibility can be broken
>  -  API compatibility can be broken (internals and userland)

> Minor Version Number
> x.y.z to x.y+1.z
>    -  Backward compatibility must be kept
>   -  API compatibility must be kept (userland)


Now, what exactly constitutes "backward compatibility" and "userland API compatibility" can be debated, but there is currently no agreed policy allowing "small compatibility breaks".

The fact that everyone acts as though there is such a policy, probably means a rewrite of that document is overdue. A policy that nobody follows is no use to either contributors or users.


Maybe we could work on some criteria that could be applied (and publicised to users) about what is and isn't allowed in minor versions. For instance:

- Code that already causes fatal behaviour might cause different fatal behaviour (e.g. throwing an Error instead of raising an E_ERROR) - Code that directly violates documented types might start throwing TypeError - Code that produced NULL as an error case only for invalid inputs might start throwing ValueError


Some of the cases linked at the start of this thread might then meet the agreed criteria, but some might not.

For instance, it looks like pathinfo does give reliable outputs if the flag argument is treated as a bitmask, even if doing so makes no logical sense. It's not strictly the wrong type, and the behaviour isn't obviously attempting to indicate an error, or doing something immediately fatal.

So to allow that as well, we'd need some broader criteria, like:

- Code that is clearly an error on the part of the programmer might starting throwing Errors

But that's probably too broad and debatable to be meaningful policy.


Another alternative is to scrap x.y.z versioning completely, and give every annual release equal status. This is the approach that PostgreSQL has taken, I believe.

We'd probably still want some kind of deprecation policy - some changes should be deprecated for X releases before removal/change. Which brings us back to some kind of criteria for which changes need that, so doesn't really solve the problem.


--
Rowan Tommins
[IMSoP]

Reply via email to