Skip to content

is_callable('\::method') asks the autoloader for an empty class name #23232

Description

@spawnia

Description

is_callable() on a string of the form '\::method' asks the autoloader for the empty class name. The same string without the leading backslash, '::method', does not autoload at all. A leading \ only marks the name as fully qualified, so this asymmetry looks like a bug.

The following code:

<?php
spl_autoload_register(function (string $class): void {
    echo "autoload: '$class'\n";
});

foreach (['::a', '\::a', '\Foo::a', 'Foo::a'] as $callable) {
    echo "is_callable(\"$callable\")\n";
    is_callable($callable);
}

Resulted in this output:

is_callable("::a")
is_callable("\::a")
autoload: ''
is_callable("\Foo::a")
autoload: 'Foo'
is_callable("Foo::a")
autoload: 'Foo'

But I expected this output instead:

is_callable("::a")
is_callable("\::a")
is_callable("\Foo::a")
autoload: 'Foo'
is_callable("Foo::a")
autoload: 'Foo'

Reproduced unchanged on 8.3.32, 8.4.24 and 8.5.9 (official php:*-cli images).

An empty class name can never resolve, so autoloading it is pure overhead — and it is observable overhead. Composer's ClassLoader::findFileWithExtension() does $first = $class[0];, which on '' emits Warning: Uninitialized string offset 0. Any application that promotes warnings to exceptions (Laravel's HandleExceptions, for example) then fails.

We hit this in CI: Laravel's Factory::expandAttributes() calls is_callable() on every string attribute, and a randomly generated password starting with \:: (about 1 in a million with Faker's password()) made unrelated tests error out with ErrorException: Uninitialized string offset 0. Fixes are also filed with composer and laravel, but the autoload request for '' looks wrong regardless of who else guards against it.

I am happy to prepare a patch rejecting the empty class name before autoload if you agree this should change.

PHP Version

PHP 8.3.32, 8.4.24, 8.5.9

Operating System

Linux (official php:8.3-cli, php:8.4-cli, php:8.5-cli images)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions