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