Re: [PRE-RFC] Runtime modules

From: Date: Sat, 27 Jun 2026 20:03:21 +0000
Subject: Re: [PRE-RFC] Runtime modules
References: 1 2 3 4  Groups: php.internals 
Request: Send a blank email to internals+get-131548@lists.php.net to get a copy of this message
On Sat, Jun 27, 2026 at 9:00 PM Rowan Tommins [IMSoP] <imsop.php@rwec.co.uk> wrote: > This feels like it's moving much more back to the "module" idea - > changes which have to propagate deep into existing code before you can > use them. No, not at all. That's what I was implying by saying about carefully designed defaults. Existing code would still behave as it knows nothing about containers, or better say: knows nothing about the container it is run in. It intersects with what Alex initially wrote about each module defining its own symbols. But I think what I describe is not exactly the same with what Alex proposes, even though we have some things in common. I'll try to describe your example better further, but now I would like to "translate" Alex's ideas into my terminology and vise-versa. What we have in common is that all symbols would be identified not just by name, but also by "module", or as in my terms: a "tag". Like, in your example there would be not just WidgetFactory, but ("A", WidgetFactory) and ("B", WidgetFactory) - two separate classes but with the same name. What is "module" in Alex's terms is a "tag" in my examples. The key moment here is what we would use "by default" if the version is not explicitly defined (like it would be in any currently existing code). Now let's get back to your example. First, I think it is not exactly correct, because if you instantiate exactly the same WidgetFactory, it would most probably give out the same Widget as a result. So, I'll "rephrase" your example. Let's say, we already use the Widget class from some library my/widgets: 2.0. But in our project we also added another library (let it be vendor/widgetfactory) which provides the factory, and it uses the my/widgets: 1.0. And this 1.0 still has the "same" Widget class. So, we try to run this WidgetFactory inside a container, like this: // It'll be Widget from 2.0, let's omit for now how do we determine that use My\Widgets\Widget; $ourWidget = new Widget(); $container = new Container(...); // we also provide an autoloader $anotherWidget = $container->run(static fn() => ... $widgetFactory->makeWidget()); Here WidgetFactory will be run inside the container with its own set of symbols and would not know anything about our Widget 2.0. The autoloader supplied to the container would presumably know that it is "rooted" from vendor/widgetfactory, so it will only load widgets: 1.0. It works internally as it would normally do, probably not even knowing that PHP introduced the concept of containers. Now for the result of the invocation. As I've said, symbols are now identified not only by name, but also by the "tag". In this case $ourWidget is not just an instance of some \My\Widgets\Widget, it is an instance of ("2.0", \My\Widgets\Widget), where "2.0" would be the default for our current "context" (or also a container, if we're inside one, or if the root context is treated itself as just a root container). In other words (or in pseudo-code) $ourWidget instanceof Widget === $ourWidget instaceof ("2.0", Widget) in current context. But $anotherWidget is not instanceof Widget in the current context. It is instanceof ("1.0", Widget); How can we work with it then? As I've said, we could import 1.0 version alongside our "default" version: use My\Widgets\Widget; // [as Widget] use My\Widgets\Widget tagged "1.0" as WidgetV1; // .... // $anotherWidget instaceof WidgetV1 === true; function convertWidget(WidgetV1 $oldWidget): Widget { // .... perform the mapping to "our" widget } How do we know that? Well, that's the reason why we're containerizing conflicting code: we know exactly that it uses another version of the same class and we're expecting it as a return. If our containerized code does not return any object (like, it's purely executing code or it returns basic data-type), we don't need any "mapping" at all: we just execute that code inside its own "context". Now as for the mentioned "defaults" which are a crucial part of it. In our main "context" when loading Widget class, our autoloader must know where it is rooted from and what version to load by default. Inside the container with WidgetFactory, the default would be "1.0", and autoloader should know that. There could be multiple ways to configure an autoloader for that. If speaking about composer, we could tell it to "root" from "vendor/widgetfactory", and since it by itself requires "my/widgets: 1.0", it'll load a corresponding version 1.0 of the Widget. Or in a more straightforward approach, we could explicitly instruct the loader to load a class from my/widgets: 1.0 when it needs \Me\Widgets\Widget. Of course there are lots of nuances to keep in mind, but the letter is already too big for that. Besides, it feels like I've already started to draft my own RFC for that :) But I hope I could explain what I see under these "containers" or "modules" as Alex initially suggested. Except that I don't see the need to somehow provide "dependencies" on PHP-level itself.

« previous php.internals (#131548) next »