# Automatically execute Closures returned from primary script

- Thread: https://externals.io/message/132587
- Original archive: https://news-web.php.net/php.internals/132587
- Started by tim@bastelstu.be on 2026-09-22T13:17:51+00:00
- Messages: 11
- Reply by email to internals@lists.php.net
- More for agents: search with https://externals.io/search.md?q={query} · RFCs at https://externals.io/rfc.md · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

---

## #132587 — tim@bastelstu.be — 2026-09-22T13:17:51+00:00

URL: https://externals.io/message/132587#132587

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

---

## #132592 — Seifeddine Gmati — 2026-09-22T15:21:53+00:00

URL: https://externals.io/message/132587#132592 · In reply to: #132587

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

```php
(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:

```php
$callable = require 'script_file.php';
$callable();
```

Is that correct?

Best regards,
Seifeddine

---

## #132593 — tim@bastelstu.be — 2026-09-22T15:29:35+00:00

URL: https://externals.io/message/132587#132593 · In reply to: #132592

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:
> 
> ```php
> $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

---

## #132598 — Seifeddine Gmati — 2026-09-22T19:43:11+00:00

URL: https://externals.io/message/132587#132598 · In reply to: #132593

>
> Hi
>
> 
> > […]
>
> 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 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:

```hack
<<__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:

```php
#[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:

1. Should top-level code in the main script execute before the
entry-point function?
2. Which signatures should be accepted? Possibilities might include
`()`, `(array $argv)`, or `(int $argc, array $argv)`, with a return
type of `void`, `int`, or `never`.
3. 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

---

## #132599 — Levi Morrison — 2026-09-22T21:12:55+00:00

URL: https://externals.io/message/132587#132599 · In reply to: #132587

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

---

## #132602 — Rowan Tommins [IMSoP] — 2026-09-23T13:11:02+00:00

URL: https://externals.io/message/132587#132602 · In reply to: #132598

>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:
>
>```php
>#[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.



>2. Which signatures should be accepted? Possibilities might include
>`()`, `(array $argv)`, or `(int $argc, array $argv)`, with a return
>type of `void`, `int`, or `never`.


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]

---

## #132603 — Larry Garfield — 2026-09-23T13:41:13+00:00

URL: https://externals.io/message/132587#132603 · In reply to: #132602

> On 22 September 2026 20:43:11 BST, Seifeddine Gmati 
> <azjezz@carthage.software> wrote:
> > […]
>
>
> 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.

> > […]
>
>
> 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

---

## #132604 — tim@bastelstu.be — 2026-09-23T13:43:58+00:00

URL: https://externals.io/message/132587#132604 · In reply to: #132599

Hi


> > […]
> 
> 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

---

## #132605 — tim@bastelstu.be — 2026-09-23T13:50:15+00:00

URL: https://externals.io/message/132587#132605 · In reply to: #132587

Hi


> My current PR automatically executes any `Closure` returned 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

---

## #132606 — Levi Morrison — 2026-09-23T14:37:26+00:00

URL: https://externals.io/message/132587#132606 · In reply to: #132604

>
> Hi
>
> 
> > […]
>
> 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.

---

## #132613 — drealecs@gmail.com — 2026-09-23T22:47:00+00:00

URL: https://externals.io/message/132587#132613 · In reply to: #132605

>
> > […]
>
> … 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
