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.

Reply via email to