On 25/05/2025 09:27, Rob Landers wrote:
Here are my thoughts, but first some vocabulary:
- direct dependency: a package that is used by the current package
- exported dependency: a direct dependency that can be used outside
the current package
- peer dependency: an indirect dependency on another package that
isn’t required to function but may offer additional functionality if
installed. I have no idea how this would be defined or used yet.
- package: the physical artefact containing one or more modules.
It seems my repeated suggestions of "container" or "sandbox" as the key
concept aren't being picked up. I'm still not sure if people disagree
with my reasoning, or just don't understand the distinction I'm trying
to make.
A key point I want to reiterate is that I think 99% of PHP applications
should not be using the feature we're talking about here. Most of the
time, having a package manager resolve a set of constraints to a set of
versions that are all mutually compatible is emphatically a good thing.
I also think that the "elephpant in the room" here is that any
implementation of this sandboxing is going to be imperfect, because it
just doesn't fit with PHP's nature. There are too many cases like
`return ['\SomeNs\SomeClass', 'someMethod'];` where the compiler won't
know that a class name is being used, so won't know to
rewrite/alias/whatever to the sandboxed version.
It may actually be that anything we design on this list is doomed to be
worse than existing userland implementations, because userland
tools can make specific assumptions about things like the WordPress core
which we could never build into the engine.
Thinking back on several of my implementation explorations of nested
classes, it should be possible to identify if a dependency/class is
able to be used outside the package during compilation.
I think there are many types of "dependency" where you can't automate
the decision, e.g.
namespace MyPlugin;
class MyLogger implements \Psr\Log\LogInterface {
public function logWithBellsOn(\DingDong\BellInterface $bell,
string $message): \Duolog\Message {
return new \ExtraLoud\Message(new \Helpful\BellAdapter($bell),
$message);
}
}
Does that refer to a global \Psr\Log\LogInterface, or a sandboxed
\MyPlugin\Psr\Log\LogInterface? Is \ExtraLoud\Message a dependency which
should be imported/exported, or a hidden implementation detail? And so
on, for every class/interface mentioned.
Only the code's author can say which was intended in each case.
So, you could imagine a generated package manifest might look
something like this:
{
"rootNamespace": "OfficeSuite"
"dependencies": {
"AlicesCalendar": "^1.0.0",
"BobsDocs": "^0.1.0"
},
"exports": {
"dependencies": ["AlicesCalendar"],
"functions": ["OfficeSuite\doSomething"],
"classes": []
}
}
Just to recap, the example wasn't of an office suite. AlicesCalendar and
BobsDocs were supposed to be two unrelated WordPress plugins, which both
happened to use Google's SDK. The only application that would know about
both of them is WordPress, and it wouldn't need to export anything.
The way I'm picturing it is more like this:
```
// The bootstrap file in AlicesCalendar will register a directory of
files as a "Container"
// Within this Container, class definitions and references are rewritten
to use a unique prefix
// The Container also has a separate autoloader stack
Container\register(
// This prefix is added to all names to make them unique
// e.g. \Monolog\Logger might become
\__Container\AlicesCalendar\Monolog\Logger
prefix: 'AlicesCalendar',
// Code in any file in these directories is considered to be
"inside" the container
directories: [
'/var/www/wordpress/wp-plugins/AlicesCalendar/src',
// This directory is probably populated by Composer, but the
Container logic doesn't care about that
'/var/www/wordpress/wp-plugins/AlicesCalendar/vendor',
],
// Classes that should be "imported" from outside the Container
// Classes matching these patterns will not be auto-prefixed
// If they are not defined before use, the "host" (e.g. WordPress
core) autoload stack will be called
import: [
// Classes inside the Container can implement the shared
definition of LoggerInterface
'\Psr\Log\LoggerInterface',
// Classes inside the Container can make use of this namespace
of classes defined outside the Container
'\WordPress\PluginTools\*'
],
// Classes that should be "exported" from inside the Container
// These will use the autoload stack inside the Container, but will
not be auto-prefixed
export: [
'\AlicesCalendar\PluginDefinition',
'\AlicesCalendar\Hooks\*'
],
);
// A completely unmodified Composer autoloader is loaded
// Because it's inside the Container, everything it registers will be on
a separate stack
require_once
'/var/www/wordpress/wp-plugins/AlicesCalendar/vendor/autoload.php';
// The plugin is registered to the application, which doesn't need to
know about the Container setup
wp_register_plugin('\AlicesCalendar\PluginDefinition');
// In a completely separate file, BobsDocs does all the same setup
// It lists its own imports and exports, and uses its own unique prefix
// Any relationship between the two plugins happens in the WordPress
Core code as usual
```
The guiding principle is that the code inside the container should need
as little modification as possible to be compatible, so that all the
code on packagist.org immediately becomes available to whatever plugin
wants it.
--
Rowan Tommins
[IMSoP]