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
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?