Re: Iteration III: Packages (was Re: [PHP-DEV] [Initial Feedback] PHP User Modules - An Adaptation of ES6 from JavaScript)

From: Date: Sun, 30 Jun 2024 21:18:20 +0000
Subject: Re: Iteration III: Packages (was Re: [PHP-DEV] [Initial Feedback] PHP User Modules - An Adaptation of ES6 from JavaScript)
References: 1 2 3 4 5 6 7 8 9 10 11  Groups: php.internals 
Request: Send a blank email to [email protected] to get a copy of this message


On Sun, Jun 30, 2024, at 22:28, Michael Morris wrote:
> 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();

Just to challenge you a bit here. The language already has use for this. Does
use stay or is it replaced with import and why not just change the meaning
of use in a package context?

> 
> 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.

Adding this after I just wrote the below book. It started out simple enough and it drives me crazy
when people crash my proposal with a counter-proposal. So, by all means, take it with a grain of
salt ... I kinda went overboard thinking about it.

I think composer and friends are a moot point. If we go a bespoke way for everything, we end up with
a mess. What about creating "hooks" that things like composer can "register" an
installer at? For example, we could define a "WELL_KNOWN/installers/composer/hooks.json"
(I'm gonna steal a bunch of ideas from kubernetes from here on), where WELL_KNOWN is some
engine-specific directory (like where the php.ini file is). Basically, any installer can register an
installer by creating a directory in WELL_KNOWN/installers of which an installer might look like the
following for composer:

WELL_KNOWN/
  installers/
    composer/
      hooks.json
      composer.phar
      cache

and hooks.json could have a schema of something like:

{
  "name": "composer",
  "version": "4.5",
  "executable": "./composer.phar",
  "scheme": "composer",
  "command": "install-package-module"
}

Then the engine can scan these directories and read each hooks.json. Then when it gets to your
example above, it sees the scheme "composer" in the URL, looks for an installer with that
name, and calls the executable with some arguments (the command, the "URL" aka the
package, current directory, etc). So, it might call composer with something like:

/WELL_KNOWN/installers/composer/composer.phar install-package-module
packagist.org/packages/twig/twig#v3.10.3 /app/public

The command is expected to dump the file to stdout -- which PHP then pipes to wherever it is
supposed to go (as you mention below).

At this point, the user may not know a single thing about composer or how it works, or anything,
really. As far as they are concerned, they said they wanted an import and they got one. However,
there still exists a bunch of composer-specific scripts, etc. In this case, we make new WELL_KNOWN
types. For example, we can have "script-runners" that can be registered so you can just do
"php run composer test" and it will run composer test for you. If you want
something shorter, you can just add alias composer="php run composer" to your
shell and bob's your uncle. 

This is basically how kubernetes handles networking, disks, etc. so that the entire thing is
completely swappable and extendable. So, we know the idea is sound and "just works," we
just need to customize it for php.

— Rob


Thread (128 messages)

« previous php.internals (#124116) next »