> On Jul 5, 2024, at 1:47 PM, Michael Morris <tendo...@gmail.com> wrote: > I went to sleep thinking about this post, on import maps in general and how > Composer works, specifically when you use a class map instead of the PSR-0 or > PSR-4 schemes. In that mode, Composer does pretty much what I've described. > This got me to thinking, could setting an import map be an alternative to > setting an autoload function? Would having the PHP runtime load the file be > faster than mucking with some userland code to do the same? And would the > engine be able to do anything that can't be done in userland? I think so.
I very much like this direction of thought. For context, when I worked with WordPress for about a decade I only ever used only Composer for websites but never for my own plugins, and I almost never used namespaces nor PSR-4 autoloaders for anything except when a plugin used them. I almost exclusively used naming convensions for "namespacing" and classmaps for autoloading. Why? Was it because I was a "bad" programmer? No, it was because when in Rome, you do as the Romans do. And also because when I tried to use Composer and PSR-4 I was always fighting when them to do things in the way that worked best for WordPress. Or to use a more modern analogy to PHP and WordPress, even though you may be landlocked by the country of Italy, if you are within the borders of Vatican City you follow the laws and conventions of Vatican City when they conflict with those of Italy. > So first, I agree that supporting two formats, while convenient, increases > the maintenance burden, so let's just go with ini. As far as the installing > function - a better name is this. Obviously I agree with having only one format, but not sure I concur with the use of .ini. However, me debating against `.ini` would be me bikeshedding and thus I will demure here. > spl_autoload_map( string $filepath ); Adding an `spl_autoload_map()` function and feature really resonates with me. As I said, I (almost?) always used class maps with WordPress so if I were to build another WordPress site with a future PHP that made an `spl_autoload_map()` available, I would TOTALLY use it. <epiphany> Reading this however caused me to ponder things certain people has said recently — and many people have said for years on this list — and I think I am recognizing something that I have always known but never put the pieces together before. Many (most?) people on PHP Internals view WordPress coding standards as bad and some even view addressing WordPress developers needs as bad for PHP. And in general I concur that those people are reasonably justified in their belief WordPress' coding standards are not the standards that PHP developer who want to do professional level software engineering should aspire. And since many (most?) PHP Internals members generally do not experience the issues that WordPress developers have they do not recognize that they are issues; IOW, "out of sight, out of mind." I also think some list members tend to dismiss WordPress developers pains as unimportant and/or think that addressing those pains have will harm PHP. (BTW, I recently had a dialog off-list with someone who wrote in an email that "Wordpress is an exception, but nobody these days treats WordPress as a valid example to do anything. It is an ancient piece of legacy code that has no bearing on modern situation and it's their problem to deal with." So I am not just erecting a straw man here.) But I think what most may not consciously recognize is that WordPress is a different type of web app than an app build using Symfony or Laravel and deployed by its developers, or by some other professional developer. WordPress differs from the apps many (most?) developers on PHP Internals work with in the following way: WordPress = User-managed app Most = Developer-managed apps In a Developer-Managed app developers choose which 3rd party functionality will be incorporated into their sites whereas with a User-managed app users choose which 3rd party functionality will be incorporated into their site. And that is the KEY difference. So I am wondering if we can get people on this PHP Internals list who dismiss the needs of WordPress developer BECAUSE it is WordPress to recognize that User-Managed apps ARE a class of PHP applications have needs that deserve to be addressed? Two (2) unmet needs of User-Managed apps that "standard" PHP currently does not address come to mind: User-managed apps needs to be able to handle both: 1. User-added add-ons ("plugins" in WordPress, "modules" in Drupal) that have conflicting dependencies, and 2. Add-on directory structures that do not follow a PSR-4 directory hierarchy. As for #2, even if those apps could rearchitect their existing directory structure they cannot realistically be expected to do with because of the huge BC issues their users would experience. And newly created User-managed apps may still find that a PSR-4 directory structure is not in the best interest of their project or their users. To elaborate, PSR-4 generally assumes that ALL code goes into ONE hierarchy and that any and all code that will be autoload gets placed in that hierarchy. But with add-ons it makes a lot more sense to have the entire add-on contained in its own add-on directory. This is exactly where PSR-4 breaks down with respect to User-managed apps. Sure, you can have multiple PSR-4 autoloader root directories, but that does not scale well to websites with a large number of add-ons as many WordPress sites I worked on used. Some had over 100 plugins. With a hierarchy of autoloader maps that Michael Morris is proposing WordPress could collect up all the maps and create one map every time a plugin is added, updated or deleted. </epiphany> Based on my above labeled epiphany, I think MOST of what you recently proposed could address those unmet needs of User-managed apps written in PHP, with a few caveats and improvements. Read on. > ; A path fragment can be used, in which case PSR-4 will be used to map the > rest of the symbol to the filename. > ; Pay attention to the direction of the slash at the tail - if the symbol > key has this the value MUST also have this. > B/ = './path/to/B/' It is not clear to me what a trailing slash means, and especially why it is needed on the left-hand side? And why slash here when namespaces use backslash? Also, as someone raised on DOS and then Windows only "converting" in 2009, I still get confused in *nix when to use a trailing slash and when to not, so this trailing slash worries me, if only for that reason alone. > ; A package is declared with a @ and maps the package namespace to its > autoload file. > ; If the package name here doesn't match what the package calls itself then > the symbol > ; given here takes precedence, acting as an alias. > @C = './path/to/C/autoload.ini' Using the `@` here feels cryptic, and hard to discover and remember. I think this would be infinitely easier to follow if packages were just included in a `[packages]` section. Your comments also confuse me a bit. Is this saying that your hypothetical app — which you stated this `.ini` file is for — needs to use a package named `C` use "definition" is located at './path/to/C/autoload.ini' then it would use this syntax, and that in the app its components would be accessed at namespace `\C`? And I were to have: @Foo\Bar\Baz = './path/to/Foo/Bar/Baz/autoload.ini' Then in the app its components would be accessed at namespace `\Foo\Bar\Baz`? I think if your examples used hypothetical "real-world" symbols it would be easier to follow than A, B, C, D, etc. > ; An import into a package can be done like so > ; Twig will load into \C\Twig and that use will need to be used by any code > outside the C package. > @C\Twig/ = './path/to/Twig/' > > ; The same library can be loaded into a different package, but a symbolic > link is used internally in the engine to optimize > @D\Twig/ = './path/to/Twig/' > > ; Nothing stops a different package from loading a different version now. > @E\Twig/ = './path/to/Twig/Version4/' Okay, this makes sense. OTOH, this is the part that of your proposal that is incomplete for the needs of User-managed apps IMO. I think you are implying a necessary "best practice" that whenever any PHP library, or package would include code they would need to prefix the namespace of package when importing it and then when using it. Given an org named ACME that released a library called Widgets then if it were to use Twig it should import and use Twig like this (did I understand your intent correctly?): @ACME\Widgets\Twig/ = './path/to/Twig/' And in PHP code?: use \ACME\Widgets\Twig; I think that would work well for newer libraries and packages authored and used by developers of Developer-managed apps. OTOH I do not think it would be sufficient for any existing libraries or frameworks, nor for non-professional developers scratching their own itch on a User-managed apps and then deciding to publish it for others to use (which happens a lot with User-managed apps.) The problem would be that most (all?) of those would not be namespace-prefixing Twig but instead using it directly. I believe you need an ADDITIONAL `replace` sectionS that allowed an app/website developer to indicate that namespace A should instead be replaced in `use` statements and direct references with `B\A` for code that exists in directory(s) `C` but not in directories `C\D` where `C` and `D` can be globs. To illustrate I created a completely hypothetical `.ini` that the WordPress plugin admin page could create any time a user would install WordPress and any time the user would add/edit/delete plugins or themes (I did try to modifying your A/B/C example but couldn't come up with anything that could illustrate the use-case): [default] root = '/wp-content/' [includes] UpdraftPlus = './plugins/updraft-plus/index.php' # Uses Twig v4 Automattic\JetPack = './plugins/jetpack/jetpack.php' # Uses Twig Elementor = './plugins/elementor/elementor.php' # Uses Twig v4 [packages] Yoast\SEO = './plugins/yoast-seo/autoload.ini' # Uses Twig v4 as Yoast\Twig WPForms = './plugins/wp-forms/autoload.ini' # Uses Twig as WPForms\Twig [replace] Twig[UpdraftPlus] = 'Twig_edaf27eb' Twig[Elementor] = 'Twig_edaf27eb' In the above example `[packages]` are ones that have gotten religion and have delivered a best-practices package where they have namespaced Twig. We can ignore them for now as they follow your best-practices. The `[includes]` are ones that have paid no attention to newer best practice and/or simply have not been updated by their authors. They were implemented to load and use Twig as simply `\Twig`. The `[replace]` section tells PHP that when `\Twig` is found included or used within the `[includes]` files denoted by those namespaces referenced such as `UpdraftPlus` it should instead use the namespace of `\Twig_edaf27eb` (dynamically generated by WordPress), and the same goes for any includes and uses by `Elementor`. My hypothetical design may not survive a fully-working implementation, but I hope it illustrates that we need to: 1.) Handle those who are NOT following best practices, AND 2.) Alias namespaces when used IN ADDITION TO when imported. Of course if the code in any of the three plugins included expect the namespaces to be exact via reflection then they would break, but I think it would be a reasonable breakage as most plugins won't do this and most plugins that break could either be updated by their authors or disabled for installs on newer PHP by the WordPress plugin repo. BTW, Go uses `replace` in `go.mod` albeit as a compiled language its use is not a one-to-one analogue to the example above. If you are interested in seeing them in the wild here is what the use of `replace` looks like for Kubernetes: https://github.com/kubernetes/kubernetes/blob/master/go.mod#L227-L258 As an "optimization", WordPress could recognize that Twig and Twig4 are being used not only by the includes but also by the packages and could generate this optimization instead: [default] root = '/wp-content/' [includes] UpdraftPlus = './plugins/updraft-plus/index.php' # Uses Twig v4 Automattic\JetPack = './plugins/jetpack/jetpack.php' # Uses Twig Elementor = './plugins/elementor/elementor.php' # Uses Twig v4 [packages] Yoast\SEO = './plugins/yoast-seo/autoload.ini' # Uses Twig v4 as Yoast\Twig WPForms = './plugins/wp-forms/autoload.ini' # Uses Twig as WPForms\Twig [replace] Twig[UpdraftPlus] = 'Twig_edaf27eb' Twig[Elementor] = 'Twig_edaf27eb' Twig[Yoast\SEO] = 'Twig_edaf27eb' Twig[Automattic\JetPack] = 'Twig_2ba3f91f' Twig[WPForms] = 'Twig_2ba3f91f' As a further optimization, WordPress could reach into all the `.ini` files recursively and create a SINGLE autoload map, but to do that we would need an additional section: `[ignore]`: [ignore] Yoast\SEO = './plugins/yoast-seo/autoload.ini' WPForms = './plugins/wp-forms/autoload.ini' The above assumes that WordPress already generated everything that was needed to no longer need to load the autoload.ini files for either the `Yoast\SEO` or `WPForms` namespaces and thus the ignore tells `spl_autoload_map()` to ignore any calls to these `autoload.ini` files if called later. (I would have created a complete example but at this point I am too tired for that.) And lastly, because WordPress would need to generate this and having a web app write to a file is a modern security no-no, then `spl_autoload_map()` should accept multiple different valid values: spl_autoload_map( string|array|\PHP\AutoloadMap $map); 1. String would be the `.ini` file path 2. Array would be the format returned by parse_ini_file() for parsing an applicable `.ini` file 3. \PHP\AutoloadMap could be a new class containing the required values in object format. (Hopefully adding such a class as a third option would not be controversial to the list members who criticize those developers still wanting to use arrays as hash maps?) And that is about it for my feedback today. -Mike