On Sat, Jun 29, 2024, at 08:32, Michael Morris 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.
I don't think that is correct... package-lock.json didn't come about until what, 2016-7ish? with pressure from yarn which did a yarn.lock file. Pretty sure composer was doing that since the beginning. I remember this being a BIG reason we switched from npm to yarn when it came out, because dev A would have different versions of libraries than dev B. Bug hunting was FUN when it was in a library. > > 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 >> > 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 >> 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. While this looks good on paper, you're going to have to standardize how packages are accessed (API calls, etc) so they can be used in this file, or literally anyone who wants to add a competing registry will have to create an RFC to allow accessing their own registry, which is a ton of politics for something that is strictly technical -- not to mention a bunch of if-this-registry-do-that type statements scattered throughout the code, which makes it harder to maintain. > > 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; > SAPIs are the programs that parse ALL php code and return it to the server (ie, nginx, apache, caddy, etc) to be displayed. The SAPI absolutely needs to parse these files in order to execute them. Servers are designed to display files, so any server configured today will just output the contents of these files because it won't be configured to send the request to the SAPI instead. It's better to suggest moving these files out of the web-root so it's a non-issue. In other news, I'm not a fan of how many times I have to write "twig" just to get Twig in the current file. The module already registers a namespace, why can't the use-statement implicitly import the module? > 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']); > > 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 In real life, my code is going to be in a module/framework and I'm going to need to render it there. This example of exporting a dependency also kinda breaks encapsulation principles, and even though it is an example, things like this end up in documentation of a feature and cause all kinds of bad practices (like Symfony and anemic objects). > >> namespace mymodule >> php 10.0 >> registry 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 >> ) > > 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. One of the first things I do in a composer.json file is remove polyfills through the replace key. It's unnecessary, annoys me in my IDE with having multiple classes of the same name, and hides the fact that I should probably install an extension for better performance. How do we do that with this new setup? In fact, it is worth pointing out that how would this system work with polyfills in-general? Polyfills have their uses -- especially for library/framework code where you don't control the runtime environment. Like how would someone polyfill mb_string since people will be adding `import @mbstring` and not `import symfony/polyfill-mbstring`? > > 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" >> > 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. So ... if we want to round, we have to use `import @math` and then we can call the global round() function? Or if we want to use DateTimeImmutable we have to add `import @date`? That seems like a step in the wrong direction since most people don't even know that most (if not all) global library functions come from extensions -- and virtually nobody knows the name of each extension and what functions they have. Also, installing extensions is not 100% straightforward as some environments need to use pecl, some need to use OS package managers. > > 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. — Rob