On Mon, Oct 26, 2026, at 11:03 AM, Frode Børli wrote: > Hi internals, > > I'd like to start the discussion on a new RFC: > > https://wiki.php.net/rfc/scope > > PHP has no object that represents a request. The request is the period > between startup and shutdown, and its state lives in process-wide > globals. That works while a process handles one request at a time, but > not when one process serves many requests, and least of all when they > overlap. > > The RFC adds Scope: an object that identifies a unit of work, such as > an HTTP request, a queue job or a CLI command. Code can create a scope, > run code in it, and ask which scope it is running in: > > (new Scope())->run($handler, $request); > > // anywhere in that request: > $db = $this->connections[Scope::getCurrent()] ??= new PDO($dsn); > > Fibers and generators join the scope they are created in, so work a > request spreads over several fibers still belongs to that request, > whichever library or scheduler created them. Code that never creates a > scope runs in the root scope and behaves exactly as today. > > The primary vote adds only the identity. Two secondary votes move > engine state into the scope: > > A. the request superglobals and the session > B. the default locale and timezone > > Other state (output buffers, error handlers, ini values) is left for > later RFCs, so each can be discussed on its own. > > An extension implementing the RFC, including both secondary votes, is > available for PHP 8.2 to 8.5: > > https://github.com/fiber-interop/ext-scope > (pie install fiber-interop/ext-scope) > > A userland polyfill is fiber-interop/scope on Packagist. A php-src > pull request will follow before the vote. > > I look forward to your feedback. > > Frode Børli
Minor thoughts as I read through it: - Given that we now have PFA, which means turning pretty much any call into a closure involves just adding ... to it, do we still want to have `run(callable, ...$args)`? `run(Closure $c)` would work just as well, and be callable like $scope->run(doAThing(1, 2, 4, ...)); I'm not certain here, but it's worth considering if we can go a step further now. - The docblock on Scope::bind() is horribly confusing and clumsy. It took me 5 reads to understand what it was saying. I *think* the idea is to "change" the scope object inside a closure, just like Closure::bind() changes the $this object inside a closure. Yes? (And returns new rather than modifying in place, but...) - "A scope keeps no link to the scope it was run from." So that fully precludes getParentScope() and such, yes? What is the reason for avoiding that? (Not that I disagree, but I want to understand why.) - "The root scope is a single object that lives as long as the PHP request:" - I think you mean "PHP process" here, as the process is not always isomorphic with an HTTP request. (Which is the whole reason this proposal exists.) - The phrasing and descriptions around closures and what scope they run in is quite confusing. It really needs better examples. Am I correct in summarizing it as "a closure inherits the scope of its callsiite, not creation site"? - For secondary vote A (the superglobals), the example is very unclear. You're `use`ing $request and $handler, but those are never defined. It looks like you're providing no alternative API or automation; really just an under the hood separation of $_GET based on scope. While I can see the simplicity of that, it also means that basically every scope start would need to do the same copying, unless it was absolutely certain there was no code that will be run it that tries to read those superglobals. Forget to do so and poof, some legacy garbage function that reads $_POST directly now gets no data and you don't know why. This feels like a footgun. - The first mention of $GLOBALS is in the superglobal secondary vote section. Are globals and thus $GLOBALS automatically scoped regardless? That should be clarified. - Oh, that's mentioned further down in the "what follows scope" section. That should come way earlier. :-) I am undecided if I agree globals should not follow the scope. That feels like the sort of thing you would want to follow scope. Overall, I think this is a promising approach. I like that it is narrowly and tightly focused. However, I am still uneasy about how narrow it is. I can see the argument for "make your own WeakMap and go away" as an approach. But I also fear that the result is we'll have a lot of subtly-different and incompatible ways of handling that (say, a multi-fiber server like AmPHP, a DI Container, and a specific service that does weird things for reasons), which could lead to hard-to-diagnose bugs. I am undecided here. As a concrete use case that I have been actively pondering of late: At work, we have an app that switches which DB it connects to per-request, based on the Customer that is specified through the domain name. (Right now it routes through an env var to a constant, but that I could change.) That works fine in FPM mode, but would fail horribly in FrankenPHP Worker mode. Scopes seem like they're the solution to that problem, but I'm not completely clear how they'd operate in practice in that case. I wouldn't want a different DB connection to cause every service downstream from it to be recreated on every soft-request, as that eliminates the main advantage of worker mode. But the RFC examples are all doing a pseudo-global service locator, not something I am able or willing to do. (I doubt I could coax Doctrine into working that way.) So what would it looks like in a real dependency injection situation? --Larry Garfield
