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]

Reply via email to