Hi
this thread concerns my PR carrying the same title as this email:
https://github.com/php/php-src/pull/23658, where Gina requested that I
ask the mailing list for opinions and/or objections against the feature.
My goal is to enable PHP scripts to act both as “reusable module”
(defining classes or functions for use in an application) as well as an
executable script at the same time, similarly to Python’s if __name__ == "__main__" pattern.
My current PR automatically executes any Closure returned by the main
PHP script, such that:
<?php
function my_json_decode(string $json): mixed {
return json_decode($json, flags: JSON_THROW_ON_ERROR);
}
return static function () {
var_dump(my_json_decode(file_get_contents('php://stdin')));
};
would just define my_json_decode() when included in an existing
script, but print the decoding of the standard input when run as echo '{"foo": "bar"}' |php my_json_decode.php by executing the returned
Closure, similar to echo '{"foo": "bar"}' |python3 -m json.tool.
Another possible solution, that I didn't yet evaluate, might be defining
a magic constant __MAIN__ that is equivalent to __FILE__ of the
script that is executed first.
Best regards
Tim Düsterhus
Hi
this thread concerns my PR carrying the same title as this email:
https://github.com/php/php-src/pull/23658, where Gina requested that I
ask the mailing list for opinions and/or objections against the feature.My goal is to enable PHP scripts to act both as “reusable module”
(defining classes or functions for use in an application) as well as an
executable script at the same time, similarly to Python’sif __name__ == "__main__"pattern.My current PR automatically executes any
Closurereturned by the main
PHP script, such that:<?php function my_json_decode(string $json): mixed { return json_decode($json, flags: JSON_THROW_ON_ERROR); } return static function () { var_dump(my_json_decode(file_get_contents('php://stdin'))); };would just define
my_json_decode()when included in an existing
script, but print the decoding of the standard input when run asecho '{"foo": "bar"}' |php my_json_decode.phpby executing the returned
Closure, similar toecho '{"foo": "bar"}' |python3 -m json.tool.Another possible solution, that I didn't yet evaluate, might be defining
a magic constant__MAIN__that is equivalent to__FILE__of the
script that is executed first.Best regards
Tim Düsterhus
Hi Tim,
A BIG yes from me.
I currently tend to use the following pattern in executable scripts,
or even index.php files in web applications:
(static function (): void {
// This is the main entry point.
})();
For example: https://github.com/carthage-software/mago/blob/main/composer/bin/mago#L18
It works, but I think returning the closure is cleaner and
communicates the intent more clearly:
Just to confirm my understanding: when the file is included from
another script, the closure would still be returned without being
executed,
so the following would continue to work as it does today:
$callable = require 'script_file.php';
$callable();
Is that correct?
Best regards,
Seifeddine
Hi
Just to confirm my understanding: when the file is included from
another script, the closure would still be returned without being
executed,
so the following would continue to work as it does today:$callable = require 'script_file.php'; $callable();Is that correct?
Yes. The only change is that the PHP CLI SAPI will make use of the
return value of the main script rather than ignoring it. No changes for
any other SAPI and no changes to include/require. Please see the
tests in the PR, and in particular
sapi/cli/tests/primary_script_closure_004.phpt.
Best regards
Tim Düsterhus
Hi
Just to confirm my understanding: when the file is included from
another script, the closure would still be returned without being
executed,
so the following would continue to work as it does today:$callable = require 'script_file.php'; $callable();Is that correct?
Yes. The only change is that the PHP CLI SAPI will make use of the
return value of the main script rather than ignoring it. No changes for
any other SAPI and no changes toinclude/require. Please see the
tests in the PR, and in particular
sapi/cli/tests/primary_script_closure_004.phpt.Best regards
Tim Düsterhus
Hi Tim,
That makes sense, thanks.
Another approach worth considering is the one used by HHVM/Hack, where
an entry point is identified using an attribute:
<<__EntryPoint>>
function main(): void {
echo "Hello world!\n";
}
The only documentation reference I could find is here:
https://docs.hhvm.com/hack/contributing/code-samples/#basic-usage
Hack prefixes built-in and special attributes with __. The PHP
equivalent might look something like this:
#[EntryPoint]
function main(): void {
echo "Hello world!\n";
}
A function carrying this attribute in the main script would be
executed automatically. This makes the entry point explicit and avoids
assigning special meaning to the script's return value.
There would be a few semantic questions to resolve:
- Should top-level code in the main script execute before the
entry-point function? - Which signatures should be accepted? Possibilities might include
(),(array $argv), or(int $argc, array $argv), with a return
type ofvoid,int, ornever. - What should happen if multiple functions are marked as entry
points? A compile-time error seems reasonable.
I still like the returned-closure proposal, but I think this
alternative is worth considering.
Best regards,
Seifeddine
Another approach worth considering is the one used by HHVM/Hack, where
an entry point is identified using an attribute ... The PHP
equivalent might look something like this:#[EntryPoint] function main(): void { echo "Hello world!\n"; }
I think I like this version better - as well as being more explicit, it means the entry point doesn't have to be at the very end of the file.
I don't think I've ever written a file (or wanted to) that was both an include and an entry point; but I have written files which include both function and class definitions and code intended for direct execution. Often, the procedural code is just something like "run_script($argv);" so adding an attribute would feel natural.
- Which signatures should be accepted? Possibilities might include
(),(array $argv), or(int $argc, array $argv), with a return
type ofvoid,int, ornever.
I think this is a good question regardless of the mechanism for marking the function. One complication would be Levi's suggestion that this could be useful in web SAPIs as well, where $argv wouldn't mean anything (and request data is in super-globals). Maybe a specific #[CliEntryPoint] would provide $argv, and a generic #[EntryPoint] would not provide anything?
Rowan Tommins
[IMSoP]
On 22 September 2026 20:43:11 BST, Seifeddine Gmati
azjezz@carthage.software wrote:Another approach worth considering is the one used by HHVM/Hack, where
an entry point is identified using an attribute ... The PHP
equivalent might look something like this:#[EntryPoint] function main(): void { echo "Hello world!\n"; }I think I like this version better - as well as being more explicit, it
means the entry point doesn't have to be at the very end of the file.I don't think I've ever written a file (or wanted to) that was both an
include and an entry point; but I have written files which include both
function and class definitions and code intended for direct execution.
Often, the procedural code is just something like "run_script($argv);"
so adding an attribute would feel natural.
I agree with Rowan. I have never, ever needed to do Python-style "library and self-execution" in PHP. The way the language is structured just doesn't make it necessary. And auto-executing a specific return type just feels hacky an unexpected. I'd be -1 on the original proposal here.
But I do, typically, have some sort of "get the heck out of the global namespace" function. Automating that in a more robust and supported way I could get behind.
- Which signatures should be accepted? Possibilities might include
(),(array $argv), or(int $argc, array $argv), with a return
type ofvoid,int, ornever.I think this is a good question regardless of the mechanism for marking
the function. One complication would be Levi's suggestion that this
could be useful in web SAPIs as well, where $argv wouldn't mean
anything (and request data is in super-globals). Maybe a specific
#[CliEntryPoint] would provide $argv, and a generic #[EntryPoint] would
not provide anything?
Since we have multiple SAPIs, would it make sense to do something like #[EntryPoint('cli')], #[EntryPoint('frankenphp')], etc? With no SAPI name indicating "all", and each SAPI implying a different set of arguments?
That might even offer a way to get super-global data passed into a function without referencing the super-globals. (And, crazy crazy thought, a way to register the run function for FrankenPHP worker mode? Dunno if that makes sense, just spitballing.)
--Larry Garfield
this thread concerns my PR carrying the same title as this email:
https://github.com/php/php-src/pull/23658, where Gina requested that I
ask the mailing list for opinions and/or objections against the feature.
Why is this PR scoped to the CLI SAPI? Shouldn't it apply equally to
any SAPI's top-level script?
Hi
this thread concerns my PR carrying the same title as this email:
https://github.com/php/php-src/pull/23658, where Gina requested that I
ask the mailing list for opinions and/or objections against the
feature.Why is this PR scoped to the CLI SAPI? Shouldn't it apply equally to
any SAPI's top-level script?
The “file is both a library and executable” use case is only really a
thing for a single-file deployment, because otherwise you could just
separate the executable bit into a different file. I believe that making
a library do something useful when executed directly is something that
would work for the CLI SAPI. In web contexts, where the front-controller
pattern is already well-established and where you generally have an
allow-list of legal entrypoints (mostly just your front-controller) for
security reasons I don't see it working well.
Best regards
Tim Düsterhus
Hi
this thread concerns my PR carrying the same title as this email:
https://github.com/php/php-src/pull/23658, where Gina requested that I
ask the mailing list for opinions and/or objections against the
feature.Why is this PR scoped to the CLI SAPI? Shouldn't it apply equally to
any SAPI's top-level script?The “file is both a library and executable” use case is only really a
thing for a single-file deployment, because otherwise you could just
separate the executable bit into a different file. I believe that making
a library do something useful when executed directly is something that
would work for the CLI SAPI. In web contexts, where the front-controller
pattern is already well-established and where you generally have an
allow-list of legal entrypoints (mostly just your front-controller) for
security reasons I don't see it working well.Best regards
Tim Düsterhus
Perhaps you have not seen this pattern in Symfony:
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (array $context) {
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
};
It uses (in my opinion) horrible autoloader magic to re-include the
file to make it work. But nonetheless, this is partly why I asked why
you scoped it to just CLI.
I don't really like top-level code, so in some respects it might be
nice to have an annotation for some function to be called
automatically. But I would need to think about this more if I would
actually support such a proposal, as I don't particularly like
annotations either. Perhaps status quo is better.
Hi
My current PR automatically executes any
Closurereturned by the main
PHP script, such that:
Based on the replies it has become clear that the proposed Closure
approach has several questions to solve and possibly needs prior design
(e.g. defining a request or context object that would be passed as a
parameter) to not paint ourselves into a corner. This requires much more
thought than I'm willing to spend right now, given I wanted to solve a
specific use case of mine.
I'm therefore abandoning the Closure idea and will instead look into …
Another possible solution, that I didn't yet evaluate, might be
defining a magic constant__MAIN__that is equivalent to__FILE__
of the script that is executed first.
… defining a MAIN constant, which should hopefully have much fewer
design considerations and might also benefit PHP applications that don't
leverage the front-controller approach to protect internal files from
direct access (e.g. as a native variant of phpBB’s IN_PHPBB constant).
Best regards
Tim Düsterhus
Another possible solution, that I didn't yet evaluate, might be
defining a magic constant__MAIN__that is equivalent to__FILE__
of the script that is executed first.… defining a MAIN constant, which should hopefully have much fewer
design considerations and might also benefit PHP applications that don't
leverage the front-controller approach to protect internal files from
direct access (e.g. as a native variant of phpBB’s IN_PHPBB constant).
I think already this would work with
realpath($_SERVER['SCRIPT_FILENAME'])
--
Alex