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 internals+get-124019@lists.php.net to get a copy of this message
> On Jun 29, 2024, at 2:32 AM, Michael Morris <tendoaki@gmail.com> 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

« previous php.internals (#124019) next »