On 02/07/2026 15:41, Michael Morris wrote:
An idea that popped into my head an hour ago, that I don't have
another PHP Dev to talk to about, what if you could register a
function that is called by PHP the first time it sees a namespace
declared?
This would allow functions and for that matter constants of the
namespace to be declared before they get called. It isn't as clean as
waiting until the function call itself is being called, but maybe
that's for the best. After all, if you have a dozen 10 line utility
functions do you necessarily want 10 files.
I think this is a really interesting idea.
As you say, grouping a bunch of functions with the same prefix into one
file is probably quite a common pattern anyway - it would certainly give
a very straight-forward migration for purely-static classes, e.g.
GuzzleHttp\Utils::headersFromLines() could become
GuzzleHttp\Utils\headersFromLines(), still defined in src/Utils.php
To answer points that have come up in other replies:
- I don't think it needs any special handling of sub-namespaces. The
same handler passed "Acme\Foo\Widgets\Green" might load both a
project-level setup at "$packageRoot/setup.php" and a namespace-specific
file "$packageRoot/src/Widgets/Green/functions.php". If it wants "run
once" behaviour, it can use require_once, "if ( defined('some_constant')
) return;", or just unregister itself from the stack.
- I don't think loaders should trigger for "use" statements at all. In
fact, I don't think "use" statements are even visible at run-time -
they're basically a compiler string expansion, which is why you can
write "use Foo\Bar\Baz as B; echo B::class;" without "Foo\Bar\Baz" ever
existing as a symbol.
I think it would be enough to call the handler in three places:
- on a "namespace" declaration (mostly once per file, but a block form
does exist)
- just before calling the normal class autoloader
- just before throwing an "undefined function/constant" error for a
*qualified* function/constant name
This really nicely side-steps the global-vs-namespace sequence problem
that function autoloading always runs into: by the time the engine
reaches an unqualified name, the namespace loader has already run for
the current namespace, and defined any functions it wants. The engine
can fall back to the global namespace, and error immediately if that
fails, without an extra autoload call.
To illustrate:
namespace Acme\Foo\Widgets;
use Omnicorp\Bar\Helpers;
$name = make_widget_name();
echo strlen($name);
Helpers\spin_wheels();
Line 1: All registered namespace loaders called with argument
'Acme\Foo\Widgets'
Line 2: Use statement, has no run-time effect
Line 3: Attempt to call Acme\Foo\Widgets\make_widget_name(); this might
have been defined by the loader at line 1. If it doesn't exist, attempt
to call \make_widget_name(); if that doesn't exist, throw an Error.
Line 4: Attempt to call Acme\Foo\Widgets\strlen(); if that doesn't
exist, call \strlen() which is built-in, so never fails.
Line 5: Attempt to call Omnicorp\Bar\Helpers\spin_wheels(); if it
doesn't exist, call all registered namespace loaders with argument
'Omnicorp\Bar\Helpers' and try again. If it still doesn't exist, throw
an Error.
The only thing that might need a bit more thought is the interaction
with classes: at the moment, the engine doesn't need to "remember" which
autoload calls have already happened, it can just assume that a
successful call won't happen again because the class is no longer
undefined. But with a namespace loader, code like"$client = new
Acme\Foo\Client; $server = new Acme\Foo\Server;" is going to trigger two
calls for "Acme\Foo". If the engine has an explicit cache to avoid the
duplicate call, do we need to invalidate that cache when new handlers
are registered?
The alternative is never to call the namespace loader for class
references, but that means it can't be used for other initialisation,
like setting namespace-level settings or loading data or whatever.
This feels very promising to me as an idea, though, thanks for
contributing it.
--
Rowan Tommins
[IMSoP]