Re: [Initial Feedback] PHP User Modules - An Adaptation of ES6 from JavaScript

From: Date: Sat, 29 Jun 2024 09:39:57 +0000
Subject: Re: [Initial Feedback] PHP User Modules - An Adaptation of ES6 from JavaScript
References: 1 2 3 4 5 6  Groups: php.internals 
Request: Send a blank email to [email protected] to get a copy of this message
> On Jun 29, 2024, at 2:32 AM, Michael Morris <[email protected]> wrote:
> 
> Not replying to anyone in particular and instead doing a mild reset taking into account the
> discussion that has gone before. 
> 
> So, I want to import a package. I'll create an index.php file at the root of my website
> and populate it with this.
> 
> <?php
> import "./src/mymodule";
> 
> Now I'll create that directory and run a command php mod init in that
> directory. Stealing this from Go, it's fairly straightforward though.  Now if we look in the
> directory we will see two files.
> 
> php.mod
> php.sum
> 
> The second file I'll not be touching on but exists to track checksums of downloaded
> packages - Composer does the same with its composer-lock.json file which in turn was inspired by
> node's package-lock.json.
> 
> The php.mod file stands in for composer.json, but it isn't a json file.  It would start
> something like this:
> 
> namespace mymodule
> php 10.0
> registry packagist.org/packages <http://packagist.org/packages>
> 
> We start with three directives - the root namespace is presumed to be the directory name. If
> that isn't true this is a text file, change it.  PHP min version should be straightforward.
> Registry details where we are going to go get code from.  Suppose we want to use our own registry
> but fallback to packagist.  That would be this:
> 
> namespace mymodule
> php 10.0
> registry (
>   github.com/myaccount <http://github.com/myaccount>
>   packagist.org/packages <http://packagist.org/packages>
> )
>  
> Multiple registry entries will be checked for the code in order.  Handling auth tokens for
> restricted registries is outside of scope at the moment.

That is very Go-like, as you stated. 

However, be aware that in a Go project repo you are likely to have only one go.mod —
or multiple if you have numerous CLI apps being generated — whereas every directory with Go code
is a package (which I think is equivalent to what you are calling "module."

So I think your use of them here is conflating the two concepts. One is a project-wide concept and
the other is a "package" concept.

Maybe you would be better to adopt module to mean project and package to
mean packaged code as Go has them?

From here on I will refer to directory rather than module or package to avoid confusion. By
directory I will mean what Go calls a "package" and what I think your original proposal
called a "module."

A big difference between Go and PHP is that Go have a compiler that compiles into an executable
before it runs. That is clearly not compatible with PHP, and why I was proposing that each directory
could have a pre-compiled .php.module that could be pre-compiled, or compiled on the
fly at first import.

Also, it is problematic to have php.mod and php.sum because web servers
would serve them if not carefully configured hence why I went with a leading dot, e.g.
.php.module

> 
> So let's build the module. We'll make a file called hello.phm.  The reason for phm
> and not php is so that web SAPIs will not try to parse this code.  Further they can be configured to
> not even allow direct https access to these files at all.
> 
> import "twig/twig";
> use \Twig\Loader\ArrayLoader;
> use \Twig\Environment;
> 
> $loader = new ArrayLoader([
>   'index' => 'Hello {{ name }}'
> ]);
> 
> $twig = new Environment($loader);
> 
> export $twig;
> 
> As mentioned in previous discussions, modules have their own variable scope.  Back in our index
> we need to receive the variable
> 
> <?php
> import $twig from "./src/mymodule"
> 
> $twig->render('index', ['name' => 'World']);


Aside from being familiar per Javascript, what is the argument to requiring the import of specific
symbols vs just a package import, e.g.:

<?php
import "./src/mymodule"

mymodule->twig->render('index', ['name' => 'World']);

To me is seems to just add to boilerplate required.  Note that having mymodule
everywhere you reference twig makes code a lot more self-documenting, especially on
line 999 of a PHP file. 🙂

> 
>  If we load index.php in the web browser we should see "Hello World".  If we look
> back in the mymodules folder we'll see the php.mod file has been updated
> 
> namespace mymodule
> php 10.0
> registry packagist.org/packages <http://packagist.org/packages>
> 
> imports (
>   twig/twig v3.10.3
>   symfony/deprecation-contracts v2.5 //indirect
>   symfony/polyfill-mbstring v1.3 //indirect
>   symfony/polyfill-php80 v1.22 //indirect
> )

Having a php.sum file is interesting but again, it should start with a period if so.

That said, I wonder if incorporating versioning does not make the scope of modules too big to
complete?

> Note the automatically entered comment that marks the imported dependencies of twig. Meanwhile
> the php.sum file will also be updated with the checksums of these packages.
> 
> So why this instead of composer?  Well, a native implementation should be faster, but also it
> might be able to deal with php extensions.
> 
> import "@php_mysqli"

I would like this, but I think hosting vendors would block it since extensions can have C bugs and
create vulnerabilities for servers.

I have long thought PHP should kick off a new type of extension using WASM, which can be sandboxed. 

But I digress.

> 
> The @ marks that the extension is either a .so or .dll library, as I'll hazard a guess
> that the resolution mechanic will be radically different from the php language modules themselves -
> if it is possible at all. If it can be done it will make working with packages that require
> extensions a hell of a lot easier since it will no longer be necessary to monkey the php.ini file to
> include them. At a minimum the parser needs to know that the import will not be in the registry and
> instead it should look to the extensions directory, hence the lead @. Speaking of, having the
> extension directory location be a directive of php.mod makes sense here.  Each module can have its
> own extension directory, but if this is kept within the project instead of globally then web SAPIs
> definitely need to stay out of those directories.
> 
> Final thing to touch on is how the module namespaces behave. The export statement is used to
> call out what is leaving the module - everything else is private to that module. 
> 
> class A {}  // private
> export class B {}  // public
> 
> All the files of the package effectively have the same starting namespace - whatever was
> declared in php.mod.  So it isn't necessary to repeat the namespace on each file of the
> package. If a namespace is given, it will be a sub-namespace
> 
> namespace tests;
> 
> export function foo() {}
> 
> Then in the importing file
> 
> import "./src/mymodule"
> use \mymodule\tests\foo
> 
> 
> Notice here that if there is no from clause everything in the module grafts onto the symbol
> table.  Subsequent file loads need only use the use statement. Exported variables however must be
> explicitly pulled because the variable symbol table isn't affected by namespaces (if I recall
> correctly, call me an idiot if I'm wrong).
> 
> The from clause is useful for permanently aliasing - if something is imported under an alias it
> will remain under that alias. Continuing the prior example
> 
> import tests\foo as boo from "./src/mymodule";
> 
> boo()
> 
> That's enough to chew on I think.

I don't think it is wise to intertwine this concept of modules with namespaces like that, but I
am replied out for the night. :-)

-Mike



Thread (128 messages)

« previous php.internals (#124019) next »