[RFC] New core autoloading mechanism with support for function autoloading

php.internals

Girgias

3 years ago
Hello Internals, Dan and I would like to propose a new core autoloading mechanism that fixes some minor design issues with the current class autoloading mechanism and introduce a brand-new function autoloading mechanism: https://wiki.php.net/rfc/core-autoloading The existing SPL autoloading functions would become aliases to the new Core ones and will continue to work without any migrations needing to be performed. Hope to hear your opinions about this! Best regards, George P. Banyard

Ondřej Mirtes

3 years ago
I don’t like the proposed function names: autoload_register_class to me reads like “register this class for autoloading”. If I observe the intended meaning correctly, it should be called “register_class_autoloader”. Same for autoload_register_function - better name would be “register_function_autoloader”. Other functions should be renamed accordingly. Function names should read like a sentence, like a command. On Mon 10. 4. 2023 at 14:17, G. P. B. <george.banyard@gmail.com> wrote:
> Hello Internals, > > Dan and I would like to propose a new core autoloading mechanism that fixes > some minor design issues with the current class autoloading mechanism and > introduce a brand-new function autoloading mechanism: > https://wiki.php.net/rfc/core-autoloading > > The existing SPL autoloading functions would become aliases to the new Core > ones and will continue to work without any migrations needing to be > performed. > > Hope to hear your opinions about this! > > Best regards, > > George P. Banyard >
-- Ondřej Mirtes

Deleu

3 years ago
On Mon, Apr 10, 2023 at 10:01 AM Ondřej Mirtes <ondrej@mirtes.cz> wrote:
> I don’t like the proposed function names: > > autoload_register_class to me reads like “register this class for > autoloading”. If I observe the intended meaning correctly, it should be > called “register_class_autoloader”. > > Same for autoload_register_function - better name would be > “register_function_autoloader”. > > Other functions should be renamed accordingly. Function names should read > like a sentence, like a command. > > On Mon 10. 4. 2023 at 14:17, G. P. B. <george.banyard@gmail.com> wrote: > > > Hello Internals, > > > > Dan and I would like to propose a new core autoloading mechanism that > fixes > > some minor design issues with the current class autoloading mechanism and > > introduce a brand-new function autoloading mechanism: > > https://wiki.php.net/rfc/core-autoloading > > > > The existing SPL autoloading functions would become aliases to the new > Core > > ones and will continue to work without any migrations needing to be > > performed. > > > > Hope to hear your opinions about this! > > > > Best regards, > > > > George P. Banyard > > > -- > > Ondřej Mirtes >
I'm guessing autoload_* was chosen because PHP internal functions are bundled by prefix and register_* would not make a good bundle name for the autoloading functionality. What helps me when thinking about PHP internals function is to assume the first word is a class, such as: $autoload = new Autoload(); And then everything after the first underscore is a method name, such as $autoload->register_class();. What I can recommend in this case is to make them autoload_register_class_loader() and autload_unregister_class_loader(), but prefixing everything with register_* would be essentially $register = new Register();, but register what?
-- Marco Deleu

Dan Ackroyd

3 years ago
On Mon, 10 Apr 2023 at 14:01, Ondřej Mirtes <ondrej@mirtes.cz> wrote:
> > I don’t like the proposed function names:
I think we're going to leave thinking about changing the names until the end of the discussion. They don't really matter, and it probably would be better to avoid clogging up the technical discussion with people's aesthetic beliefs. cheers Dan Ack

A.L.E.C

3 years ago
On 10.04.2023 14:17, G. P. B. wrote:
> Hello Internals, > > Dan and I would like to propose a new core autoloading mechanism
I think it would make a lot of sense to have one function for all kind of features, but the user should define which ones. For example: Loader::register(callable $callback, int $type, bool $prepend = false): void; then Loader::register($callback, Loader::TYPE_CLASS | Loader::TYPE_FUNCTION); Or sth like that. My 2c.
-- Aleksander Machniak Kolab Groupware Developer [https://kolab.org] Roundcube Webmail Developer [https://roundcube.net] ---------------------------------------------------- PGP: 19359DC1 # Blog: https://kolabian.wordpress.com

Anton Smirnov

3 years ago
Hello George, I'm not sure I'm 100% correct but I think that this RFC still allows to call different functions for the same code, just not in the same run. Consider this pseudocode: //- funcs.php namespace My; function myfunc(...) { ... } function array_map(...) { ... } //- code1.php namespace My; myfunc(1); // autoload triggered // bonus points if it's a different file or some obscure method array_map(strlen(...), []); // My\array_map //- code2.php namespace My; // no prior autoload array_map(strlen(...), []); // global array_map And if I understand the current order of execution correctly, here is a more extreme example: //- moreextreme.php namespace My; if (some_random()) { array_map('My\myfunc', []); // global array_map } else { array_map(myfunc(...), []); // My\array_map }
-- Anton

Girgias

3 years ago
On Mon, 10 Apr 2023 at 20:12, Anton Smirnov <sandfox@sandfox.me> wrote:
> Hello George, > > I'm not sure I'm 100% correct but I think that this RFC still allows to > call different functions for the same code, just not in the same run. > > Consider this pseudocode: > > //- funcs.php > > namespace My; > > function myfunc(...) { ... } > function array_map(...) { ... } > > //- code1.php > > namespace My; > > myfunc(1); // autoload triggered > // bonus points if it's a different file or some obscure method > array_map(strlen(...), []); // My\array_map > > //- code2.php > > namespace My; > > // no prior autoload > array_map(strlen(...), []); // global array_map >
No, array_map will trigger the autoloader (actually strlen() will trigger it once first) and the autoloader should then load the array_map function from the My namespace. However, if the autoloader does not correctly load the My\array_map() function then it will be bound to the global function. I can add some test cases for that if needed.

Anton Smirnov

3 years ago
On Tue, 2023-04-11 at 11:57 +0100, G. P. B. wrote:
> No, array_map will trigger the autoloader (actually strlen() will > trigger it once first) and the autoloader should then load the > array_map function from the My namespace. > However, if the autoloader does not correctly load the My\array_map() > function then it will be bound to the global function.
Thanks for the clarification, my scenario falls apart now :D LGTM. I hope it will be approved.
-- Anton

Michał Brzuchalski

3 years ago
Hi George and Dan, pon., 10 kwi 2023 o 14:17 G. P. B. <george.banyard@gmail.com> napisał(a):
> Hello Internals, > > Dan and I would like to propose a new core autoloading mechanism that fixes > some minor design issues with the current class autoloading mechanism and > introduce a brand-new function autoloading mechanism: > https://wiki.php.net/rfc/core-autoloading > > The existing SPL autoloading functions would become aliases to the new Core > ones and will continue to work without any migrations needing to be > performed. > > Hope to hear your opinions about this! > > Best regards, > > George P. Banyard >
Thanks for bringing this up, I like the RFC and addressing function autoload. Can we improve the RFC with a short description of issues on SPL autoload this RFC tries to address? It is mentioned in at least two places that this proposal addresses some minor issues with SPL autoload. However, I think it'd be worth mentioning them in RFC so the reader can get a complete picture of what the RFC tries to address besides adding new features like function autoload. Cheers, Michał Marcin Brzuchalski

Dan Ackroyd

3 years ago
On Tue, 11 Apr 2023 at 08:14, Michał Marcin Brzuchalski <michal.brzuchalski@gmail.com> wrote:
> > Can we improve the RFC with a short description of issues on SPL autoload > this RFC tries to address?
Sure, if you want to propose some clearer words than these: "The spl_autoload_register() does not become an alias for autoload_register_class() to preserve BC by continuing to return true, allowing it to register the default SPL autoloader, and accepting the ignored second parameter, but they are both forwarded to an identical internal implementation." The RFC could always be improved. But the main reason to separate the autoload functions from the SPL, is that imo autoloading probably should never have been part of SPL. At the time, new functionality was dumped into SPL as a convenient place to put stuff. At some point (probably after distributing extensions becomes a lot easier) moving the SPL away from PHP core might be a sensible thing to do, as the SPL has some 'not great' design choices that are pretty impossible to solve: https://phpopendocs.com/rfc_codex/spl_summary Or at least impossible to solve while the release cycle of the SPL is tied to that of PHP itself. But autoloading would need to stay as part of PHP core itself. cheers Dan Ack

Rowan Collins

3 years ago
On 10 April 2023 13:17:04 BST, "G. P. B." <george.banyard@gmail.com> wrote:
>Hello Internals, > >Dan and I would like to propose a new core autoloading mechanism that fixes >some minor design issues with the current class autoloading mechanism and >introduce a brand-new function autoloading mechanism: >https://wiki.php.net/rfc/core-autoloading
Hi! Thanks both for tackling this, I know function autoloading has long been on a lot of people's wish lists. A few thoughts:
> As a result of the pinning, function_exists() will return true for functions in namespaces that have been pinned to a global function.
The lookup pinning seems like the right way to optimise the implementation, but I don't think it should be visible to user code like this - a lookup for "strlen" in namespace "Foo" is not the same as defining the function "Foo\strlen". Consider this code: namespace Bar { if ( function_exists('Foo\strlen') ) { \Foo\strlen('hello'); } } namespace Foo { strlen('hello'); // triggers name pinning } namespace Bar { if ( function_exists('Foo\strlen') ) { \Foo\strlen('hello'); } } If I'm reading the RFC correctly, the second function_exists will return true. I'm less clear if the call to \Foo\strlen will actually succeed - if it gives "undefined function", then function_exists is clearly broken; if it calls the global strlen(), that's a very surprising side effect. For bonus points, the call to strlen that triggers pinning could be inside an autoloader, making even the first function_exists call return true. Similarly, I think it should be possible to "unpin" a function lookup with a later definition, even if no autoloading would be triggered. That is, this should not be a duplicate definition error: namespace Foo; if ( strlen('magic') != 42 ) { function strlen($string) { /* ... */ } }
> The use of the word class in the API is currently accurate
This isn't actually true: classes, interfaces, traits, and enums all share a symbol table, and thus an autoloader. I don't know of a good name for this symbol table, though. Regarding the API, would it be possible to take advantage of nearly all autoloaders only being interested in particular namespace prefixes? Currently, every registered autoloader is run for every lookup, and most immediately check the input for one or two prefixes, and return early if not matched. I suspect this design is largely because autoloading came before namespaces, so the definition of "prefix" wasn't well-defined, but going in and out of userland callbacks like this is presumably rather inefficient. Perhaps the "register" functions should take an optional list of namespace prefixes, so that the core implementation can do the string comparison, and only despatch to the userland code if the requested class/function name matches. Thanks again for working on this! Regards,
-- Rowan Tommins [IMSoP]

Girgias

3 years ago
On Tue, 11 Apr 2023 at 09:48, Rowan Tommins <rowan.collins@gmail.com> wrote:
> > As a result of the pinning, function_exists() will return true for > functions in namespaces that have been pinned to a global function. > > The lookup pinning seems like the right way to optimise the > implementation, but I don't think it should be visible to user code like > this - a lookup for "strlen" in namespace "Foo" is not the same as defining > the function "Foo\strlen". Consider this code: > > namespace Bar { > if ( function_exists('Foo\strlen') ) { > \Foo\strlen('hello'); > } > } > namespace Foo { > strlen('hello'); // triggers name pinning > } > namespace Bar { > if ( function_exists('Foo\strlen') ) { > \Foo\strlen('hello'); > } > } > > If I'm reading the RFC correctly, the second function_exists will return > true. I'm less clear if the call to \Foo\strlen will actually succeed - if > it gives "undefined function", then function_exists is clearly broken; if > it calls the global strlen(), that's a very surprising side effect. >
It *should* indeed call the global strlen() but I hadn't actually created such a test as ... I hadn't thought of doing that. However, we *already* do function pinning which can result in this behaviour via the function cache, see the following bug which defines a new function via eval(): https://bugs.php.net/bug.php?id=64346 I am not sure that it calling the global strlen() is that surprising, as it is basically aliasing the function \Foo\strlen() to \strlen(). For bonus points, the call to strlen that triggers pinning could be inside
> an autoloader, making even the first function_exists call return true. >
If it is in the same namespace as the autoloader, then yes. However, if the autoloader is in Bar then only Bar\strlen() is being aliased to \strlen(). Ilija mentioned this off-list, and I hadn't considered this, but this could lead to a large increase of symbols being defined in the function symbol table, as every nonqualified call (either by using the "use" statement, or writing the full FQN) will get aliased to a global function and take an entry in the symbol table.
> Similarly, I think it should be possible to "unpin" a function lookup with > a later definition, even if no autoloading would be triggered. That is, > this should not be a duplicate definition error: > > namespace Foo; > if ( strlen('magic') != 42 ) { > function strlen($string) { /* ... */ } > } >
There are some larger technical issues at play, as mentioned in the previous bug. The function cache will pin the call and there is no way of unpinning it. I tried looking into fixing this, but it turns out to be too complicated (/impossible?). More so, disabling the function cache is a massive performance penalty. As such, the RFC follows the current de facto behaviour.
> > The use of the word class in the API is currently accurate > > This isn't actually true: classes, interfaces, traits, and enums all share > a symbol table, and thus an autoloader. I don't know of a good name for > this symbol table, though. >
They do share a symbol table indeed but using class is probably the least confusing one.
> Regarding the API, would it be possible to take advantage of nearly all > autoloaders only being interested in particular namespace prefixes? > > Currently, every registered autoloader is run for every lookup, and most > immediately check the input for one or two prefixes, and return early if > not matched. I suspect this design is largely because autoloading came > before namespaces, so the definition of "prefix" wasn't well-defined, but > going in and out of userland callbacks like this is presumably rather > inefficient. > > Perhaps the "register" functions should take an optional list of namespace > prefixes, so that the core implementation can do the string comparison, and > only despatch to the userland code if the requested class/function name > matches. >
That is actually interesting, hadn't thought about taking an array of prefixes. And yes, every callback call requires a VM re-entry, which is expensive. Should the prefix be with or without the trailing backlash? Best regards, George P. Banyard

Rowan Collins

3 years ago
On 11 April 2023 12:16:18 BST, "G. P. B." <george.banyard@gmail.com> wrote:
> >However, we *already* do function pinning which can result in this >behaviour via the function cache, see the following bug which defines a new >function via eval(): >https://bugs.php.net/bug.php?id=64346
That's not the same thing - that's pinning *the meaning of an unprefixed name within a particular scope*, my example was of using *a fully-qualified function name*, of a function that has never been defined. Substituting a fully-qualified name in the example from that bug runs the namespaced function just fine: https://3v4l.org/tReC3
>I am not sure that it calling the global strlen() is that surprising, as it >is basically aliasing the function \Foo\strlen() to \strlen().
That's the implementation detail that I'm saying should not leak. It is not the current semantics of function lookups, and I don't think it's desirable semantics.
>> Similarly, I think it should be possible to "unpin" a function lookup with >> a later definition, even if no autoloading would be triggered. That is, >> this should not be a duplicate definition error: >> >> namespace Foo; >> if ( strlen('magic') != 42 ) { >> function strlen($string) { /* ... */ } >> } >> > >There are some larger technical issues at play, as mentioned in the >previous bug.
The above code doesn't currently generate an error, and the function defined is callable with and without prefix: https://3v4l.org/nPbat If I understand right, the caching issue is that some uses of unprefixed strlen(...) might not pick up the new function; but fully qualified uses will reliably do so.
>That is actually interesting, hadn't thought about taking an array of >prefixes. >And yes, every callback call requires a VM re-entry, which is expensive. > >Should the prefix be with or without the trailing backlash?
It would probably make sense to follow Composer's lead and require it; their reasoning seems sound (from https://getcomposer.org/doc/04-schema.md#psr-4):
> Namespace prefixes must end in \\ to avoid conflicts between similar prefixes. For example Foo would match classes in the FooBar namespace so the trailing backslashes solve the problem: Foo\\ and FooBar\\ are distinct.
Regards,
-- Rowan Tommins [IMSoP]

Dan Ackroyd

3 years ago
On Tue, 11 Apr 2023 at 09:48, Rowan Tommins <rowan.collins@gmail.com> wrote:
> > Similarly, I think it should be possible to "unpin" a function > lookup with a later definition,
Can you say what the technical justification for that is? There's reasons why (imo) it's probably wrong, but I don't currently understand what you would want it for, so want to make sure I don't miss something in my response.
> This isn't actually true: classes, interfaces, traits, and enums all share > a symbol table, and thus an autoloader. I don't know of a good name for > this symbol table, though.
As per the RFC it would probably just be 'type'. When PHP is compiling some code: function foo(Bar $bar): Quux { ... } Before 'Bar' and 'Quux' are loaded, the only thing that is safe to assume about them is that they are a type, whether that be a class either with methods or without (also called an interface), or an enum, or eventually a type alias like `type number = float|int;`.
> Regarding the API, would it be possible to take advantage of nearly > all autoloaders only being interested in particular namespace prefixes?
Lots of things are possible, but I'd strongly prefer to have that be a separate discussion, which it's listed as a future scope: https://wiki.php.net/rfc/core-autoloading#higher_performance_through_maps If nothing else, it's going to take people time to figure out how they are going to organise code that is 'just' a bunch of functions. cheers Dan Ack

Rowan Collins

3 years ago
On 11 April 2023 13:01:08 BST, Dan Ackroyd <Danack@basereality.com> wrote:
>On Tue, 11 Apr 2023 at 09:48, Rowan Tommins <rowan.collins@gmail.com> wrote: >> >> Similarly, I think it should be possible to "unpin" a function >> lookup with a later definition, > >Can you say what the technical justification for that is?
Perhaps "should" is too strong, but I would find it surprising if *using* a global function made it impossible to *define* a namespaced one. As I said in my previous message, there is a fundamental difference between caching of unprefixed name lookups, and actual function definitions. The current fallback and caching can always be skipped by using a fully qualified name. If additional caching is achieved using a dummy entry in the function table, a real entry for that function should be able to evict it, so that a fully-qualified function name can still be used.
>Lots of things are possible, but I'd strongly prefer to have that be a >separate discussion, which it's listed as a future scope: >https://wiki.php.net/rfc/core-autoloading#higher_performance_through_maps > >If nothing else, it's going to take people time to figure out how they >are going to organise code that is 'just' a bunch of functions.
I'm not talking about full mapping from name to file, or about functions specifically, but about a lot of existing autoloaders beginning with boilerplate like "if ( ! str_starts_with('Acme\\Foo\\') ) { return; }", and then whatever logic is used for loading Acme\Foo classes. If the core implementation can perform that check, it can skip the context switch to userland code. That's a benefit to the class autoloading anyway, but I'd be very surprised if function autoloaders looked any different, because that separation of ownership is pretty much what namespaces are *for*. Regards,
-- Rowan Tommins [IMSoP]

Sara Golemon

3 years ago
> On Apr 10, 2023, at 07:17, G. P. B. <george.banyard@gmail.com> wrote: > > Hello Internals, > > Dan and I would like to propose a new core autoloading mechanism that fixes > some minor design issues with the current class autoloading mechanism and > introduce a brand-new function autoloading mechanism: > https://wiki.php.net/rfc/core-autoloading
At a high level: +1, would vote for again (I think I voted for this last time it came up...) Initial thoughts, mostly based on replies so far: 1/ There's certainly some bike-shedding for the names to do. Good points have already been made and I won't belabor them. 2/ I'm unconcerned by the edge case brought up about load ordering as I feel that having multiple definitions of a single function name in an autoload friendly codebase grouped in files which permit this kind of shenanigans is a technical possibility, but a practical absurdity. Nobody should be designing traps like that. 3/ Pinning concerns me a little, and we should certainly build some strong unittests to validate behavior here, but I'm confident that can be resolved during implementation. 4/ If nothing else, I just look forward to gaining consistency here. autoloading being exclusive to classes has long annoyed me, just not quite to the point of action. :) -Sara

Mark Baker

3 years ago
On 10/04/2023 14:17, G. P. B. wrote:
> Dan and I would like to propose a new core autoloading mechanism that fixes > some minor design issues with the current class autoloading mechanism and > introduce a brand-new function autoloading mechanism: > https://wiki.php.net/rfc/core-autoloading > > The existing SPL autoloading functions would become aliases to the new Core > ones and will continue to work without any migrations needing to be > performed.
I fully support this endeavour: I've wanted this for such a long time now. A while ago I was pressured into splitting two of my libraries that provided both function and class/method calls, increasing my maintenance workload. This would allow me to combine them again. Looking at all the points that have already been raised, I think Sara sums up well. I also like Rowan's suggestion of a list of namespace prefixes. In particular, you're already considering the use of maps for even better performance, and Constant and Stream autoloading, as possible future enhancements. Constants in particular is something I'd find particularly useful; and my only concern with this is the increased number of functions compared with a single function that accepts a Type argument (as Aleksander suggests). A couple of general questions: Are there any core functions that should never by overloaded (function_exists() perhaps)? We have the special constants like \MyNamespace\MyClass::class. Would you envisage (or is there value in) equivalent constants like \MyNamespace\MyFunction::function? If there is value, how would that tie in with referencing them as Callables in callback functions?
-- Mark Baker

Kamil Tekiela

3 years ago
> > We have the special constants like \MyNamespace\MyClass::class. Would > you envisage (or is there value in) equivalent constants like > \MyNamespace\MyFunction::function? >
If anything, I would suggest renaming the existing constant to something more meaningful. There is no point in adding ::function because that would just be an additional confusing alias for the same thing. Doing strpos::class works just fine, but is semantically incorrect.

Rowan Collins

3 years ago
On 11 April 2023 13:21:08 BST, Mark Baker <mark@demon-angel.eu> wrote:
>We have the special constants like \MyNamespace\MyClass::class. Would you envisage (or is there value in) equivalent constants like \MyNamespace\MyFunction::function?
This has come up before, but there are two things about ::class to bear in mind: 1) It has nothing to do with classes. It just means "resolve namespaced name", and doesn't care what string appears on its left-hand side. 2) It is calculated (except in a few specific cases) at compile-time, based entirely on the contents of the current file, with no relationship to autoloading or what is defined elsewhere. That's very different from function (and constant) lookups, which happen at run-time, and specifically check whether a function is defined in the current namespace before falling back to the global one. In other words, the value of foo::function would be dependent on runtime state in a way that Foo::class is not. Meanwhile, the main use case for such a syntax - getting a reference to use as a callback - is now well served by the first class callable syntax my_function(...) Regards,
-- Rowan Tommins [IMSoP]

Unnamed Person

3 years ago
On Monday 10 April 2023 14:17:04 (+02:00), G. P. B. wrote:
> Hello Internals, > > Dan and I would like to propose a new core autoloading mechanism [...] > > The existing SPL autoloading functions would become aliases to the new
Core
> ones and will continue to work without any migrations needing to be > performed. > > Hope to hear your opinions about this!
Thanks for taking the time for this, I only quickly skimmed over it, an insightful reading already. What I wondered about and which made me writing a reply on the list is in retrospect of the times class autoloading came in, which is also closer to the time when namespaces were introduced. It was the latter which brought us `class_alias()` [0] and I've used it often in code migrations (is 8.3 the new 5.3? would be easy to remember.). So I'd love to see some commentary on a `function_alias()` if now function autoloading is considered to come in, as I can imagine this has similar effects for how you'd like to namespace PHP code with the new, better functionality of function autoloading and access to an alias table from PHP "userspace" I'd consider helpful then. Best, -- hakre [0]: https://www.php.net/class_alias

Dan Ackroyd

3 years ago
On Tue, 11 Apr 2023 at 18:12, Hans Krentel via internals <internals@lists.php.net> wrote:
> > So I'd love to see some commentary on a `function_alias()` > if now function autoloading is considered to come in
I wouldn't be opposed to it, but it should be a separate RFC. The implementation could be copied from https://www.php.net/manual/en/function.runkit7-function-copy and probably wouldn't be that complicated, or conflict with this RFC.
> It was the latter which brought us `class_alias()` [0] > and I've used it often in code migrations
Yes, in particular the changing from Twig_Template_Loader to Twig\Template\Loader migration. I'll add it to my list at https://phpopendocs.com/rfc_codex It would seem a good "my first RFC" for someone. Though, function migration could also be solved in a far more powerful way. Check my forthcoming reply to Rowan... cheers Dan Ack

Nicolas Grekas

3 years ago
Hello George, Dan and I would like to propose a new core autoloading mechanism that fixes
> some minor design issues with the current class autoloading mechanism and > introduce a brand-new function autoloading mechanism: > https://wiki.php.net/rfc/core-autoloading > > The existing SPL autoloading functions would become aliases to the new Core > ones and will continue to work without any migrations needing to be > performed. >
Thanks to you and Dan for the RFC. I have two main questions about this: 1. What's the performance hit? 2. If the perf hit is measurable, is function autoloading worth it? aka what problem does it address? About the perf hit, I see that you put a lot of thought into minimizing it and that's sound. The way I see this feature be spread to userland is via composer of course. I foresee that composer will add a way for packages to declare function autoloading. This means that every application that uses composer will be impacted by the perf overhead eventually (aka the case of "programs that do not have a function autoloader registered" mentioned in the RFC won't happen in practice.) In e.g. https://github.com/php/php-src/pull/6627#issuecomment-773922448, Dmitry explains that adding inheritance cache to PHP provided a 8% perf benefit when running a demo app. I would like to see a similar benchmark with function autoloading enabled. https://github.com/phpbenchmarks/ provides several demo apps so it could be a nice playground for running this. Then, depending on the resulting numbers, I anticipate several possible outcomes: - if the impact is really low, then all is fine and we can keep things as is; - otherwise, I'm also interested in this idea to scope function autoloading per namespace, so that the engine can filter ahead of calling any userland autoloaders; - if the overhead is still significant, could another idea be to load functions per namespace, where the engine would ensure to call function autoloaders only once per namespace? Even with the current proposal, I can easily anticipate that a common practice for package authors could be to put all functions in one file, and register a function autoloader that would require that file for any of the functions in it. Loading per namespace would fit this loading strategy. But before exploring this idea, let's get some numbers about the performance of the previous approaches. Then comes my second main question: what does this solve? To me, class autoloading is a performance feature. If we didn't care about performance, we would be fine with eagerly loading a bunch of classes as e.g. Java does. But because we run PHP mainly in short-lived request/response loops, loading just the few classes we need to serve a specific request provides the best performance. In recent versions of PHP, we may question this practice, since we now have a crazy good opcache. Of course, I'm not suggesting removing autoloading for classes. But if we do have that crazy good opcache, what do we need function autoloading for? The current way for package authors to declare classes is via composer "files <https://getcomposer.org/doc/04-schema.md#files>" entry. One could argue in the past that this adds needless "require" on the hotpath. But if they cost nothing thanks to recent opcache improvements, we don't need to care, do we? At least, we need to compare both approaches, because if the main argument for adding function autoloading is lazy-loading of function implementations, then it must be faster than eager-loading. This circles back to my previous question, so we'd really need a thorough perf analysis IMHO. Nicolas

Rowan Collins

3 years ago
On Wed, 12 Apr 2023 at 09:24, Nicolas Grekas <nicolas.grekas+php@gmail.com> wrote:
> > To me, class autoloading is a performance feature. If we didn't care about > performance, we would be fine with eagerly loading a bunch of classes as > e.g. Java does.
I think (class) autoloading actually serves two purposes: as you say, it allows lazy-loading, which was an important performance consideration before opcache; but it also allows mapping of class name to file name. Java still has to find the source file for each class mentioned, it just has a more rigid set of rules for where it will look. One of the big differences you'll see between PHP 4 and PHP 5 codebases is the number of source files - when people had to manually list each file to include, they tended to bundle things into larger categories; with autoloading, they used one file per class. There's probably more willingness to create new classes in such an arrangement, since they're easier to find. I think function autoloading would provide a similar shift in thinking: rather than one big "functions.php", projects will put smaller batches of functions together in tighter namespaces. That in turn will make it feel more natural to create a new file of functions, rather than using a class full of static methods just to create that grouping. It doesn't enable anything that a script generating a list of include statements couldn't, but PHP users aren't used to needing a "build" command just to discover a new file, so function autoloading feels more natural. Regards,
-- Rowan Tommins [IMSoP]

Dan Ackroyd

3 years ago
On Wed, 12 Apr 2023 at 09:24, Nicolas Grekas <nicolas.grekas+php@gmail.com> wrote:
> > I would like to see a similar benchmark with function autoloading enabled. https://github.com/phpbenchmarks/
Can you point me to where one can tell the benchmark framework to use a custom version of PHP?
> One of the big differences you'll see between PHP 4 and PHP 5 codebases is > the number of source files - when people had to manually list each file to > include, they tended to bundle things into larger categories;
Yes. Or to put it more generally, the ergonomics of using a language (aka the developer experience) affects how people think and write code. I'm just going to quote someone from Reddit and my reply, because it captures the essence of the situation perfectly: TheBroccoliBobboli wrote in /r/php
>> I'm struggling to see the advantages of this vs working with static >> class functions, e.g. Helper::function(). >> >> That's mostly because I can't even remember when I last declared a >> function outside of a class in any serious project though.
> Danack wrote: > That the ergonomics of using functions in PHP is currently so bad, that you > don't use them, is proof enough of why the ergonomics of using them needs > to be improved, and autoloading is the most obvious improvement.
Nicolas Grekas wrote:
> Then comes my second main question: what does this solve?
Or, to appeal to a higher authority: "There is this one thing that I noticed recently and that concerns me: PHP devs don’t use functions." - Nikita Popov https://www.npopov.com/2012/08/10/Are-PHP-developers-functophobic.html Nicolas Grekas wrote:
> It doesn't enable anything that a script generating a list of include
statements couldn't Exactly the same argument could made against class autoloading. cheers Dan Ack

Claude Pache

3 years ago
> Le 10 avr. 2023 à 14:17, G. P. B. <george.banyard@gmail.com> a écrit : > > Hello Internals, > > Dan and I would like to propose a new core autoloading mechanism that fixes > some minor design issues with the current class autoloading mechanism and > introduce a brand-new function autoloading mechanism: > https://wiki.php.net/rfc/core-autoloading >
Hi, 1. The proposed modification of `function_exists()` will break existing code: <?php namespace foo; if (!function_exists(__NAMESPACE__.'\\bar')) { function bar() { // .... } } ?> as soon as a `bar()` function is defined in global scope, with no obvious hint why it suddenly broke. You should either (from my increasing order of preference): * acknowledge it in the “Backward Incompatible Changes” section; * default the $autoload extra parameter to `false`; * keep the current semantics, that using a namespaced or qualified function name, (including a function name given as string, which is implicitly fully qualified), doesn’t fall back to the global scope, — even when a previous use of the same function name did trigger the fall back, see: https://3v4l.org/mnVWO (Independently of any mechanism introduced to avoid to repeatedly trigger the function autoloader.) 2. If you add function autoloading, you ought to support constant autoloading as well, of course. —Claude

Rowan Collins

3 years ago
On 12/04/2023 17:31, Claude Pache wrote:
> * keep the current semantics, that using a namespaced or qualified function name, (including a function name given as string, which is implicitly fully qualified), doesn’t fall back to the global scope, — even when a previous use of the same function name did trigger the fall back, see:https://3v4l.org/mnVWO (Independently of any mechanism introduced to avoid to repeatedly trigger the function autoloader.)
I could just about live with that example changing so that the fallback was cached, but I definitely don't think an explicit call like \foo\strlen('x') should become an implicit alias for \strlen('x'), which is apparently the current proposal. I really like the majority of this proposal, but right now would vote against it based on that. Regards,
-- Rowan Tommins [IMSoP]

Jordi Boggiano

3 years ago
On 2023-04-12 22:26, Rowan Tommins wrote:
> I could just about live with that example changing so that the > fallback was cached, but I definitely don't think an explicit call > like \foo\strlen('x') should become an implicit alias for > \strlen('x'), which is apparently the current proposal. > > I really like the majority of this proposal, but right now would vote > against it based on that. >
I agree here, I missed that fact while reviewing the RFC as I just understood it as an internal cache that \foo\strlen => \strlen, but function_exists('foo\strlen') should still return false IMO until it is defined, and once it is defined the cache should be busted. https://3v4l.org/VLW5O looks like correct behavior to me correctly. Best, Jordi
-- Jordi Boggiano @seldaek - https://seld.be

Dan Ackroyd

3 years ago
On Thu, 13 Apr 2023 at 11:39, Jordi Boggiano <j.boggiano@seld.be> wrote:
> > On 2023-04-12 22:26, Rowan Tommins wrote: > > I could just about live with that example changing so that the > > fallback was cached, but I definitely don't think an explicit call > > like \foo\strlen('x') should become an implicit alias for > > \strlen('x'), which is apparently the current proposal. > > > > I really like the majority of this proposal, but right now would vote > > against it based on that. > > > I agree here, I missed that fact while reviewing the RFC as I just > understood it as an internal cache that \foo\strlen => \strlen, but > function_exists('foo\strlen') should still return false IMO until it is > defined, and once it is defined the cache should be busted.
Thanks for the code example. Code is a great of being clear... particularly when I've gotten the wrong end of the stick. Yeah, that would be a BC break. For now, I'll update the RFC words to say that, but that means we need to go and do some thinking, to figure out how to proceed. cheers Dan Ack

Robert Landers

3 years ago
Hello, This looks really interesting but it might be a good idea to add a section for the definition of "pinning" and the scope of the pin. For example, what is the output of this code, executed in this order (with this new autoloading configured): // Test.php namespace Test { function password_verify(string $password, string $hash): bool { return true; } } // another file echo Test\password_verify('password', 'hash'); echo password_verify('password', 'hash'); // yet another file echo password_verify('password', 'hash'); Does this get pinned in the global namespace and does this pin extend to other files? If so, this could lead to very hard-to-reason-about code. Cheers, Rob Landers Utrecht, Netherlands

Dan Ackroyd

3 years ago
On Wed, 12 Apr 2023 at 17:32, Claude Pache <claude.pache@gmail.com> wrote:
> > The proposed modification of `function_exists()` will break existing code:
Please can you submit a failing test to https://github.com/Girgias/php-src/tree/zend_autoloader that shows a BC break. cheers Dan Ack

Rowan Collins

3 years ago
On 12 April 2023 23:50:25 BST, Dan Ackroyd <Danack@basereality.com> wrote:
>On Wed, 12 Apr 2023 at 17:32, Claude Pache <claude.pache@gmail.com> wrote: >> >> The proposed modification of `function_exists()` will break existing code: > >Please can you submit a failing test to >https://github.com/Girgias/php-src/tree/zend_autoloader that shows a >BC break.
I'm not sure how to interpret this. Are you saying you agree that the behaviour should not change, plan to update the RFC, and are asking for help picking good examples to come up with a new implementation? Or are you expressing disbelief that the behaviour change is actually a BC break, because the examples so far haven't convinced you? Regards,
-- Rowan Tommins [IMSoP]

Rowan Collins

3 years ago
On 12 April 2023 23:50:25 BST, Dan Ackroyd <Danack@basereality.com> wrote:
>On Wed, 12 Apr 2023 at 17:32, Claude Pache <claude.pache@gmail.com> wrote: >> >> The proposed modification of `function_exists()` will break existing code: > >Please can you submit a failing test to >https://github.com/Girgias/php-src/tree/zend_autoloader that shows a >BC break.
I see that George has added this test based on a previous example in this thread: https://github.com/php/php-src/blob/7aaae2efa080084035911065c346d5f8932b0d63/Zend/tests/autoloading/function/local_function_pinned-to_global.phpt It currently enshrines the proposed change in behaviour, so if you want a test that passes on existing versions and fails on the current branch, just delete the last two lines in the EXPECT section. Regards,
-- Rowan Tommins [IMSoP]

Derick Rethans

3 years ago
On Mon, 10 Apr 2023, G. P. B. wrote:
> Hello Internals, > > Dan and I would like to propose a new core autoloading mechanism that fixes > some minor design issues with the current class autoloading mechanism and > introduce a brand-new function autoloading mechanism: > https://wiki.php.net/rfc/core-autoloading > > The existing SPL autoloading functions would become aliases to the new Core > ones and will continue to work without any migrations needing to be > performed. > > Hope to hear your opinions about this!
I find the chosen names (_register_class, _unregiester_class, _call_class, _list_class, _register_function, _unregister_function, _call_function, and _list_function) not very clear. From reading just their names, I would have no idea of what they would do as they don't read like an instruction. You're not registering a class or function, you're registering a *handler* for resolving the class or function name being passed in. | If a function named strlen exists in the namespace bar PHP would use | that, otherwise PHP would 'fallback' to the strlen() function in the | global namespace. ... | | When code tries to call a function that doesn't currently exist in the | current namespace, the function autoloader mechanism will call the | registered function autoloaders once with the fully namespaced | function name. Doesn't that mean that the autoloader would be called for (almost) every built-in function? That sounds like a large performance hit. Even if done only once per ... per what exactly? Source location? Fully qualified unction name? Where is this information stored? Your line on pinning is a little vague on this. | The position of this RFC is that this behaviour is correct both from a | performance point-of-view I would like to see that backed that up with hard data. | though arguably from other positions it might be less correct. Which positions would that be? | The current RFC as proposed has a large BC break for the code: … | Which needs to be thought about. Can you clarify what the output would be? I'm expecting: bool(false) int(1) bool(true) bool(true) int(42) Which is already confusing, IMO? Or does the first function_exists also call the autoloader? Have you seen this pattern used anywhere? If it is, then this can't / shouldn't be broken in PHP 8.3, but rather earlist in 9.0. | Accuracy of the naming I think that the phrasing in there indicates that there is already a scope for improving on the (IMO) less than stellar proposed naming of the functions that register autoload handlers. And perhaps using *type* in the name is the best way forwards? cheers, Derick