On Mon, Jul 8, 2024 at 2:42 AM Rowan Tommins [IMSoP] <imsop....@rwec.co.uk> wrote:
> > > On 8 July 2024 04:25:45 CEST, Jordan LeDoux <jordan.led...@gmail.com> > wrote: > >I think it's strange that this discussion has driven deep down the tangent > >of versioning... > [...] > >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. > > I agree. I wrote most of the below a couple of days ago, but I don't think > it posted correctly, so apologies if some people see it twice: > > Autoloading is just a way to load files later, by the engine telling you > when a class is first needed. PHP does not, and should not, make any > assumptions about how files are laid out on disk; an autoloader doesn't > actually need to load any files at all, and if it does, it uses the same > include or require statements which have been in PHP for decades. > > Likewise, installing packages and defining version schemes is a completely > separate problem space that can probably be served by a few small tweaks to > Composer once the language provides the underlying functionality. > > The core of the problem you seem to want to solve is this: if you have two > files foo_1.php and foo_2.php, which both define a class \Acme\Foo, how do > you load both of them, so that you end up with two differently named > classes? > > In JS, that's easy, because functions and object constructors (and > "classes") exist as objects you can pass around as variables, they don't > need to know their own name. In PHP, everything is based on the idea that > functions and classes are identified by name. You can rewrite the name in > the class declaration, and in direct references to it, but what about code > using ::class, or constructing a name and using "new $name", and so on? How > will tools using static analysis or reflection handle the renaming - e.g. > how does DI autowiring work if names are in some sense dynamic? > > You've also got to work out what to do with transitive dependencies - if I > "import 'foo_1.php' as MyFoo", but Foo in turn has "import 'guzzle_2.php' > as MyGuzzle", what namespace do all Guzzle's classes get rewritten into? > What about dependencies that are specifically intended to bridge between > packages, like PSR-7 RequestInterface? > > My advice: start with the assumption that something has already installed > all the files you need into an arbitrary directory structure, and something > is going to generate a bunch of statements to load them. What happens next, > in the language itself, to make them live side by side without breaking? If > we get a solid solution to that (which I'm skeptical of), we can discuss > how Composer, or the WordPress plugin installer, would generate whatever > include/import/alias/rewrite statements we end up creating. > > Regards, > -- > Rowan Tommins > [IMSoP] > Rowan Tommins > [IMSoP] > I think it could be done somewhat simply (relative to the other things that have been discussed) if the engine reserved a specific namespace for imported symbols internally. Something like: `\__Imported\MyImportStatement` Where the `\__Imported` namespace is reserved and throws a parser error if it occurs in code anywhere, and `MyImportStatement` corresponds to an application importing the code using something like `import MyPackage as MyImportStatement;` Then, all symbols which are loaded into the global space as a result of the import are actually rewritten into the hidden namespace the engine actually uses under the hood, and any uses from the import statement in the application code which has the import would reference the symbols in the prefixed namespace. This would not be trivial however. The engine code which supports this would need to keep track of a kind of "context" for each file, based on what namespace the file was included from. For instance, if an autoload occurs inside the package that was loaded into `MyImportStatement`, the engine would need to be aware that the code being executed is defined in that namespace, REGARDLESS of whether it was a class, function, or statement, and load ALL symbols that are created as a result into the rewritten namespace. It would also need to translate in the other direction for `use` statements inside the package, since it would not know ahead of time what rewritten namespace it would actually be loaded in. However, this is the simplest solution I see that doesn't involve writing a second PHP engine just for this sort of thing. Jordan PS: For those unaware, for each "symbol" (something that has a unique referenceable name in the code, roughly), there is at least one name that refers to ONLY that thing internally. (I'm fairly certain that there are NO situations where one name can refer to two things at all, but I am not enough of an expert in the C code to be completely certain about this, and it's entirely possible this is in fact a niche common thing that I've never encountered before). When something is namespaced, the entire namespace in the engine is prefixed to the "name" of the thing when it is created. So a function `foo` in the namespace `Bar` has the name "\Bar\foo". Any time you use it as just "foo", the engine because of context knows to put "Bar" in front of it before looking up its definition to execute it. The global symbol space are the items which have nothing prepended, and if a namespace was inaccessible because the parser errored on `use`, `namespace`, `new`, and other similar statements that used a part of that namespace like I outlined, the result would be that for the engine it would treat all of the code as if it were one application that it knows some extremely complex namespace replacement rules for (because that's what it actually would be), but to PHP devs it would act almost like sandboxes where code from one area cannot access or affect other areas. This would create some edge cases, like how `global` behaves, or how any of the superglobal variables could be used, etc. But those are probably easier to nail down than writing a different engine or running a second process and setting up some messaging between the two. Though that might be the more "correct" way to handle something like this. However, I could be wrong about the difficulty of this, as I've never attempted that kind of change in the Zend engine before.