rfc:function_autoloading

This is an old revision of the document!


PHP RFC: Function Autoloading

Introduction

PHP presently offers the ability to import class-like structures (classes, interfaces and traits) via a callback (or series of them) that can be registered. This lets a developer “catch” the fact that a class isn't present, and gives them a chance to load it. This is used to great effect in the PHP community.

Presently, other types of symbols tables are not autoloadable. This RFC proposes a new unified autoloading mechanism to unify autoloading efforts across all three symbol tables (class, function and constant).

Proposal

This RFC proposes to add a suite of functions and constants in the php namespace to achieve total control over autoloading.

Constants

This proposal registers the following constants:

  • php\AUTOLOAD_ALL => ~0 - Represents all possible autoloading types (including future ones)
  • php\AUTOLOAD_CLASS => 1 - Represents Class autoloading
  • php\AUTOLOAD_FUNCTION => 2 - Represents Function autoloading
  • php\AUTOLOAD_CONSTANT => 4 - Represents Constant autoloading

Functions

This proposal registers the following functions:

bool php\autoload_register(callable $callback, int $type = php\AUTOLOAD_ALL, bool $prepend = false)

This function behaves similar to the current spl_autoload_register function.

bool php\autoload_unregister(callable $callback)

This function behaves similar to the current spl_autoload_unregister function, and unregisters a callback that was previously registered.

Behavior

Registering autoloaders with the new API will allow callbacks to be fired on class, function and/or constant missing errors.

Default Behavior

The default behavior is to respond to all types of missing constructs. The second parameter to the callback will be set to the value of a single constant indicating the type of construct being requested.

basic_usage.php
<?php
php\autoload_register(function($name, $type) {
    var_dump($name, $type);
    switch ($type) {
        case php\AUTOLOAD_FUNCTION:
            eval("function $name(){}");
            break;
        case php\AUTOLOAD_CLASS:
            eval("class $name {}");
            break;
        case php\AUTOLOAD_CONSTANT:
            define($name, $name);
            break;
    }
});
foo(); // string(3) "foo" int(2)
new foo(); // string(3) "foo" int(1)
foo; // string(3) "foo" int(3)
?>

Single Type Behavior

By passing a single constant to the register function, the callback will only be called for types that match (the $type parameter is still set, but will never vary).

single_type.php
<?php
php\autoload_register(function($name, $type) {
    var_dump($name, $type);
    switch ($type) {
        case php\AUTOLOAD_FUNCTION:
            eval("function $name(){}");
            break;
        case php\AUTOLOAD_CLASS:
            eval("class $name {}");
            break;
        case php\AUTOLOAD_CONSTANT:
            define($name, $name);
            break;
    }
}, php\AUTOLOAD_FUNCTION);
foo(); // string(3) "foo" int(2)
new foo(); // FATAL_ERROR
?>

Multiple Type Behavior

By passing a bitwise combination of constants to the register function, the callback will only be called for types that match.

multiple_type.php
<?php
php\autoload_register(function($name, $type) {
    var_dump($name, $type);
    switch ($type) {
        case php\AUTOLOAD_FUNCTION:
            eval("function $name(){}");
            break;
        case php\AUTOLOAD_CLASS:
            eval("class $name {}");
            break;
        case php\AUTOLOAD_CONSTANT:
            define($name, $name);
            break;
    }
}, php\AUTOLOAD_FUNCTION | php\AUTOLOAD_CONSTANT);
foo(); // string(3) "foo" int(2)
foo; // string(3) "foo" int(3)
new foo(); // FATAL_ERROR
?>

Registering The Same Callback Multiple Times

Only the first registration of a callback will succeed:

single_registration.php
<?php
$callback = function($name, $type) {
    var_dump($name, $type);
    switch ($type) {
        case php\AUTOLOAD_FUNCTION:
            eval("function $name(){}");
            break;
        case php\AUTOLOAD_CLASS:
            eval("class $name {}");
            break;
        case php\AUTOLOAD_CONSTANT:
            define($name, $name);
            break;
    }
};
php\autoload_register($callback, php\AUTOLOAD_FUNCTION);
php\autoload_register($callback, php\AUTOLOAD_FUNCTION | php\AUTOLOAD_CONSTANT); // returns false, as it could not be re-registered
foo(); // string(3) "foo" int(2)
foo; // FATAL_ERROR
?>

Userland Backwards Compatibility

SPL

This RFC proposes to strip the current spl_autoload_register functionality, and make spl_autoload_* simple proxies for registering core autoloaders. They will function exactly as they do now, but under the hood they will be using the new interface.

This means that calls to spl_autoload_functions() will include any autoloader (which indicates support for php\AUTOLOAD_CLASS) registered through php\autoload_register().

__autoload()

The legacy __autoload() function still works (only for classes) if no autoloader has been registered. If any autoloader is registered (class, function or constant), the legacy system will disable itself (this is how it works currently).

C API Backwards Compatibility

SPL

The autoload related SPL globals have been removed, due to the implementation being centralized.

Zend

A pair of new functions have been added:

  • ZEND_API int zend_lookup_function(const char *name, int name_length, zend_function **fbc TSRMLS_DC)
  • ZEND_API int zend_lookup_function_ex(const char *name, int name_length, const zend_literal *key, int use_autoload, zend_function **fbc TSRMLS_DC)

These will do a normal lookup for a function, and then fall back to an autoloader.

A pair of new “helper macros” have also been added:

  • ZEND_LOOKUP_FUNCTION_BY_NAME(name, name_length, fbc)
  • ZEND_LOOKUP_FUNCTION_BY_LITERAL(name, name_length, literal, fbc)

These two will do a legacy style hash-table lookup before triggering the autoloading function call (to zend_lookup_function()). The reason for this is performance.

Opcodes which lookup functions, are using this new macro. This way, there should be no performance regression at all (thanks to short-circuit operators) for defined functions.

General questions & answers

Why Rewrite A Complete Autoloader?

Initially, I implemented this as spl_function_autoload_register. Quickly, it became clear that there was a lot of duplication, and the original system was a bit stringy.

This implementation greatly simplifies the (internal) handling of autoloading in general.

Why support multiple "types" for a single autoloader?

It is more of a “why not” question. Supporting multiple types of autoloaded constructs in a single callback can result in a more flexible architecture for end users.

Backward Incompatible Changes

Userland

There should be no user-land BC changes.

PECL

EG(autoload_func)

PECL extensions which rely on the EG(autoload_func) global variable will break (due to refactor).

A quick scan of LXR shows that only the optimizer extension would change.

autoload_func_info

PECL extensions which reply on the SPL type autoload_func_info will break (due to refactor).

A quick scan of LXR shows that no extensions use this.

SPL_G(autoload_functions)

PECL extensions which rely on the SPL globals will break (due to refactor).

A quick scan of LXR shows that no extensions use this.

Proposed PHP Version(s)

PHP 5.6.x

SAPIs Impacted

None.

Impact to Existing Extensions

See Backward Incompatible Changes

New Constants

  • php\AUTOLOAD_ALL => ~0 - Represents all possible autoloading types (including future ones)
  • php\AUTOLOAD_CLASS => 1 - Represents Class autoloading
  • php\AUTOLOAD_FUNCTION => 2 - Represents Function autoloading
  • php\AUTOLOAD_CONSTANT => 4 - Represents Constant autoloading

php.ini Defaults

None.

Open Issues

None yet.

Discussion Points

Autoloading Constants

Summary

When refactoring the autoloader to support functions, adding support for constants isn't significantly difficult, yet can have an advantage.

Stance

This RFC takes the stance that it is worth while autoloading constants for consistency.

Deprecation of __autoload()

Summary

The legacy function __autoload() can be deprecated.

Stance

This is out of scope for this RFC.

Deprecation of spl_autoload_register()

Summary

Since there is a new implementation which supports class autoloading, spl_autoload_register() is redundant. Therefore, it can be deprecated.

Stance

This RFC takes the stance that deprecation should not happen right away, if at all. It is therefore out of scope for this RFC.

Patches and Tests

There is a proof-of-concept patch against PHP-5.6 (currently master) Github Branch. Note that this includes the accepted but not merged implementation of RFC: Use Function.

This patch is not production ready, but serves as a demonstration of the functionality.

References

Rejected Features

- None.

Vote

Changelog

  • 2013-08-29 0.1 Initial Creation
rfc/function_autoloading.1377861094.txt.gz · Last modified: 2017/09/22 13:28 (external edit)