On Sun, Jun 1, 2025, at 09:17, Rob Landers wrote: > > > On Sun, Jun 1, 2025, at 07:26, Michael Morris wrote: >> Ok, the conversation is getting sidetracked, but I think some progress is >> being made. >> >> I started this latest iteration last year with a thread about introducing >> something similar to the ES module system of JavaScript to PHP. What >> attracts me to this particular model is that it should already be familiar >> to the vast majority of PHP users. Prior to ES modules browsers had no >> natural module import mechanic. Prior to ES modules all symbols were >> attached to the window. You can see this if you serve open this index.html >> from a server (Note that opening the file locally will result in the js >> being blocked by modern browser security. ) >> >> ```html >> <!DOCTYPE html> >> <html> >> <head> >> <script> >> var a = 1234 >> </script> >> </head> >> <body> >> <script> >> console.log(a) >> console.log(window.a) >> </script> >> </body> >> </html> >> ``` >> The above spits 1234 into the console twice. Second example - let's put a >> module in. >> >> ```html >> <!DOCTYPE html> >> <html> >> <head> >> <script> >> var a = 1234 >> </script> >> <script type="module"> >> const a = 5678 >> var b = 9123 >> </script> >> </head> >> <body> >> <script> >> console.log(a) >> console.log(window.a) >> console.log(b) >> </script> >> </body> >> </html> >> ``` >> This outputs 1234 twice and an error is raised about b being undefined. >> >> I bring the above up to demonstrate that is the desired behavior of what I >> originally called a PHP module and have been bullied over and taken to task >> about not understanding the meaning of "module". Rowain seems to be more >> comfortable characterizing this as containers. If everyone is happy with >> that term I really don't care - I just want a way to isolate a code block so >> that whatever happens in there stays in there unless I explicitly export it >> out, and the only way I see things in that scope is if I bring them in. >> >> The other thing that was done with ES is that the syntax for the modules was >> tightened. JavaScripters cannot dictate what browser a user chooses, so the >> bad decisions of the early days of JS never really went away until ES came >> along which enforced their strict mode by default. PHP has no such strict >> mode - it has a strict types mode but that isn't the same thing. There are >> multiple behaviors in PHP that can't go away because of backwards >> compatibility problems, and one of those might indeed be how namespaces are >> handled. In PHP a namespace is just a compile shortcut for resolving symbol >> names. The namespace is prefixed to the start of every symbol within it. >> Unlike Java or C#, PHP has no concept of namespace visibility. At the end of >> the day it's a shortcut and its implementation happens entirely at compile >> time. >> >> Previously in the discussion Alwin Garside made a long but insightful post >> on namespaces and their workings that I've been thinking on and trying to >> digest for the last several days. What I've arrived at is the discussions >> about composer and autoloaders are indeed a red herring to the discussion. >> At the end of the day, PHP's include statements are a means to separate the >> php process into multiple files. In his email he explored some of the >> rewriting that could be done, and myself and Rowain have also explored this >> in the form of namespace pathing and aliasing. >> >> We've gotten away from the original focus of containing this code and how >> that would work. So once again this moron is going to take a stab at it. >> >> Container modules are created with require_module('file/path'). All code >> that executes as a result of this call is isolated to its container. That >> includes the results of any require or include calls made by the module file >> itself or any file it requires. >> >> Since the module file is cordoned off to its own container from the rest of >> the application whatever namespaces it uses are irrelevant to outside code. >> Any symbols created in the module will not be established in the script that >> made the require_module() call. Since it is coming into being with a new >> require mechanism it could be subjected to more efficient parsing rules if >> that is desired, but that's a massive can of worms for later discussion. One >> of those will be necessary - it will need to return something to the php >> code that called it. The simplest way to go about this is to just require >> that it have a return. So... >> >> $myModule = require_module('file/path'); >> >> or perhaps >> >> const myModule = require_module('file/path'); >> >> The module probably should return a static class or class instance, but it >> could return a closure. In JavaScript the dynamic import() statement >> returns a module object that is most similar to PHP's static classes, with >> each export being a member or method of the module object. >> >> Circling back to a question I know will be asked - what about autoloaders? >> To which I answer, what about them? If the module wants to use an autoloader >> it has to require one just as the initial php file that required it had to >> have done at some point. The container module is for all intents and >> purposes its own php process that returns some interface to allow it to talk >> to the process that spawned it. >> >> Will this work? I think yes. Will it be efficient? Hell no. Can it be >> optimized somehow? I don't know. >> > > This could work! I have a couple of critiques, but they aren’t negative: > > I think I like it. It might be worth pointing out that JavaScript "hoists" > the imports to file-level during compilation — even if you have the import > statement buried deep in a function call. Or, at least it used to. I haven’t > kept track of the language that well in the last 10 years, so I wouldn’t be > surprised if it changed; or didn’t. I don’t think this is something we need > to worry about too much here. > > It’s also worth pointing out that when PHP compiles a file, every file has > either an explicit or implicit return. > https://www.php.net/manual/en/function.include.php#:~:text=Handling%20Returns%3A,from%20included%20files. > > So, in other words, what is it about require_module that is different from > `require` or `include`? Personally, I would then change PHP from "compile > file" mode when parsing the file to "compile module" mode. From a totally > naive point-of-view, this would cause PHP to: > 1. if we already have a module from that file; return the module instead of > compiling it again. > 2. swap out symbol tables to the module’s symbol table. > 3. start compiling the given file. > 4. concatenate all files as included/required. > 5. compile the resulting huge file. > 6. switch back to the calling symbol table (which may be another module). > 7. return the module. > For a v1, I wouldn’t allow autoloading from inside a module — or any > autoloaded code automatically isn’t considered to be part of the module (it > would be the responsibility of the main program to handle autoloading). This > is probably something that needs to be solved, but I think it would need a > whole new approach to autoloading which should be out of scope for the module > RFC (IMHO). > > In other words, you can simply include/require a module to load the entire > module into your current symbol table; or use require_module to "contain" it. > > As for what should a module return? I like your idea of just returning an > object or closure. > > — Rob
I just had another thought; sorry about the back-to-back emails. This wouldn’t preclude something like composer (or something else) from being used to handle dependencies, it would just mean that the package manager might export a "Modules" class + constants — we could also write a composer plugin that does just this: require_once 'vendor/autoload.php'; $module = require_module Vendor\Module::MyModule; where Vendor\Module is a generated and autoloaded class containing consts to the path of the exported module. — Rob