Hi internals,
I would like to ask for early feedback on an idea I have been
exploring recently: runtime modules.
I put my initial idea in writing here:
https://news-web.php.net/php.internals/127343
I have recently been playing around with the idea locally.
It seems technically doable, but it touches enough parts of the engine
that I would like to check whether this direction makes sense before
writing a proper RFC.
The problem has been discussed many times over the past few years. I
am now looking at it from real-life package level usage with Composer,
and how to achieve package symbol isolation in a way that would
minimally impact the ecosystem.
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.
A module would see its own symbols, PHP internal/builtin symbols, and
symbols from its direct dependencies. This maps fairly naturally to a
Composer style model where a package depends on PHP and on other
packages, but not implicitly on application/root symbols.
Just to make it clearer, composer related, I see it this way:
- each package would be defined in its own module
- module dependencies would map directly from Composer package dependencies
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.- All execution contexts have attached to them the module they were
defined for, and any new symbols defined while an execution runs would
have the same module. The exception ismodule_run(), which overrides
the closure module before running it.
The exact API is not the main point at this stage, but I aim to keep it minimal.
I am more interested in whether the model itself is reasonable.
Technically, the engine would need to track module ownership for
compiled code and symbols, keep per-module symbol tables and direct
dependency lists, and make lookup, type resolution, autoload, and
include_once behavior module aware.
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.
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 would like feedback on this package oriented runtime module model,
especially whether you see any major technical blockers or design
flaws.
I aim to work on turning this into a complete RFC within the next 6
months, but it might take more. I am far from experienced with
internals details, and I will most probably need guidance and help
with the implementation.
It is not something I want to rush, as this could be an important
addition to the language and we need to get it right.
Thank you,
Alex
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]
Hi, Alex and Rowan!
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/
I actually really like the idea of some sort of "containers" in general,
because it is quite a common problem in my practice when we have a
more-or-less big monolithic app and eventually come to conflicting
dependencies.
As for passing objects between containers, it does not seem like an
unsolvable problem. Even though our thoughts are very abstract for now,
generally speaking there could be some matching of objects in
inter-container usage. Like, if "my" container is now using
\My\Namespace\SomeObject from my dependencies (let it be, say,
myvendor/mylib: 1.0), when receiving such an object from another
container, it could be known what exact dependency it is from. And if it
is also myvendor/mylib: 1.0 - there should be no problems. If versions
don't match, there could be some explicit inter-dependency mapping defined
by the user, or an explicit runtime error if no mapping was provided.
Such an inter-container mapping mechanism would require careful designing
of course, but technically it does not look unsolvable. Generally
speaking, we could have the mentioned \My\Namespace\SomeObject loaded both
from v1.0 and say v1.1 so we could map one to another on the calling side.
Here comes the quirk that PHP itself is not aware about the composer and
its structure. But PHP can always know where exactly a specific class was
sourced from, so we could use it as a distinguisher. I bet distinguishing
by filename would be a very bad design though. But if PHP allowed to
somehow "tag" loaded classes, it would be handy for containers AND
composer.
Like, when "starting" a new container, the only thing we need to provide is
a class-loader, which would presumably "tag" the loaded classes in a known
manner. In the case of composer, it could tag them by version. In our code,
we could import all needed classes with some advanced syntax with
specifying the "tag". As an example from the top of my head:
use \My\Namespace\SomeObject tagged "..." [as SomeObjectV1], explicitly
telling which version we need. And we could have both versions imported
this way (and map one to another, accordingly). Autoload will receive this
"tag" (if provided) and load a corresponding class. The power of containers
would come with carefully designed defaults for these class versions when
no version was explicitly stated (but container-based autoloader already
knows where it is rooted from).
These are still very rough thoughts though, but with deeper thinking, I believe
it can evolve what Alex and you are suggesting.
Regards,
Alexander Egorov.
As for passing objects between containers, it does not seem like an
unsolvable problem. Even though our thoughts are very abstract for now,
generally speaking there could be some matching of objects in
inter-container usage. Like, if "my" container is now using
\My\Namespace\SomeObject from my dependencies (let it be, say,
myvendor/mylib: 1.0), when receiving such an object from another
container, it could be known what exact dependency it is from. And if it
is also myvendor/mylib: 1.0 - there should be no problems. If versions
don't match, there could be some explicit inter-dependency mapping defined
by the user, or an explicit runtime error if no mapping was provided.
Just knowing the version / source of an individual class is not enough.
At the very least, you need a fingerprint that also includes the
versions of the classes it inherits from, the interfaces it implements,
and the traits it uses.
And then what about other relationships, like the classes they create
and return? For example:
class WidgetFactory {
public function makeWidget(): Widget { ... }
}
$factoryA = new WidgetFactory();
$factoryB = $someContainer->run(fn() => new WidgetFactory);
$widgetA = $factoryA->makeWidget();
$widgetB = $factoryB->makeWidget();
If the container contains the same version of WidgetFactory, but a
different version of Widget, what objects do I end up with?
Like, when "starting" a new container, the only thing we need to provide is
a class-loader, which would presumably "tag" the loaded classes in a known
manner. In the case of composer, it could tag them by version. In our code,
we could import all needed classes with some advanced syntax with
specifying the "tag". As an example from the top of my head:
use \My\Namespace\SomeObject tagged "..." [as SomeObjectV1], explicitly
telling which version we need. And we could have both versions imported
this way (and map one to another, accordingly).
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.
To me, the guiding principle of containers needs to be that the
configuration all exists at the boundary of the container. I'm
thinking of the EXPOSE and VOLUME keywords in a Dockerfile, for example.
--
Rowan Tommins
[IMSoP]
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.
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.
That "most probably" is doing a lot of work. Let me give a real-world
example:
google/apiclient depends on monolog/monolog, which in turn depends on
psr/log
The following code will give you an object which is an instance
Monolog\Logger, and an instance of Psr\Log\LoggerInterface:
$client = new \Google\Client;
$logger = $client->getLogger();
But:
- the current version of google/apiclient can use either version 2.x or
3.x of monolog/monolog - the 3.x series of Monolog is compatible with both version 2.0 and 3.0
of psr/log - the 2.x series is compatible with 1.0.1, 2.0, and 3.0 of psr/log
So even with the exact same version of the \Google\Client class, there
are 6 different combinations which might be installed.
Now what happens if I instead run this:
$client2 = $container->run(fn() => new \Google\Client);
$logger2 = $client2->getLogger();
Even if I detect somehow that $client2 is the same version of
Google\Client I'm using, $logger2 can be any of the 6 different
combinations.
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.
This sounds reasonable, if you replace the "2.0" tag with some kind of
identifier for a whole container. In fact, this is apparently how Java
works: every class definition is silently tagged with the "ClassLoader"
which defined it, and two classes can have the same name but different
ClassLoader tags.
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;
If the idea is that two containers somehow know to give a class entry
the same tag, I don't think it's possible; in the example above, the
"tag" for Google\Client needs to describe not just its own version, but
which of the six possible combinations its getLogger() method will return.
Again, if the "tag" somehow identifies a container, this could work;
but as I mentioned in my first reply, identifying containers by name
seems like it would just re-create the name collision problem we're
trying to solve.
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.
This is what I mean about "module-like" - the code inside the container
has to be modified to be aware of which classes belong to which
packages, and how to annotate them with the correct versions. And what
if some code inside the container has been edited, so doesn't correspond
to any official version?
A DockerFile defines exposed ports, and file system mount points; it
doesn't define a list of packages and their versions which another
container can reach in and interact with.
--
Rowan Tommins
[IMSoP]
On Sun, Jun 28, 2026 at 1:23 AM Rowan Tommins [IMSoP]
imsop.php@rwec.co.uk wrote:
That "most probably" is doing a lot of work. Let me give a real-world
example:google/apiclient depends on monolog/monolog, which in turn depends on
psr/logThe following code will give you an object which is an instance
Monolog\Logger, and an instance of Psr\Log\LoggerInterface:$client = new \Google\Client;
$logger = $client->getLogger();But:
- the current version of google/apiclient can use either version 2.x or
3.x of monolog/monolog- the 3.x series of Monolog is compatible with both version 2.0 and 3.0
of psr/log- the 2.x series is compatible with 1.0.1, 2.0, and 3.0 of psr/log
So even with the exact same version of the \Google\Client class, there
are 6 different combinations which might be installed.Now what happens if I instead run this:
$client2 = $container->run(fn() => new \Google\Client);
$logger2 = $client2->getLogger();Even if I detect somehow that $client2 is the same version of
Google\Client I'm using, $logger2 can be any of the 6 different
combinations.
Thanks for the example, it is a very interesting edge-case. But I think
even this situation is solvable. First, why do I consider it an edge-case.
In my vision, the primary use of containers would be through supplying
proper autoloaders for them. In this case, all code inside the container
and which relies by default on autoloading (which is the majority of cases)
will load all classes in a way we expect with corresponding tags. And so we
would be able to easily match all classes.
Your case differs from that because, as I understand it, it loads classes
manually depending on the conditions. In this case those classes would be
loaded with some meaningless tag for us as a creator of the container, in
other words $logger2 would be an instance of (<some_tag>, Logger). But we
could theoretically still match this class against known versions to us.
For this PHP could introduce alternatives to strict "instanceof". Let's
consider this example: we have some class defined in src/MyClass.php, and
it has been loaded twice with different tags: tag1 and tag2.
In this case $a = ("tag1", MyClass) is not an instance of ("tag2", MyClass)
in a strict (or formal) way. But since it is still the same class
internally (and PHP would be aware of that), we could have a less formal
"instanceof" which would give us true when comparing them. So, coming back
to your example, if we need to strictly map returned logger to our own
symbols, we could try to match it against all known/expected versions of
Logger, like:
use Monolog\Logger\Logger; // our "default" logger
use Monolog\Logger\Logger tagged "1.0" as LoggerV1;
use Monolog\Logger\Logger tagged "2.0" as Logger V2;
$client2 = $container->run(fn() => new \Google\Client);
$logger2 = $client2->getLogger();
match (true) {
$logger2 instanceof Logger => ...., // here I mean less formal instanceof
$logger2 instanceof LoggerV1 => ...,
$logger2 instanceof LoggerV2 => ...,
}
Even though $logger2 would not strictly match to our imported symbols
because it has a meaningless tag to us, we could match it less strictly. In
fact, as it seems to me, such less formal comparison would make sense as a
default behaviour for instanceof.
If the idea is that two containers somehow know to give a class entry
the same tag, I don't think it's possible; in the example above, the
"tag" for Google\Client needs to describe not just its own version, but
which of the six possible combinations its getLogger() method will return.
Well, as I've said, in my vision autoloaders play an important role here
because it is them who decide which tag to attach to loaded classes. And in
this case all classes loaded inside the container could have meaningful
tags for us as a calling side.
Again, if the "tag" somehow identifies a container, this could work;
but as I mentioned in my first reply, identifying containers by name
seems like it would just re-create the name collision problem we're
trying to solve.
In my vision, "tag" does not really identify the container. It is whatever
we wish semantically. For example, let's say we're using Monolog\Logger 2.0
as our "default" logger. But we also use another library which is designed
for 1.0. And for us that Logger from version 1.0 would be identified as
("1.0", Logger) according to the semantics we've chosen and which is
reflected in our autoloader.
While running inside the container, this library will create its own logger
as it would do without any container (assuming it relies on autoloading).
For it it will still just be a Monolog\Logger\Logger class in exactly the
same way as it works now. And it will eventually return this logger to us.
But since it was loaded using the autoloader which we gave to the
container, for us it will already be not just some "unknown" Logger, but
exactly ("1.0", Logger) in our "host" semantics. And as I've described
earlier, even if the library does not rely on autoloading, "their" logger
would not be that meaningful to us directly, as it would have whatever tag.
But we could still match that (<whatever>, Logger) to our own ("1.0",
Logger).
This is what I mean about "module-like" - the code inside the container
has to be modified to be aware of which classes belong to which
packages, and how to annotate them with the correct versions. And what
if some code inside the container has been edited, so doesn't correspond
to any official version?A DockerFile defines exposed ports, and file system mount points; it
doesn't define a list of packages and their versions which another
container can reach in and interact with.
I hope I could explain why no existing code has to be modified to be usable
inside containers, at least as I see it. No need to explicitly "expose"
anything from 3rd-party code. All the "management" would be done on the
calling side. But speaking about the example you provided with google
client, it is a good example why such "polymorphic" libraries themselves
would benefit from such class-versioning and containerization and would
themselves want to switch to it to get rid of all that compatibility
shenanigans.
In my vision, the primary use of containers would be through supplying
proper autoloaders for them. In this case, all code inside the container
and which relies by default on autoloading (which is the majority of cases)
will load all classes in a way we expect with corresponding tags. And so we
would be able to easily match all classes.
OK, then your vision is fundamentally different from mine. I don't want the container to know anything about how the code inside it works.
Remember that autoloading is something that any library can implement its own version of. There are autoloaders which fetch from PHAR files, autoloaders which generate code on demand, and so on.
Version tags break the abstraction of the container, and I don't think they solve a real problem.
Your case differs from that because, as I understand it, it loads classes
manually depending on the conditions.
No, just that the container configuration doesn't know anything about "packages" or "versions". You run 'require "$someDir/vendor/autoload.php";' inside the container as normal. Or maybe you don't, and you just list a big bunch of includes for some reason. The container doesn't care.
As a human author, you maybe know what versions of packages are in your container - depending how tightly you've configured them - but you have no idea what someone else might include in their container.
If you include version 2.11 of Monolog and version 2.0 of psr/log, but some other container includes version 2.11 of Monolog and version 3.0 of psr/log, those objects aren't interchangeable. If you treat them as though they are, something is going to blow up in your face.
$client2 = $container->run(fn() => new \Google\Client);
$logger2 = $client2->getLogger();match (true) {
$logger2 instanceof Logger => ...., // here I mean less formal instanceof
$logger2 instanceof LoggerV1 => ...,
$logger2 instanceof LoggerV2 => ...,
}
I think you're still missing the point - if I pass around $client2, and it passes checks for "instanceof Google\Client", I can pass it to functions which have no idea it came from a different container. They will have no idea that it might return an object from getLogger() which doesn't pass a check for "instanceof Logger".
In my vision, "tag" does not really identify the container. It is whatever
we wish semantically. For example, let's say we're using Monolog\Logger 2.0
as our "default" logger. But we also use another library which is designed
for 1.0. And for us that Logger from version 1.0 would be identified as
("1.0", Logger) according to the semantics we've chosen and which is
reflected in our autoloader.
I think the only way for two containers to agree that a class is the same, is if they both start with that class name already defined, so it points to the same class entry in memory.
Otherwise, containers can completely break each other by using the same tags for different definitions.
But speaking about the example you provided with google
client, it is a good example why such "polymorphic" libraries themselves
would benefit from such class-versioning and containerization and would
themselves want to switch to it to get rid of all that compatibility
shenanigans.
On the contrary, the entire point of packages like psr/log is to define objects which can be passed around from one package to another. By advertising that it can be installed with multiple versions of something, a library is giving applications freedom to resolve a set of versions which works for them.
That's exactly why I've been arguing for a distinction between "modules" and "containers", and saying that the vast majority of current code would not benefit from containers at all.
Rowan Tommins
[IMSoP]