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]

Reply via email to