On 26/06/2026 14:20, Alex Pătrănescu wrote:
The rough idea is to have request-lifetime runtime modules.
A runtime module would be a named internal unit with its own userland
class/function/constant tables.
Code running in a module would define symbols into that module, and
symbol identity for module-owned code would effectively become
`(module, symbol_name)`.
The root context would keep the existing behavior, and conceptually we
can view it as a root/default module.
Root context userland symbols would not automatically be visible to
runtime modules.
Hi Alex,
If I understand right, this is what I have suggested in previous threads
could be called "containers". The reason I prefer that name is that it
frames expectations of who needs to make changes: the person
*distributing* a piece of code, or the person *consuming* it.
In most contexts, terms like "module", "package", "library", etc, refer
to ways to *distribute* a piece of code; structuring code, adding
metadata, etc, so that the code can be combined with others to produce
larger pieces of functionality. A "container", in the sense of Docker,
Podman, Kubernetes, etc, is a way to *consume* other people's code;
taking a complete configured application and isolating it without
modifying each internal part.
The example I've been using is a WordPress plugin which wants to use a
specific version of Guzzle, without colliding with other plugins. To do
that, it needs to isolate not just Guzzle itself, but a tree of at least
a dozen other packages which Guzzle depends on. If every one of those
packages needs to be altered in some way, as implied by the term
"module", the chance of success is low.
On the other hand, if the WordPress plugin can create a "container"
where all of those packages run *unchanged*, then the feature would
immediately give access to thousands of existing packages.
One possible userland API shape I have been using to experiment with is:
```
module_add_dependency(string $module): void
module_run(string $module, Closure $closure): mixed
```
- `module_add_dependency()` declares a module dependency for the current module.
- `module_run()` executes the passed closure in the specified module context.
Given the above, I'm not sure what "module_add_dependency" would do;
what is the difference between "depending on" something, and "running"
that thing?
I also don't think using a string as an identifier is useful or
necessary; avoiding reliance on global names is the whole point of the
exercise, after all.
Instead, how about this?
```
class ExecutionContainer {
public function run(callable $code): mixed;
}
```
Creating a new container initialises a new symbol table, autoloader
stack, etc, and gives you an object referring to them. Calling that
object's run() method then executes some code in the context of that
container, and returns its result.
Also, due to the dynamic nature of PHP, objects can be passed to
module code that might not have their class known, but I do not see
this as a blocker.
I think this is actually the biggest challenge: what happens when
objects are passed between containers?
To use the previous example: as an initialisation step, the WordPress
plugin might want to set up an API client inside its container; later,
it might want to make use of that API client, plus an object passed to
it by a WordPress hook.
The containers are inside the same thread, so in principle there's no
problem referencing object handles which are "owned by" a different
container. But what is the type of those objects? How do they respond to
get_class(), instanceof, etc?
Perhaps there are things we can learn from other languages like Java's
"isolated ClassLoader" which I mentioned on another thread?
https://www.javathinking.com/blog/what-is-an-isolated-classloader-in-java/
The design I have been considering also rejects visible shadowing:
unrelated modules may define the same symbol name, but adding a
dependency or declaring a later symbol would fail if it makes two
different symbols with the same name visible from the same context.
I think this would mean in practice that every container should start
with an empty symbol table (or rather, one with only built-in symbols).
If a container starts with all currently-loaded symbols, it would no
longer have any control over name collisions, so would be useless.
You could perhaps have a way to "import" and "export" specific symbols,
so that e.g. "Psr\Log\LoggerInterface" refers to the same thing in two
different containers; but I think this would need to explicit, so the
container always ran consistently.
I would like feedback on this package oriented runtime module model,
especially whether you see any major technical blockers or design
flaws.
I think this would be a powerful feature, but one that the vast majority
of PHP applications won't use. So the key to success will be minimising
the impact in performance and engine complexity.
Regards,
--
Rowan Tommins
[IMSoP]