Re: [PRE-RFC] Runtime modules

From: Date: Sun, 28 Jun 2026 08:49:51 +0000
Subject: Re: [PRE-RFC] Runtime modules
References: 1 2 3 4 5 6  Groups: php.internals 
Request: Send a blank email to internals+get-131551@lists.php.net to get a copy of this message
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/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. 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.

« previous php.internals (#131551) next »