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

Reply via email to