PHP has autoloaded classes for over 20 years, but functions have never had the same treatment. If a library ships plain functions, they have to be loaded up front, usually through Composer's files list, whether a request uses them or not. That is a big part of why so many libraries wrap helpers in static methods instead.
This RFC, the fifth attempt at the idea since 2011, proposed copying class autoloading for functions.
How it would have worked#
You register a loader. When PHP hits a function it can't find, it calls your loader with the fully qualified name, and the loader can require the file that defines it:
spl_autoload_register_function_loader(function (string $function): void {
$path = __DIR__ . '/functions/' . str_replace('\\', '/', $function) . '.php';
if (is_file($path)) {
require $path;
}
});
echo \Foo\Math\add(2, 3);
Loaders only run when a lookup fails, so calls to functions that already exist cost nothing extra. Autoloading would also have kicked in for call_user_func(), is_callable(), callbacks passed to array_map() and friends, and Reflection.
The RFC added four functions: spl_autoload_register_function_loader(), spl_autoload_unregister_function_loader(), spl_autoload_function_loaders() and spl_autoload_call_function_loader(). It also gave function_exists() a second $autoload parameter that defaults to true, the same as class_exists().
The namespace catch#
Unqualified function calls inside a namespace fall back to the global function of the same name, and that fallback happens before any loader would run. So a namespaced function that shadows a built-in never gets autoloaded:
namespace Foo;
$len = strlen('bar');
The fix is to import it with use function Foo\strlen;, or to use declare(strict_namespace=1) from the companion Strict Namespace Resolution RFC, which turns the global fallback off for a file. The author checked the top 1,000 Packagist packages and found only 8 call sites across 2 packages that would hit this.
What would have changed for existing code#
With no loaders registered, nothing. The two visible changes were the four new global function names, and function_exists() calling your loaders before returning. That second one would have affected the common polyfill pattern if (! function_exists('foo')) { ... }, unless you passed false as the second argument.