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 onfunction __autoload()
, not the concept of autoloading.internals@lists.php.net/msg52307.html" rel="nofollow" target="_blank">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)
andfunction_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.):
- 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:
- It looks for a function from the current namespace: A\B\foo().
- 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.
- It (first) looks for a function from the current namespace: A\B\foo().
- It (next) tries to find and call the global function foo().
- (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.) - 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)
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.
Thespl_autoload_*()
changes it mentions is what I had in mindThere'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 onfunction __autoload()
, not the concept of autoloading.internals@lists.php.net/msg52307.html" rel="nofollow" target="_blank">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()
ordefined()
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)
andfunction_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.):
- 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:
- It looks for a function from the current namespace: A\B\foo().
- 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.
- It (first) looks for a function from the current namespace: A\B\foo().
- It (next) tries to find and call the global function foo().
- (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.)- 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 ErrorAnd 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
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.
Thespl_autoload_*()
changes it mentions is what I had in mindThere'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 onfunction __autoload()
, not the concept of autoloading.internals@lists.php.net/msg52307.html" rel="nofollow" target="_blank">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()
ordefined()
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)
andfunction_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.):
- 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:
- It looks for a function from the current namespace: A\B\foo().
- 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.
- It (first) looks for a function from the current namespace: A\B\foo().
- It (next) tries to find and call the global function foo().
- (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.)- 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 ErrorAnd 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 :-)
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.
Thespl_autoload_*()
changes it mentions is what I had in mindThere'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 onfunction __autoload()
, not the concept of autoloading.internals@lists.php.net/msg52307.html" rel="nofollow" target="_blank">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()
ordefined()
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)
andfunction_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.):
- 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:
- It looks for a function from the current namespace: A\B\foo().
- 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.
- It (first) looks for a function from the current namespace:
A\B\foo().- It (next) tries to find and call the global function foo().
- (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.)- 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 ErrorAnd 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
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]
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
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?
I do like this approach. A new call opcode would make sense for that.
It won't help for internal functions or their polyfills such as mb_strlen()
, etc.,
so autoloading functions would still make some sense if that approach was taken.
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
- It (first) looks for a function from the current namespace: A\B\foo().
- It (next) tries to find and call the global function foo().
- (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.)- 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]
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?
It (and my proposed autoloader change)
changes it for that call (at that opcode, i.e. for the call at that line and column).
PHP's caching does not cache it for other calls within the same file.
If that call opcode was within a function or loop, however,
subsequent calls would use the cached resolution.
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)
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.
If we skip autoloading ambiguous calls in the current namespace,
then we wouldn't have to perform an autoloader invocation every call,
and the problem of fixing that bug would be unchanged.
I marked step 3 as optional because there were arguments for/against it.
-Tyson
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]
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.
I assume you're arguing against autoloading without autoloading \NS\strlen every time,
but I still consider the approach I'm proposing an improvement over existing unpredictable behavior.
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.
The use case in your example is very different from the common use case I
have in mind for proposing autoloading - name reuse should be uncommon in practice.
Adding "use NS\strlen", "namespace\strlen", etc. are alternatives to make it predictable
I'd consider reusing the name from the global namespace intentionally to be a code smell.
Overlap might be unavoidable for common names such as \NS\run() in some libraries,
but hopefully they'd preload the entire file of functions for those,
or be in a different namespace from the code using those libraries,
and other libraries (e.g. polyfills in global namespace) could still use autoloading.
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]
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.
-
One option I'd thought of is to invalidate runtime caches of
ambiguous calls' opcodes when the set of autoloaders change.
This is technically possible,
but it would makespl_autoload_register()
/spl_autoload_unregister() extremely slow for large applications.
(it would be implemented by iterating over all functions/closures/methods/global scopes
and deleting the runtime cache entry for those call opcodes.)That way, if a new autoloader got registered, existing unqualified calls to
strlen()
would check for \Foo\strlen once, the next time they were called.I don't have any plans to try this or work on this approach,
but it's worth noting that it's possible.
This approach is taken in http://github.com/runkit7/runkit7
when modifying definitions of internal functions (runkit_function_redefine()), etc.
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?
The largest issue with that is that unqualified names are extremely common in php 7 code.
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.
- ASIDE: I guess something along these lines would be less likely to cause surprises
if one implemented "autoload, but only for qualified functions outside of the global namespace,
but autoloading global functions is my main reason to want this with current code (e.g. polyfills).
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]
On Sun, Jan 5, 2020 at 7:48 PM Rowan Tommins rowan.collins@gmail.com
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:
-
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. -
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.