The Problem: Interoperability.

That's really it.  Scenario
Alice provides whatchamacallit A that depends on other whatchamacallit D to
work.
Bob provides whatchamacallit B that also depends on D.
Charles is using A and B.
D gets updated with a new incompatible API to its prior version.
Alice publishes an update which includes a security fix.
Bob retired.
Charles, who can't program, can't update to Alice's latest code. His site
eventually gets pwned.

That's the problem. Packages with dependencies are not interoperable at
this time. They must be self contained. This is why WordPress doesn't
support Composer at all.

Drupal, Laravel et al bypass this problem by forcing all their
whachamacallits to stay on the same version. This has limited their market
penetration compared to WordPress because, despite being significantly
superior codebases in all respects, they aren't user friendly to someone
who doesn't code at all.


The Solution (10,000 overview)
Composer could be made to allow interoperable packages, but it will need
support at the language level to do so.  Specifically, it needs to know who
wants what. It can then make decisions based on that information.

Composer's primary link to the language is the autoload closure it
provides. That closure currently takes one argument - the fully qualified
name of the symbol to be loaded - currently almost always classes as for
various reasons function autoloading isn't a thing.  Can it not take a
second argument to modify its behavior?  The current behavior is to flat
require the file if it is found in accordance to whichever schema is in
use.  Perhaps we don't want that anymore - perhaps we want to return the
file path to use. This allows the engine to make decisions about how
exactly to include the file, including the possibility of monkey typing it
as can be done in userland, though when done in userland this effectively
generates a new package.


(5,000 ft. overview)
Suppose we have a whatchamacallit that declares its namespace as a new root
independent of / .  If a file inclusion happens in this namespace, this
namespace prepends everything in the included file. So if I do a file
include in the \MyPlugin namespace and that file declares its namespace as
Twig, it will become \MyPlugin\Twig.

That works, but direct file include is no longer the PHP norm though.
Autoloading is. So we need to tell the Autoloader that we want a file path
returned - do NOT require the file yourself in your namespace.  This could
be as simple as a boolean flag of true sent to the autoloader. BUT it isn't
- the autoloader (usually composer) needs to know the identity of this
requestor because by configuration in the package json (the details of
which are wildly out of scope) it might change which file path it returns.

When the engine gets the path it does the include and the prepending
business on the fly that Strauss and similar packages already do
in userland.

(2,500 ft overview)

The above I think would more or less work, but it would lead to massive
code duplication as Whatchamacallit A and B now have their own D's at \A\D
and \B\D (assuming namespaces match whatchamacallit names).

Here's what I think would prevent that:

A asks the autoloader for D. The autoloader returns a file path and the
engine mounts to \D
B asks for D. The autoloader returns a different file path so the engine
mounts to B\D and rewrites the D file with the new namespace the same way
Stauss would have done.

This works except for the problem of who had the older version, A or B? and
what order are A and B going to be asking - cause depending on the
application's architecture this order is not guaranteed.

To solve this the autoloader can tell the engine it is safe to mount the
file on root using an array return of [path, true] and mount on the
whatchamacallit's namespace if [path, false]. So

A asks for D. Autoloader returns [path, false]. Engine maps to \A\D and
monkey types D as needed.
B asks for D. Autoloader returns [path, true]. Engine maps to \D

Non whatchamacallit code at namespace C asks for D. It will get the same
version B is using and the autoloader shouldn't be queried unless C makes
this ask before B.

When C asks the autoloader gets (string RequestedSymbol, null) so it can
either do the require itself or return a string, either will work (and it
has to be this way for backwards compat).
When B asks the autoloader gets ( Requested, 'B' ) and it should return
[path, true]


I hope the above is followable. It's more of a morning brainstorm than a
spec.

Reply via email to