Autoloading functions/consts without a performance impact

php.internals

tyson andre

6 years ago
After a quick search, it turns out I've mostly reinvented https://wiki.php.net/rfc/autofunc which I hadn't remembered. The `spl_autoload_*()` changes it mentions is what I had in mind There's been changes to php since then, it's been 7 years, and this adds thoughts on some implementation details. It's possible to support function / const autoloading in a way that keeps existing performance. Some of the objections at the time no longer apply. - `SOME_CONSTANT` no longer falls back to the literal string in php 8. It throws an Error. - APC (used by some implementations) is no longer supported. - Many of the objections were against the available implementations based on `function __autoload()`, not the concept of autoloading. https://www.mail-archive.com/internals@lists.php.net/msg52307.html - There wasn't much traction from leads for the concept or a Proof of Concept to vote on for that RFC (I think). What if an ambiguous `function_name()` or const outside the global namespace would attempt to autoload functions in the global namespace if it would throw an Error, but not the current namespace? `function_exists()` or `defined()` would not autoload, to preserve the behavior/performance of current programs. Anything with the callable type hint (e.g. `array_map`) would also need to be modified, but I think it already needs to do that for method arrays and 'MyClass::method'. - Same for global constants - That should avoid the performance hit - this autoloading would only be triggered when php would previously throw an Error or warning for undefined functions/constants. - This would also avoid the need to load polyfill files (e.g. mbstring) or test framework files when their functions/constants are unused. - One blocker for other autoload proposals seemed to be performance if I understood correctly (e.g. attempting to autoload NS\strlen() every time strlen was called). https://externals.io/message/54425#54616 and https://externals.io/message/54425#54655 detail this - choosing a different order of checks avoids the performance hit. - In addition to the RFC, changing the signatures to `defined(string $name, bool $autoload = false)` and `function_exists($name, bool $autoload = false)` might be useful ways to allow users to choose to autoload when checking for existence. And there'd have to be a hash map, flag, or other check to avoid recursion on the same constant/function. Background: A function call or constant usage is ambiguous if it could look for the function in two namespaces, as described by the rules documented in the below link. It's unambiguous if it ends up looking in only one namespace. https://www.php.net/manual/en/language.namespaces.rules.php The only type of ambiguous function call or global constant use is (7.): > 7. For unqualified names, > if no import rule applies and the name refers to a function or constant > and the code is outside the global namespace, the name is resolved at runtime. > Assuming the code is in namespace A\B, here is how a call to function foo() is resolved: > 1. It looks for a function from the current namespace: A\B\foo(). > 2. It tries to find and call the global function foo(). My approach should be in line with the current name resolution for unqualified names outside the global namespace. 1. It (first) looks for a function from the current namespace: A\B\foo(). 2. It (next) tries to find and call the global function foo(). 3. (Optional) NEW ADDITION: Next, if both functions were undefined, the autoloader(s) attempt to autoload the function A\B\foo() (and call it instead of throwing if found) before proceeding to step (4.) 4. NEW ADDITION: If both functions were undefined, the autoloader(s) attempt to autoload the global function foo() (and call it instead of throwing if found) before throwing an Error And for unambiguous calls, find and autoload the only name it has. The fact that one of the two possible functions gets cached by the php VM in CACHED_PTR (for the lifetime of the request) the first time the function is found in either namespace will remain unchanged with this proposal. (Out of scope of the "use global functions/consts" RFC, so I'm splitting out this discussion. There is no proof of concept yet.) ------ Pros: - Don't need to preload entire files and dependencies up-front whether or not they're used (e.g. with composer), to supply functional libraries or frameworks, or to supply polyfills for native modules. - Supporting this encourages using functions/global constants from namespaces, where they were previously discouraged by lack of autoloading support, or needed manual require_once statements. - Preloading support makes it easier to avoid \NS\FooDoer::foo() instead of \NS\do_foo() in cases which would be unnatural (e.g. classes with just one method)

Nikita Popov

6 years ago
On Fri, Jan 3, 2020 at 2:51 AM tyson andre <tysonandre775@hotmail.com> wrote:
> After a quick search, it turns out I've mostly reinvented > https://wiki.php.net/rfc/autofunc which I hadn't remembered. > The `spl_autoload_*()` changes it mentions is what I had in mind > > There's been changes to php since then, it's been 7 years, > and this adds thoughts on some implementation details. > > It's possible to support function / const autoloading > in a way that keeps existing performance. > Some of the objections at the time no longer apply. > > - `SOME_CONSTANT` no longer falls back to the literal string in php 8. > It throws an Error. > - APC (used by some implementations) is no longer supported. > - Many of the objections were against the available implementations > based on `function __autoload()`, not the concept of autoloading. > > https://www.mail-archive.com/internals@lists.php.net/msg52307.html > - There wasn't much traction from leads for the concept > or a Proof of Concept to vote on for that RFC (I think). > > What if an ambiguous `function_name()` or const outside the global > namespace would attempt to autoload functions in the global namespace > if it would throw an Error, but not the current namespace? > > `function_exists()` or `defined()` would not autoload, > to preserve the behavior/performance of current programs. > Anything with the callable type hint (e.g. `array_map`) would also need to > be modified, > but I think it already needs to do that for method arrays and > 'MyClass::method'. > > - Same for global constants > - That should avoid the performance hit - this autoloading would only be > triggered > when php would previously throw an Error or warning > for undefined functions/constants. > - This would also avoid the need to load polyfill files (e.g. mbstring) > or test framework files when their functions/constants are unused. > - One blocker for other autoload proposals > seemed to be performance if I understood correctly > (e.g. attempting to autoload NS\strlen() every time strlen was called). > > https://externals.io/message/54425#54616 and > https://externals.io/message/54425#54655 > detail this - choosing a different order of checks avoids the > performance hit. > - In addition to the RFC, changing the signatures to `defined(string > $name, bool $autoload = false)` > and `function_exists($name, bool $autoload = false)` > might be useful ways to allow users to choose to autoload when checking > for existence. > > And there'd have to be a hash map, flag, or other check > to avoid recursion on the same constant/function. > > Background: A function call or constant usage is > ambiguous if it could look for the function in two namespaces, > as described by the rules documented in the below link. > It's unambiguous if it ends up looking in only one namespace. > > https://www.php.net/manual/en/language.namespaces.rules.php > The only type of ambiguous function call or global constant use is (7.): > > > 7. For unqualified names, > > if no import rule applies and the name refers to a function or > constant > > and the code is outside the global namespace, the name is resolved at > runtime. > > Assuming the code is in namespace A\B, here is how a call to function > foo() is resolved: > > 1. It looks for a function from the current namespace: A\B\foo(). > > 2. It tries to find and call the global function foo(). > > My approach should be in line with the current name resolution > for unqualified names outside the global namespace. > > 1. It (first) looks for a function from the current namespace: A\B\foo(). > 2. It (next) tries to find and call the global function foo(). > 3. (Optional) NEW ADDITION: Next, if both functions were undefined, > the autoloader(s) attempt to autoload the function A\B\foo() > (and call it instead of throwing if found) before proceeding to step > (4.) > 4. NEW ADDITION: If both functions were undefined, > the autoloader(s) attempt to autoload the global function foo() > (and call it instead of throwing if found) before throwing an Error > > And for unambiguous calls, find and autoload the only name it has. > > The fact that one of the two possible functions gets cached by the php VM > in CACHED_PTR > (for the lifetime of the request) the first time the function is found > in either namespace will remain unchanged with this proposal. >
I believe the problem here is this: The fact that the global function is being cached, and continues to be cached when a namespaced function is later defined, is a bug. See for example https://bugs.php.net/bug.php?id=64346. We've never prioritized fixing this issue, but implementing the proposed autoloading behavior would make fixing it essentially impossible, as we certainly can't perform an autoloader invocation on each call. Nikita

Rasmus Schultz

6 years ago
On Fri, Jan 3, 2020 at 10:35 AM Nikita Popov <nikita.ppv@gmail.com> wrote:
> On Fri, Jan 3, 2020 at 2:51 AM tyson andre <tysonandre775@hotmail.com> > wrote: > > > After a quick search, it turns out I've mostly reinvented > > https://wiki.php.net/rfc/autofunc which I hadn't remembered. > > The `spl_autoload_*()` changes it mentions is what I had in mind > > > > There's been changes to php since then, it's been 7 years, > > and this adds thoughts on some implementation details. > > > > It's possible to support function / const autoloading > > in a way that keeps existing performance. > > Some of the objections at the time no longer apply. > > > > - `SOME_CONSTANT` no longer falls back to the literal string in php 8. > > It throws an Error. > > - APC (used by some implementations) is no longer supported. > > - Many of the objections were against the available implementations > > based on `function __autoload()`, not the concept of autoloading. > > > > https://www.mail-archive.com/internals@lists.php.net/msg52307.html > > - There wasn't much traction from leads for the concept > > or a Proof of Concept to vote on for that RFC (I think). > > > > What if an ambiguous `function_name()` or const outside the global > > namespace would attempt to autoload functions in the global namespace > > if it would throw an Error, but not the current namespace? > > > > `function_exists()` or `defined()` would not autoload, > > to preserve the behavior/performance of current programs. > > Anything with the callable type hint (e.g. `array_map`) would also need > to > > be modified, > > but I think it already needs to do that for method arrays and > > 'MyClass::method'. > > > > - Same for global constants > > - That should avoid the performance hit - this autoloading would only be > > triggered > > when php would previously throw an Error or warning > > for undefined functions/constants. > > - This would also avoid the need to load polyfill files (e.g. mbstring) > > or test framework files when their functions/constants are unused. > > - One blocker for other autoload proposals > > seemed to be performance if I understood correctly > > (e.g. attempting to autoload NS\strlen() every time strlen was called). > > > > https://externals.io/message/54425#54616 and > > https://externals.io/message/54425#54655 > > detail this - choosing a different order of checks avoids the > > performance hit. > > - In addition to the RFC, changing the signatures to `defined(string > > $name, bool $autoload = false)` > > and `function_exists($name, bool $autoload = false)` > > might be useful ways to allow users to choose to autoload when checking > > for existence. > > > > And there'd have to be a hash map, flag, or other check > > to avoid recursion on the same constant/function. > > > > Background: A function call or constant usage is > > ambiguous if it could look for the function in two namespaces, > > as described by the rules documented in the below link. > > It's unambiguous if it ends up looking in only one namespace. > > > > https://www.php.net/manual/en/language.namespaces.rules.php > > The only type of ambiguous function call or global constant use is (7.): > > > > > 7. For unqualified names, > > > if no import rule applies and the name refers to a function or > > constant > > > and the code is outside the global namespace, the name is resolved > at > > runtime. > > > Assuming the code is in namespace A\B, here is how a call to > function > > foo() is resolved: > > > 1. It looks for a function from the current namespace: A\B\foo(). > > > 2. It tries to find and call the global function foo(). > > > > My approach should be in line with the current name resolution > > for unqualified names outside the global namespace. > > > > 1. It (first) looks for a function from the current namespace: A\B\foo(). > > 2. It (next) tries to find and call the global function foo(). > > 3. (Optional) NEW ADDITION: Next, if both functions were undefined, > > the autoloader(s) attempt to autoload the function A\B\foo() > > (and call it instead of throwing if found) before proceeding to step > > (4.) > > 4. NEW ADDITION: If both functions were undefined, > > the autoloader(s) attempt to autoload the global function foo() > > (and call it instead of throwing if found) before throwing an Error > > > > And for unambiguous calls, find and autoload the only name it has. > > > > The fact that one of the two possible functions gets cached by the php VM > > in CACHED_PTR > > (for the lifetime of the request) the first time the function is found > > in either namespace will remain unchanged with this proposal. > > > > I believe the problem here is this: The fact that the global function is > being cached, and continues to be cached when a namespaced function is > later defined, is a bug. See for example > https://bugs.php.net/bug.php?id=64346. We've never prioritized fixing this > issue, but implementing the proposed autoloading behavior would make fixing > it essentially impossible, as we certainly can't perform an autoloader > invocation on each call. > > Nikita >
I will just briefly mention an alternative idea I've brought up before. (there's no RFC for this.) use function MyClass::myStaticFunction; myStaticFunction(); This would allow importing a static function and calling it just like a flat function. The way most people normally write code in PHP, they're favoring static functions over flat functions, since aggressively preloading flat functions (e.g. "file" in "composer.json") doesn't scale, and using manual include/require statements is clumsy and error-prone. (and not really possible with Composer.) Many existing PHP codebases already have "classes" that are really just "pseudo-namespaces" for collections of functions, so this approach takes into account the existing ecosystem. It also implicitly addresses autoloading, which would just automatically work with Composer (or any class auto-loader) with no further changes. It also bypasses any learning curve surrounding function name resolution rules, if this were to become more complex by adding more rules and details with regards to resolving and autoloading flat functions. Is there a really good reason to encourage the entire community to migrate all of their existing codebases from static classes to flat functions? Expecting Composer to add support for it. Expecting package vendors to list all function names in package metadata files, and so on. In many cases, migrating from static to flat functions also means migrating any constants from class constants to namespaced constants - so this likely leads down the rabbit hole to autoloading constants as well, and/or having to use some mix of class constants and flat functions which isn't very desirable. Is there any practical return on that investment? Static functions aren't really any different from flat functions. (if you're thinking "state", that's really up to the author - your flat functions could depend on global state just like a static function could depend on static properties...) Just my five cents on the matter :-)

Nikita Popov

6 years ago
On Fri, Jan 3, 2020 at 12:22 PM Rasmus Schultz <rasmus@mindplay.dk> wrote:
> On Fri, Jan 3, 2020 at 10:35 AM Nikita Popov <nikita.ppv@gmail.com> wrote: > >> On Fri, Jan 3, 2020 at 2:51 AM tyson andre <tysonandre775@hotmail.com> >> wrote: >> >> > After a quick search, it turns out I've mostly reinvented >> > https://wiki.php.net/rfc/autofunc which I hadn't remembered. >> > The `spl_autoload_*()` changes it mentions is what I had in mind >> > >> > There's been changes to php since then, it's been 7 years, >> > and this adds thoughts on some implementation details. >> > >> > It's possible to support function / const autoloading >> > in a way that keeps existing performance. >> > Some of the objections at the time no longer apply. >> > >> > - `SOME_CONSTANT` no longer falls back to the literal string in php 8. >> > It throws an Error. >> > - APC (used by some implementations) is no longer supported. >> > - Many of the objections were against the available implementations >> > based on `function __autoload()`, not the concept of autoloading. >> > >> > https://www.mail-archive.com/internals@lists.php.net/msg52307.html >> > - There wasn't much traction from leads for the concept >> > or a Proof of Concept to vote on for that RFC (I think). >> > >> > What if an ambiguous `function_name()` or const outside the global >> > namespace would attempt to autoload functions in the global namespace >> > if it would throw an Error, but not the current namespace? >> > >> > `function_exists()` or `defined()` would not autoload, >> > to preserve the behavior/performance of current programs. >> > Anything with the callable type hint (e.g. `array_map`) would also need >> to >> > be modified, >> > but I think it already needs to do that for method arrays and >> > 'MyClass::method'. >> > >> > - Same for global constants >> > - That should avoid the performance hit - this autoloading would only be >> > triggered >> > when php would previously throw an Error or warning >> > for undefined functions/constants. >> > - This would also avoid the need to load polyfill files (e.g. mbstring) >> > or test framework files when their functions/constants are unused. >> > - One blocker for other autoload proposals >> > seemed to be performance if I understood correctly >> > (e.g. attempting to autoload NS\strlen() every time strlen was >> called). >> > >> > https://externals.io/message/54425#54616 and >> > https://externals.io/message/54425#54655 >> > detail this - choosing a different order of checks avoids the >> > performance hit. >> > - In addition to the RFC, changing the signatures to `defined(string >> > $name, bool $autoload = false)` >> > and `function_exists($name, bool $autoload = false)` >> > might be useful ways to allow users to choose to autoload when >> checking >> > for existence. >> > >> > And there'd have to be a hash map, flag, or other check >> > to avoid recursion on the same constant/function. >> > >> > Background: A function call or constant usage is >> > ambiguous if it could look for the function in two namespaces, >> > as described by the rules documented in the below link. >> > It's unambiguous if it ends up looking in only one namespace. >> > >> > https://www.php.net/manual/en/language.namespaces.rules.php >> > The only type of ambiguous function call or global constant use is (7.): >> > >> > > 7. For unqualified names, >> > > if no import rule applies and the name refers to a function or >> > constant >> > > and the code is outside the global namespace, the name is resolved >> at >> > runtime. >> > > Assuming the code is in namespace A\B, here is how a call to >> function >> > foo() is resolved: >> > > 1. It looks for a function from the current namespace: A\B\foo(). >> > > 2. It tries to find and call the global function foo(). >> > >> > My approach should be in line with the current name resolution >> > for unqualified names outside the global namespace. >> > >> > 1. It (first) looks for a function from the current namespace: >> A\B\foo(). >> > 2. It (next) tries to find and call the global function foo(). >> > 3. (Optional) NEW ADDITION: Next, if both functions were undefined, >> > the autoloader(s) attempt to autoload the function A\B\foo() >> > (and call it instead of throwing if found) before proceeding to step >> > (4.) >> > 4. NEW ADDITION: If both functions were undefined, >> > the autoloader(s) attempt to autoload the global function foo() >> > (and call it instead of throwing if found) before throwing an Error >> > >> > And for unambiguous calls, find and autoload the only name it has. >> > >> > The fact that one of the two possible functions gets cached by the php >> VM >> > in CACHED_PTR >> > (for the lifetime of the request) the first time the function is found >> > in either namespace will remain unchanged with this proposal. >> > >> >> I believe the problem here is this: The fact that the global function is >> being cached, and continues to be cached when a namespaced function is >> later defined, is a bug. See for example >> https://bugs.php.net/bug.php?id=64346. We've never prioritized fixing >> this >> issue, but implementing the proposed autoloading behavior would make >> fixing >> it essentially impossible, as we certainly can't perform an autoloader >> invocation on each call. >> >> Nikita >> > > I will just briefly mention an alternative idea I've brought up before. > (there's no RFC for this.) > > use function MyClass::myStaticFunction; > > myStaticFunction(); > > This would allow importing a static function and calling it just like a > flat function. > > The way most people normally write code in PHP, they're favoring static > functions over flat functions, since aggressively preloading flat functions > (e.g. "file" in "composer.json") doesn't scale, and using manual > include/require statements is clumsy and error-prone. (and not really > possible with Composer.) > > Many existing PHP codebases already have "classes" that are really just > "pseudo-namespaces" for collections of functions, so this approach takes > into account the existing ecosystem. > > It also implicitly addresses autoloading, which would just automatically > work with Composer (or any class auto-loader) with no further changes. > > It also bypasses any learning curve surrounding function name resolution > rules, if this were to become more complex by adding more rules and details > with regards to resolving and autoloading flat functions. > > Is there a really good reason to encourage the entire community to migrate > all of their existing codebases from static classes to flat functions? > Expecting Composer to add support for it. Expecting package vendors to list > all function names in package metadata files, and so on. > > In many cases, migrating from static to flat functions also means > migrating any constants from class constants to namespaced constants - so > this likely leads down the rabbit hole to autoloading constants as well, > and/or having to use some mix of class constants and flat functions which > isn't very desirable. > > Is there any practical return on that investment? > > Static functions aren't really any different from flat functions. > > (if you're thinking "state", that's really up to the author - your flat > functions could depend on global state just like a static function could > depend on static properties...) > > Just my five cents on the matter :-) >
Hey Rasmus, One issue I see with important static methods in this manner, is that PHP has no concept of a static method call -- rather, it performs scoped calls, that may refer to static or instance methods. use function A::foo; class B extends A { public function test() { foo(); // What does this do? } } If the call to foo() is simply treated equivalently to a call to A::foo(), then this may have some quite surprising behavior: If A::foo() is an instance method, then this call to foo() will inherit $this, which is something that normally does not happen with free-standing function calls. Possibly this would need a new call type that enforces that the method is actually static? Overall though, I do tend to agree that the use of static methods is at this point more idiomatic than the use of free-standing functions, and it might make more sense to go in that direction. Nikita

Rowan Collins

6 years ago
On Fri, 3 Jan 2020 at 11:37, Nikita Popov <nikita.ppv@gmail.com> wrote:
> Overall though, I do tend to agree that the use of static methods is at > this point more idiomatic than the use of free-standing functions, and it > might make more sense to go in that direction. >
Perhaps a way forward would be to effectively abandon namespaced functions as a language feature: * Implement https://wiki.php.net/rfc/use_global_elements * Plan to eventually make "use global functions" the default * Revive the "static class" modifier, which was rejected under https://wiki.php.net/rfc/abstract_final_class primarily on the grounds that "namespaced functions should be used instead" * Add a "use function A::foo" syntax that requires the target function to actually be static Namespaces containing only functions and constants could then be replaced by autoloadable classes. However, if we're looking at radical changes of direction, I wonder if we should instead plan to phase out autoloading itself. With OpCache and now pre-loading, the idea of "only include the files you need" makes much less sense than it did ten years ago. OpCache on the command-line is probably used much more rarely, but given how easy it is to incidentally pull in dozens of classes from a third-party library, maybe that's something that should change? Regards,
-- Rowan Tommins [IMSoP]

Mike Schinkel

6 years ago
> On Jan 3, 2020, at 9:48 AM, Rowan Tommins <rowan.collins@gmail.com> wrote: > > However, if we're looking at radical changes of direction, I wonder if we > should instead plan to phase out autoloading itself.
Yes. Please. Autoloading is a medicine that is almost worse than the disease. -Mike

tyson andre

6 years ago

Mike Schinkel

6 years ago
> On Jan 3, 2020, at 6:22 AM, Rasmus Schultz <rasmus@mindplay.dk> wrote: > > I will just briefly mention an alternative idea I've brought up before. > (there's no RFC for this.) > > use function MyClass::myStaticFunction; > > myStaticFunction();
This strikes me as an excellent approach. Well-spent five cents. -Mike

Rowan Collins

6 years ago
On Fri, 3 Jan 2020 at 01:51, tyson andre <tysonandre775@hotmail.com> wrote:
> 1. It (first) looks for a function from the current namespace: A\B\foo(). > 2. It (next) tries to find and call the global function foo(). > 3. (Optional) NEW ADDITION: Next, if both functions were undefined, > the autoloader(s) attempt to autoload the function A\B\foo() > (and call it instead of throwing if found) before proceeding to step > (4.) > 4. NEW ADDITION: If both functions were undefined, > the autoloader(s) attempt to autoload the global function foo() > (and call it instead of throwing if found) before throwing an Error >
I think running the autoloaders out of sequence from the actual name lookup is a recipe for confusion. Consider this case: namespace Foo; echo strlen('hello'); // Finds a global function at step 2, so doesn't trigger the autoloader echo Foo\strlen('hello'); // Explicitly namespaced function, so triggers the autoloader echo strlen('hello'); // Should this run \strlen or Foo\strlen? If we cache the resolution to global function at line 2, removing that line changes the rest of the program. If we don't cache it there, then what happens if we run this code in a loop? Admittedly, that ambiguity can already be introduced if you explicitly define the function between the two calls, but that can be worked around by pre-loading all functions for a namespace. The whole purpose of autoloading is that you don't need to think about when definitions are loaded, so this kind of case would be much more likely to bite people. Regards,
-- Rowan Tommins [IMSoP]

tyson andre

6 years ago

Rowan Collins

6 years ago
On Fri, 3 Jan 2020 at 14:39, tyson andre <tysonandre775@hotmail.com> wrote:
> > namespace Foo; > > echo strlen('hello'); // Finds a global function at step 2, so doesn't > > trigger the autoloader > > echo \Foo\strlen('hello'); // Explicitly namespaced function, so triggers > > the autoloader > > echo strlen('hello'); // Should this run \strlen or Foo\strlen? > > The example you were thinking of would call \strlen on line 2, \Foo\strlen > on line 3, and \Foo\strlen on line 4. > (because the caching is per-opcode, not per-file or per-namespace) >
Sorry, I should have been clearer - I wasn't asking for a literal answer based on any particular implementation, I was asking rhetorically what a user would expect to happen. Let's make the example a tiny bit more complex: namespace Foo; function one() { echo strlen('hello'); } function two() { echo Foo\strlen('hello again'); } Now the behaviour of my program can completely change depending on which of those functions I call first, which might even depend on user input. Regardless of exactly how the cache works, that kind of unpredictability is a recipe for disaster. The only way to make it predictable again is to pre-define the namespaced function in my startup code, at which point I don't need function autoloading. Regards,
-- Rowan Tommins [IMSoP]

tyson andre

6 years ago

Rowan Collins

6 years ago
On Fri, 3 Jan 2020 at 15:21, tyson andre <tysonandre775@hotmail.com> wrote:
> > Now the behaviour of my program can completely change depending on which > of > > those functions I call first, which might even depend on user input. > > Regardless of exactly how the cache works, that kind of unpredictability > is > > a recipe for disaster. > > It already does completely change depending on user input, without > autoloading, in the same way. >
Not really, the only way you could have the same dependency on call order at the moment would be if it looked like this: namespace Foo; function one() { echo strlen('hello'); } function two() { require_once 'functions/Foo/strlen.php'; } At that point, it's pretty obvious that you're conditionally including a definition, which is not the case with the original example.
> I assume you're arguing against autoloading without autoloading \NS\strlen > every time >
Yes, I'm saying that the autoloader should be called for each lookup, so autoloading of Foo\strlen should come between checking if Foo\strlen is defined, and checking if \strlen is defined.
> > > The only way to make it predictable again is to pre-define the namespaced > > function in my startup code, at which point I don't need function > > autoloading. > > Other use cases would benefit from function autoloading, though.
Then maybe we should target the feature at those use cases - don't call the autoloader *at all* unless we know the fully-qualified name, and give up trying to "square the circle" by combining fallbacks with autoloading for unqualified names? Regards,
-- Rowan Tommins [IMSoP]

tyson andre

6 years ago

Rowan Collins

6 years ago
On 05/01/2020 18:03, tyson andre wrote:
>> Yes, I'm saying that the autoloader should be called for each lookup, so >> autoloading of Foo\strlen should come between checking if Foo\strlen is >> defined, and checking if \strlen is defined. > That would be the intuitive approach for a language, > but as mentioned earlier, > it would significantly harm performance of existing code for php 7 > (slightly without an autoloader, more with autoloaders), > and I can't imagine that getting approved.
Yes, I'm fully aware of the downsides. I just think relaxing that constraint would lead to a horribly confusing language.
> I'd considered that option, but that introduces its own surprises, > which I'd consider to have more frequent and significant impact on applications that start using this. > > namespace NS; > function f1($x) { > return mb_strlen($x) + 1; > } > function f2($x) { > return \mb_strlen($x) * 2; > } > > Calling f2() then f1() would work, but f1() then f2() would not work, because f1() wouldn't autoload.
True; again, it violates the user's expectation, which I think can be summed up this way: - The autoloader for a function should run the first time that function would run if it was pre-defined. That in turn stems from an even more fundamental expectation: - An application using an autoloader should behave the same as one which defines all functions in advance. The more I think about it, the more I think we should just be optimising for the case where everything *is* defined in advance. As far as I know, PHP's autoloading is an anomaly among programming languages, and maybe it has outlived its usefulness. Regards,
-- Rowan Tommins (né Collins) [IMSoP]

Rasmus Schultz

6 years ago
On Sun, Jan 5, 2020 at 7:48 PM Rowan Tommins <rowan.collins@gmail.com> wrote:
> On 05/01/2020 18:03, tyson andre wrote: > >> Yes, I'm saying that the autoloader should be called for each lookup, so > >> autoloading of Foo\strlen should come between checking if Foo\strlen is > >> defined, and checking if \strlen is defined. > > That would be the intuitive approach for a language, > > but as mentioned earlier, > > it would significantly harm performance of existing code for php 7 > > (slightly without an autoloader, more with autoloaders), > > and I can't imagine that getting approved. > > > Yes, I'm fully aware of the downsides. I just think relaxing that > constraint would lead to a horribly confusing language. > > > > I'd considered that option, but that introduces its own surprises, > > which I'd consider to have more frequent and significant impact on > applications that start using this. > > > > namespace NS; > > function f1($x) { > > return mb_strlen($x) + 1; > > } > > function f2($x) { > > return \mb_strlen($x) * 2; > > } > > > > Calling f2() then f1() would work, but f1() then f2() would not work, > because f1() wouldn't autoload. > > > True; again, it violates the user's expectation, which I think can be > summed up this way: > > - The autoloader for a function should run the first time that function > would run if it was pre-defined. > > That in turn stems from an even more fundamental expectation: > > - An application using an autoloader should behave the same as one which > defines all functions in advance. > > > The more I think about it, the more I think we should just be optimising > for the case where everything *is* defined in advance. As far as I know, > PHP's autoloading is an anomaly among programming languages, and maybe > it has outlived its usefulness. >
I would argue that name resolution, the way it works right now, is also somewhat of an anomaly among programming languages. Removing autoloading from the language would have two major implications: 1. You have to figure out what to load in advance - thinking you can just preload all *.php files in a folder is probably pretty naive, because some of those files (PHP being a scripting language) are going to be actual be scripts (CLI scripts, tests, etc.) and not just declarations. 2. Code generation - and I mean, think of that what you want, but PHP lends itself well to code generation, and many projects (ORMs, template engines, annotation parsers, AOP and other meta-programming frameworks, etc.) use the autoloader as a hook to generate code on the fly. So this seems like a fairly drastic change to the language, which might render a lot of projects defunct with no recourse.