On Sat, Jun 27, 2026 at 9:00 PM Rowan Tommins [IMSoP]
<[email protected]> 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.