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
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.
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-autoloadingThe 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 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.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-autoloadingThe 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
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
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
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
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.
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--
To unsubscribe, visit: https://www.php.net/unsub.php
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
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-autoloadingThe 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
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
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]
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 globalstrlen()
, 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
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]
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
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_mapsIf 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]
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
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-autoloadingThe 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
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.
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:
- 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.
- 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]
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
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
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-autoloadingThe 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:
- What's the performance hit?
- 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
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]
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."
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
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,
- 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.)
- If you add function autoloading, you ought to support constant autoloading as well, of course.
—Claude
- 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]
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
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
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
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
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]
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]
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-autoloadingThe 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