So let's take another crack at this based on all the points raised in the thread. This should also underline why I don't consider this an RFC - I am iterating until we arrive at something that may be refinable into an RFC. And I say we because without the aid of those in this conversation I would not have arrived at what will follow.
Before I continue I would like to apologize for being somewhat irritable. We're all here because we enjoy using this language and want to see it improved and prevent bad changes. Opinions will differ on this and in the heat of the moment of arguing a point things can get borderline. Returning to a point I made earlier, Composer isn't used on Wordpress. I went over to the Wordpress discussion list and read over why, because that discussion provides clues to what kind of package management may be adoptable. I think the largest point is that Wordpress should be usable without ever resorting to using the command line. Yes, it does have a command line tool - wp-cli - and it is powerful, but using it as an administrator of a Wordpress site is not required. The largest block to composer's inclusion in Wordpress is the inability to run multiple versions of a module. Yes, it's a mess when this happens, but if you're an end user, you just want your plugins to work. If one plugin that no one has updated in a year that you're using is consuming version 2 of a package, you're gonna be annoyed at best if the module stops working when you install a new plugin that is using version 3 of the same package and has a BC break in it. Composer can't resolve this easily. There are WordPress plugins that use composer - I have a couple in the website I'm working on. But they accomplish the inclusion of composer by redistributing the packages, and using a utility called brianhenryie/strauss to monkey type the entire included package into the plugin, changing the namespace of the entire package to something different. The approach works, but it's ugly. In any event, the plugin that results from this carries a copy of the code from packagist rather than sourcing the code from packagist. -- IMPORT -- The import statement is for bringing in packages. It needs to be able to deal with: * Extensions - the existing and oldest of packages for PHP * PECL Extensions * Phar Packages * Composer Packages * PHP Modules - this is the new module system that has dominated the conversation, but in this iteration it's going to be broken away from import to some degree in this iteration. Today we'll look just at composer. Now import needs to load packages in a manner that allows different versions to be run concurrently. A PHP application such as Wordpress should be distributable without needing to use the command line. That is, if WordPress leverages this in any way, they don't have to give up their famous 10 minute quick install. Some terms here to keep myself from getting lost (let alone anyone trying to read this). * APPLICATION - This is the overall application - WordPress, Drupal, BobbysFirstFramework, etc. - that is doing the import. This code is on the root scope. * ROOT SCOPE - This is where the global variables and the namespaces as we know them exist. Contrast this with * PACKAGE SCOPE - Each package brought in with import gets its own package scope. This is a distinct behavior from Include/Require. I think each package scope will need to be on its own request thread, but this is an implementation detail I can't speak to with any authority. The goal is whatever happens in a package stays in the package. If two different packages want to define /foo(), they can. When a package is imported the parser will look for a `.php.mod` file at the root of the package. Among other things, this file details what type of package it is and where to mount it by default in the namespace of the ROOT SCOPE. So, GIVEN a package with this .php.mod file package MyModule WHEN I issue this import in an application import "MyModule"; THEN I should be able to access a method in that module with this code \MyModule\foo(); Aliasing is an option - `import "MyModule" as BeerModule` will make the methods accessible in the root with \BeerModule\foo(); Unlike require/include import is sensitive to the namespace it is in for mounting. So namespace Trees; import "MyModule"; MyModule\foo(); // works \Trees\MyModule\foo(); // needed from another namespace. That said, with aliasing an absolute namespace for the module can be assigned. namespace Trees; import "MyModule" as \MyModule; MyModule\foo(); // works if my understanding of existing namespace resolution rules is correct. \MyModule\foo(); // also works. Now, with that in place, let's crack a tougher nut - handling a composer package. By default composer is designed to set up an autoloader, then resolve symbol references as they come up. This works until you have two packages that want the same symbol reference - which will most frequently occur with incompatible versions of the same package. So our puzzle here is how to allow composer to do its thing without rewriting it. We'll deal with admittedly the hardest case first - importing a package whose maintainers have taken no action to make it compatible with this new system. import "composer://packagist.org/packages/twig/twig#v3.10.3" as TemplateEngine The reason for that alias and not "Twig" is because the mounting point comes before the internal namespace of the file. This is unavoidable with this scheme The URL there is "loader://package_url". PHP by default will know what the composer loader is. It will look to see if the user has globally installed composer already and use that, otherwise it will locally install composer for the project, initialize it, download the package and have composer resolve the package ending in setting up an autoloader that is only invoked within that package. Application configuration can make a lot of this go away. So let's step away from the import statement itself to look at that. -- APPLICATIONS -- Applications can configure how they store their packages, but in the absence of such PHP will use some logical default behaviors. We've already looked at one, the loading of a composer package, but we have a long ugly import as a result. Most PHP applications have a single point of entry, and part of that is the establishment of a cwd (current working directory). When PHP loads a file it will look for a `.php-packages` directory in the current working directory and if one doesn't exist it will make one the first time the import statement is invoked (so code not using import will not have this directory created). It is here that the package downloads land. We can also choose to go ahead and make this directory ourselves and place `.php.mod` in that directory. Let's look at what one might look like for Drupal, which already uses composer. package Drupal; php: 10 registry packagist.org/packages composer imports ( phar://getcomposer.org/composer.phar ) init ( composer install ) require ( ./vendor/autoload.php ) Now, composer is a known and popular quantity, so the imports, init and require directives can probably be baked into PHP, but if they ever change - or if a competitor to composer shows up like yarn did to npm then there needs to be a way to set it up. Also, for the moment I'm using go.mod's format because it feels the cleanest. The exact format of this file - whether it's yaml, json, toml, .ini or whatever else, is a discussion for another day. Key in on the type of information that needs to be relayed here, not how it's relayed. Importantly, because this .php.mod file is at the top level of the application's .php-packages directory it affects the behavior of the ROOT SCOPE. The php directive gives the minimum php version for the application. The registry directive sets the registry to packagist and sets the loader for that registry to composer. Multiple registries can have separate loaders. The imports directive loads composer using the default phar loader. We use an absolute path because we don't have a phar registry. This particular call could be baked in due to composer's popularity. The init directive runs the first time the application runs, just before any file in the application is parsed. The .php.sum file will bookmark the last time the init has run and there will likely need to be a mechanism to force it's rerun. The require directive requires these files once before starting the application's first file. Assume we move our existing composer.json file into .php-packages, what then? We gain the following: * Composer install will be ran for us, without using the cli. * The autoloader will be set up for us without explicitly requiring it anywhere. * We can install an alternate version of packages into their own package scope import "twig/twig#v2.5" as OldTwig; Or again, if we are in a namespace we can import to that namespace. I'll stop here cause this is a healthy chunk to absorb and I just spent 4 hours thinking on this as I wrote it out and I'm tired. If this was in the language today, Wordpress plugins could start using it without fear of mucking each other up or messing with Wordpress core. But that's a discussion for later.