On Wed, Jul 3, 2024 at 5:18 PM Michael Morris <tendo...@gmail.com> wrote:

> Hello all. Hitting reset again as the primary problem at hand has become
> clear.  Let's recap it.
>
> Autoloading is great for loading packages, but it can't load different
> versions of the same package at the same time.  Why would you want to do
> that?
>
> When you don't have full control of the code.
>
> For example, consider Drupal.  It is running Twig at some version of 3 at
> the moment. Suppose Twig 4 is introduced with significant backward
> compatibility breaks (Not saying the authors would do such a thing) but
> also wonderful features.
>
> If you're writing a Drupal extension you might want to use this new Twig.
> This is possible if you are willing to monkey-type the package - that is,
> have a code package traverse over the entire package and change all
> instances of `namespace Twig` in the files to `namespace NewTwig`. You can
> then use the package at the namespace of \NewTwig.
>
> This is painful, but the pain factor increases if multiple extension
> developers choose to do the same thing.  Each extension using its own Twig
> library is going to incur a performance hit.
>
> One upshot of this is I've noted that major package distributors, like
> Symfony, take BC into account with major releases - and may not develop new
> features or change things in those releases out of fear of people not
> wanting to upgrade.
>
> Now don't get me wrong, changing things just because is a bad thing. If a
> BC can be avoided it should be. But having a mechanism to move forward is
> important.
>
> In some ways versioning packages is like static typing variables. It
> doesn't seem important at all until you are faced with a problem only it
> can solve, or faced with a problem created by dynamic typing of variables.
>
> What can be done in the engine?
>
> Well first off, recognize that autoloading isn't going to work with a
> versioned package scheme. Autoloaders, regardless of their resolution
> schema be it PSR-0, PSR-4, or BobbysFirstAutoloader-Scheme can only have
> one symbol per package, set by the namespace.
>
> Can PHP support multiple packages without rewriting the whole engine?  I
> think so, but it isn't trivial, and the side effects need to be cordoned
> off so that those who need this complexity can have it while the beginning
> and intermediate coders can ignore it just like they ignore strict
> comparison operators and strict typing unless a library they are trying to
> use foists it on them.
>
> This is why I advocate a new keyword for this - import.  Import's behavior
> is most similar to require_once, but it doesn't have to be the same.  Since
> it is a new entrypoint into the engine the way the engine considers the
> code can be different - whether slightly different or radically different
> is a debate for another time. I'm going to stick with only those changes
> that make sense in the context of package links.
>
> Let's start with the simplest problem, importing this file.
>
>   namespace A;
>   function foo() { echo 'Hi'; }
>
> To review, if we require_once this file we'll find the function at
> \A\foo().  If our current file uses the same namespace we can just use foo()
>
> At its root import would do the same. `import "file.php"` would do the
> same as a require_once assuming there's no difference between the file
> structure rules for import - again there is opportunity here, but it's not
> a requirement.
>
> If that's all it does, it's pointless.  However, import can alias.
>
>   import 'file.php' as B;
>
> Now we have \B\foo();  This makes it relatively easy to have two different
> versions of the package running since in our own code we can always
> reference the foo in the B namespace. But while that allows limited package
> versioning, it doesn't solve the multiple extensions wanting to use the new
> stuff problem outlined above.
>
> So we have to call out the version in code, like so.
>
>   import 'file.php v1.0.0';
>
> A simple space separates the version from the file.  If the filename has a
> space, well \ characters aren't just for namespaces.
>
> Now for the first real behavior difference between import and
> require_once, even if we aren't doing anything fancy.  Import cares about
> the namespace it's invoked from.  Require_once does not.  To illustrate
> this behavior he's some pseudocode - we are including the file.php given
> earlier
>
>   namespace D;
>   require_once 'file.php';
>
>   \A\foo(); // Hi.
>
>   import 'file.php';
>
>   \D\A\foo(); // Hi.
>
> See that? The namespace of the calling file is prepended to the namespace
> contained in the import.
>
> Why?  What's the value here?  I'll explain.
>
> Now, let's suppose we do have two versions of file.php. So in addition to
> the above, elsewhere in the code this happens
>
>   namespace C;
>   import 'file.php v2.0.0'
>
>   A\foo(); // Welcome, since version 2 echoes welcome. Remember your
> namespace resolution rules - this import is actually at:
>   \C\A\foo(); Welcome, as this is the absolute path to the code we just
> imported.
>   \A\foo(); // Hi, as the package at root was brought in by require_once()
>   \D\A\foo(); Hi, as that's what was imported into the D namespace.
>
> Now for the kicker
>
>   namespace E;
>   import 'file.php';
>
>   A\foo(); // Hi.
>
> The engine can be left as is and this would work, but if the engine is
> altered to support symbolic links on the symbol table then the performance
> hit might be avoided.  That is, when a redundant import occurs that would
> pull the same package the engine just quickly links up the new namespace.
> Hence \E\A\foo() quietly points to \D\A\foo() as it was declared first.
>
>
> What hasn't been discussed in this iteration are the following critical
> points:
> 1) How the package path gets resolved in the first place. Does it work
> like require and check locally then check the PHP include paths?
> 2) When does the code get downloaded from where it is downloaded?
> 3) Is a registry used like composer and npm, or are repos directly invoked
> as in go (I don't remember how Python does it, but someone providing that
> example might be useful)
> 4) The huge ball of wax that is the package definition file. Just look at
> the properties of composer.json and package.json to get an idea of that
> scope. How much of if any of this should PHP deal with.
> 5) Is import to be locked into loading other PHP files, or could it deal
> with .so (Unix) or .dll (Windows) files? Phar files?
>
> It's not like I'm not interested in any of these questions, but too many
> questions at once is too much so I'd like to leave them aside for now.
>
> And there are yet more questions as well raised in previous iterations,
> but I've again left those out because they touched off controversy. While
> I'm not afraid of such, I'm inclined to avoid it if possible.
>
> A quick thank you to everyone who has participated in the thread, even the
> torpedo tossers because it's forcing me to think this through entirely. And
> I'm trying to take as much into consideration as possible.  And yes, this
> remains a brainstorm for now, but each successive brainstorm is more tight
> than the one before it.
>
>
>

I think it's strange that this discussion has driven deep down the tangent
of versioning, as if the selling point of any kind of module/package system
in PHP core would be to do what composer does. Let compose do what composer
does, it does it well.

Instead of building out the specific features like this that honestly
shouldn't be built into the language directly, I would think it makes more
sense for the discussion to be centered around the compiler and engine
features that would LET or ENABLE software like composer to easily meet
these requirements.

Things like separating global scope between importer and importee, managed
visibility of symbols and exports from modules/packages, allowing for
separate autoloaders for things which are called or included via an import,
etc. Those are the things that the language itself can do.

All this other stuff feels like a distraction.

Jordan

Reply via email to