Hello internals,
I've been having this mild annoyance with exit()/die() since I wrote a CLI
script using a boolean $hasErrors variable to know if the script failed or not
and to indicate if the script failed via a non-zero status code by doing:
exit($hasErrors);
However, it turns this doesn't work, because exit() will cast everything
that is not an integer to a string, something that I find surprising but
also weird as it will not type error on resources or array but rather print
the warning to the CLI.
Anyway, yesterday I decided to put on my mad scientist lab coat and make this
a reality, I have a W.I.P. PR here:
https://github.com/php/php-src/pull/13483
I hear you, but what about exit; ?!
Do not worry exit; is still supported!
It only requires a tiny bit of dark magic to achieve this!
exit; would just be interpreted as a fetch to a constant,
so when attempting to access the undefined exit/die case-insensitive constant
we just need to exit as if we were calling the function!
Having exit and die be functions gives us access to some interesting
functionality, such as defining exit() in a namespace, or removing it via the
disable_functions INI setting (the latter allowing us to define a custom global
exit() which could be useful for testing that a function actually calls exit).
We can also pass it like any other callable and reflect on it with reflection.
Finally, this removes the T_EXIT
token and ZEND_EXIT opcode freeing one slot.
The W.I.P. PR implement every possible restriction:
- not being able to declare an exit()/die() function
- not being able to disable them
- not being able to define a constant named exit/die
Maybe it would be wise to deprecate using exit as a statement to be able to get
rid of the undefined constant magic handling.
Anyhoot, before I spend more time on this and write a proper RFC, do people
think this is a good idea or not?
Best regards,
Gina P. Banyard
Hello internals,
I've been having this mild annoyance with exit()/die() since I wrote a CLI
script using a boolean $hasErrors variable to know if the script failed or not
and to indicate if the script failed via a non-zero status code by doing:
exit($hasErrors);However, it turns this doesn't work, because exit() will cast everything
that is not an integer to a string, something that I find surprising but
also weird as it will not type error on resources or array but rather print
the warning to the CLI.Anyway, yesterday I decided to put on my mad scientist lab coat and make this
a reality, I have a W.I.P. PR here:
https://github.com/php/php-src/pull/13483I hear you, but what about exit; ?!
Do not worry exit; is still supported!
It only requires a tiny bit of dark magic to achieve this!
exit; would just be interpreted as a fetch to a constant,
so when attempting to access the undefined exit/die case-insensitive constant
we just need to exit as if we were calling the function!Having exit and die be functions gives us access to some interesting
functionality, such as defining exit() in a namespace, or removing it via the
disable_functions INI setting (the latter allowing us to define a custom global
exit() which could be useful for testing that a function actually calls exit).We can also pass it like any other callable and reflect on it with reflection.
Finally, this removes the
T_EXIT
token and ZEND_EXIT opcode freeing one slot.The W.I.P. PR implement every possible restriction:
- not being able to declare an exit()/die() function
- not being able to disable them
- not being able to define a constant named exit/die
Maybe it would be wise to deprecate using exit as a statement to be able to get
rid of the undefined constant magic handling.Anyhoot, before I spend more time on this and write a proper RFC, do people
think this is a good idea or not?Best regards,
Gina P. Banyard
Hi Gina,
I'm not sure a pet-peeve is a good motivation for creating an (I expect
large) breaking change.
The upgrade path, I suppose, would be updating calls to die
/exit
to
always have parentheses ? Or alternatively changing those calls to new
throw expressions ?
While that shouldn't be that huge a problem for real codebases (and
would be auto-fixable for adding the parentheses), the bigger problem I
see is the huge amount of teaching materials, tutorials and blog posts
using the versions without parentheses which will now all be
invalidated. I think the pain and confusion that will cause for a change
like this, will linger for years and years.
Smile,
Juliette
Hi Gina,
I'm not sure a pet-peeve is a good motivation for creating an (I expect large) breaking change.
The upgrade path, I suppose, would be updating calls to
die
/exit
to always have parentheses ? Or alternatively changing those calls to new throw expressions ?While that shouldn't be that huge a problem for real codebases (and would be auto-fixable for adding the parentheses), the bigger problem I see is the huge amount of teaching materials, tutorials and blog posts using the versions without parentheses which will now all be invalidated. I think the pain and confusion that will cause for a change like this, will linger for years and years.
Smile,
Juliette
I didn't actually know one could do exit;
But like I said, it is extremely easy to support, and the current PR does support it by hooking into the undefined constant code in the engine.
I don't have strong opinions about removing support for this.
However, I do have strong opinions about changing the type juggling semantics of exit() to be the usual ones, because the current one is just confusing.
I am also not sure what would make this a large breaking change, as changing this from a language construct to a function provides us with more capabilities.
Best regards,
Gina P. Banyard
On Saturday, 24 February 2024 at 01:57, Juliette Reinders Folmer
php-internals_nospam@adviesenzo.nl wrote:Hi Gina,
I'm not sure a pet-peeve is a good motivation for creating an (I
expect large) breaking change.The upgrade path, I suppose, would be updating calls to
die
/exit
to always have parentheses ? Or alternatively changing those calls to
new throw expressions ?While that shouldn't be that huge a problem for real codebases (and
would be auto-fixable for adding the parentheses), the bigger problem
I see is the huge amount of teaching materials, tutorials and blog
posts using the versions without parentheses which will now all be
invalidated. I think the pain and confusion that will cause for a
change like this, will linger for years and years.Smile,
JulietteI didn't actually know one could do exit;
But like I said, it is extremely easy to support, and the current PR
does support it by hooking into the undefined constant code in the engine.
I don't have strong opinions about removing support for this.
However, I do have strong opinions about changing the type juggling
semantics of exit() to be the usual ones, because the current one is
just confusing.
I am also not sure what would make this a large breaking change, as
changing this from a language construct to a function provides us with
more capabilities.
Ah, I think I missed the part about the syntax both with and without
parentheses still being supported, with the "with parentheses" mapping
to a function call and the "without parentheses" mapping to a
case-insensitive constant.
In that case, I don't see a BC-break and I would regard this as an
"under the hood" change with only a very subtle, minimal impact (the
type checking part if a param is passed).
I do wonder what the documentation would look like as it would leave it
as a function, but one with a special status, in that there is a native
constant of the same name which will enforce the same behaviour.
Smile,
Juliette
I hear you, but what about exit; ?!
Do not worry exit; is still supported!
It only requires a tiny bit of dark magic to achieve this!
exit; would just be interpreted as a fetch to a constant,
so when attempting to access the undefined exit/die case-insensitive constant
we just need to exit as if we were calling the function!
Hi Gina,
If it is mostly an implementation detail, this is probably ok. However, please make sure that $foo = 'exit'; constant($foo);
(or any other future way to interpolate the constant) does not stop the program, but only fetch the value of a “real” constant.
Alternatively, it is probably possible to keep the current parsing with T_EXIT, but to make it equivalent to invoke a “regular” function named exit()
— somehow the same way that using the backtick operator is equivalent to calling shell_exec()
, up to the point that disabling shell_exec()
will also disable the the backtick operator?
(BTW, it is currently possible to define and use a constant named “exit”: https://3v4l.org/aBq2R — of course, no need to keep such a “feature”.)
—Claude