rfc:function_autoloading

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
rfc:function_autoloading [2013/08/30 12:28] – add constant benchmark ircmaxellrfc:function_autoloading [2017/09/22 13:28] (current) – external edit 127.0.0.1
Line 1: Line 1:
 ====== PHP RFC: Function Autoloading ====== ====== PHP RFC: Function Autoloading ======
  
-  * Version: 0.2 +  * Version: 0.3 
-  * Date: 2013-08-29+  * Date: 2015-09-04
   * Author: Anthony Ferrara <ircmaxell@php.net>   * Author: Anthony Ferrara <ircmaxell@php.net>
   * Status: Draft   * Status: Draft
Line 11: Line 11:
 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. 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).+Presently, other types of symbols tables are not autoloadable. This RFC proposes a new unified autoloading mechanism to unify autoloading efforts across all four symbol tables (class, function, stream wrapper and constant).
  
 ===== Proposal ===== ===== Proposal =====
Line 21: Line 21:
 This proposal registers the following 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_CLASS => 1// - Represents Class autoloading +  * //php\AUTOLOAD_FUNCTION => 2// - Represents Function autoloading 
-   * //php\AUTOLOAD_FUNCTION => 2// - Represents Function autoloading +  * //php\AUTOLOAD_CONSTANT => 4// - Represents Constant autoloading 
-   * //php\AUTOLOAD_CONSTANT => 4// - Represents Constant autoloading+  * //php\AUTOLOAD_STREAM 8// - Represents Stream autoloading
  
-==== Functions ====+==== Userland Functions ====
  
-This proposal registers the following functions:+This proposal registers / modifies the following functions:
  
-=== bool php\autoload_register(callable $callback, int $type = php\AUTOLOAD_ALL, bool $prepend = false) ===+=== bool php\autoload_register(callable $callback, int $type, bool $prepend = false) ===
  
-This function behaves similar to the current //spl_autoload_register// function.+This function behaves similar to the current //spl_autoload_register// function. You can pass in a bitmask of types to register for this autoloader.
  
-=== bool php\autoload_unregister(callable $callback) ===+=== bool php\autoload_unregister(callable $callback, int $type = 0) ===
  
-This function behaves similar to the current //spl_autoload_unregister// function, and unregisters a callback that was previously registered.+This function behaves similar to the current //spl_autoload_unregister// function, and unregisters a callback that was previously registered. Note that if you registered the same callback for multiple types, this will unregister all of them unless the //$type// argument is specified.
  
-==== Behavior ====+=== array php\autoload_list(int $type) ===
  
-Registering autoloaders with the new API will allow callbacks to be fired on class, function and/or constant missing errors.+This function will return a list of all registered autoloaders for a specific type
  
-=== Default Behavior ===+=== function_exists() ===
  
-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.+A new optional boolean argument is added to //function_exists()// to match the behavior of //class_exists()// when it comes to autoloading functions.
  
-<file php basic_usage.php> +=== defined() === 
-<?php + 
-php\autoload_register(function($name, $type) { +A new optional boolean argument is added to //defined()// to match the behavior of //class_exists()// when it comes to autoloading constants. 
-    var_dump($name, $type); + 
-    switch ($type) { +==== Internal Typedefs ==== 
-        case php\AUTOLOAD_FUNCTION: + 
-            eval("function $name(){}"); +This proposal adds the following internal structures: 
-            break; + 
-        case php\AUTOLOAD_CLASS: +=== zend_autoload_func === 
-            eval("class $name {}"); + 
-            break; +This structure stores the autoloader function. 
-        case php\AUTOLOAD_CONSTANT: + 
-            define($name, $name); +<code c> 
-            break; +typedef struct zend_autoload_func 
-    } +    zend_fcall_info fci; 
-}); +    zend_fcall_info_cache fcc; 
-foo(); // string(3) "foo" int(2) +    zval *callable; 
-new foo()// string(3) "foo" int(1+    int pass_type; 
-foo; // string(3) "foo" int(3) +} _zend_autoload_func; 
-?> +</code> 
-</file>+ 
 +==== Internal Functions ==== 
 + 
 +This proposal adds the following ZEND_API functions/macros: 
 + 
 +=== void* zend_autoload_call(zend_string *name, zend_string *lname, int type) === 
 + 
 +This function will call an autoloader of the specified type. It requires two versions of the name, one which is the called case, and one which is the normalized lookup case. In the case of case-sensitive constants, they should be identical. In the case of insensitive constants, functions and classes, //lname// should be a lowercase version of the name. 
 + 
 +=== int zend_autoload_register(zend_autoload_func* func, int type, int flags=== 
 + 
 +This will register an autoload function with the specified flags (prepend is currently the only supported flag)
 + 
 +=== int zend_autoload_unregister(zend_autoload_func* func, int type=== 
 + 
 +This will unregister an autoloader function that's passed. 
 + 
 +=== int zend_lookup_function(const char *name, int name_length, zend_function <nowiki>**</nowiki>fbc=== 
 +   
 +Lookup a function by name, using autoloading 
 + 
 +=== int zend_lookup_function_ex(const char *name, int name_length, const zend_literal *key, int use_autoload, zend_function <nowiki>**</nowiki>fbc=== 
 + 
 +Lookup a function by name, optionally calling the autoloader. 
 + 
 +=== ZEND_LOOKUP_FUNCTION_BY_NAME(name, fbc=== 
 + 
 +This macro will find a function in the symbol table, or attempt to autoload it if not defined. The name must be a zend_string. 
 + 
 +=== ZEND_LOOKUP_FUNCTION_BY_KEY(name, key, fbc=== 
 + 
 +This macro will find a function in the symbol table, or attempt to autoload it if not defined. This separates the called function in //name// from the looked up function in //key//. Both name and key must be zend_string. 
 + 
 +=== ZEND_LOOKUP_FUNCTION_BY_NS_KEY(name, key, fbc=== 
 + 
 +This macro will find a function in the symbol table, or attempt to autoload it if not defined. This separates the called function in //name// from the looked up function in //key//. Both name and key must be zend_string. 
 + 
 +==== Behavior ==== 
 + 
 +Registering autoloaders with the new API will allow callbacks to be fired on class, function and/or constant missing errors.
  
 === Single Type Behavior === === Single Type Behavior ===
Line 80: Line 119:
 }, php\AUTOLOAD_FUNCTION); }, php\AUTOLOAD_FUNCTION);
 foo(); // string(3) "foo" int(2) foo(); // string(3) "foo" int(2)
-new foo(); // FATAL_ERROR+new foo(); // FATAL_ERROR as no autoloader is registered
 ?> ?>
 </file> </file>
Line 86: Line 125:
 === Multiple Type Behavior === === Multiple Type Behavior ===
  
-By passing a bitwise combination of constants to the register function, the callback will only be called for types that match.+By passing a bitwise-or'd constant to the register function, the callback will only be called for types that match).
  
 <file php multiple_type.php> <file php multiple_type.php>
Line 93: Line 132:
     var_dump($name, $type);     var_dump($name, $type);
     switch ($type) {     switch ($type) {
-        case php\AUTOLOAD_FUNCTION: +       case php\AUTOLOAD_FUNCTION: 
-            eval("function $name(){}"); +           eval("function $name(){}"); 
-            break; +           break; 
-        case php\AUTOLOAD_CONSTANT: +       case php\AUTOLOAD_CONSTANT: 
-            define($name, $name); +           define($name, $name); 
-            break;+           break;
     }     }
 }, php\AUTOLOAD_FUNCTION | php\AUTOLOAD_CONSTANT); }, php\AUTOLOAD_FUNCTION | php\AUTOLOAD_CONSTANT);
 foo(); // string(3) "foo" int(2) foo(); // string(3) "foo" int(2)
-foo; // string(3) "foo" int(3+FOO; // string(3) "FOO" int(4
-new foo(); // FATAL_ERROR+new foo(); // FATAL_ERROR as no autoloader is registered
 ?> ?>
 </file> </file>
  
-=== Registering The Same Callback Multiple Times ===+=== Registering The Same Callback Multiple Times For Different Types ===
  
-Only the first registration of a callback will succeed: +<file php multiple_registration.php>
- +
-<file php single_registration.php>+
 <?php <?php
 $callback = function($name, $type) { $callback = function($name, $type) {
-    var_dump($name, $type); +    var_dump($name); 
-    switch ($type) { +    if ($name === 'foo') { 
-        case php\AUTOLOAD_FUNCTION: +        eval("function $name(){}"); 
-            eval("function $name(){}"); +    } else 
-            break; +        define($name, $name);
-        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_register($callback, php\AUTOLOAD_FUNCTION | php\AUTOLOAD_CONSTANT); // returns false, as it could not be re-registered+php\autoload_register($callback, php\AUTOLOAD_CONSTANT);
 foo(); // string(3) "foo" int(2) foo(); // string(3) "foo" int(2)
-foo; // FATAL_ERROR+FOO; // string(3) "FOO", "FOO"
 ?> ?>
 </file> </file>
- 
-==== Performance ==== 
- 
-The following benchmarks are all run on a non-debug build compiled with //--disable-all --disable-cgi//. Note that the numbers provided are the average of the 3 fastest runs for a specific test (higher numbers are thrown out). 
- 
-=== Class Loading === 
- 
-1000 classes were generated, each in a single file. The following test script was used to execute the following tests: 
- 
-<file php benchmark_autoloading.php> 
-<?php 
-spl_autoload_register(function($class) { 
-        require __DIR__ . '/files/' . $class . '.php'; 
-}); 
- 
-$start = microtime(true); 
-for ($i = 0; $i < 1000; $i++) { 
-        $class = 'test' . $i; 
-        new $class; 
-} 
-$end = microtime(true); 
- 
-echo "Completed in " . ($end - $start) . " seconds\n"; 
-?> 
-</file> 
- 
-  * Master's //spl_autoload// loader: average 0.0225 seconds to load 1000 class files. 
-  * Proposed //php\autoload_register// loader: average 0.0219 seconds to load 1000 class files. (called via spl_autoload_register) 
- 
-Therefore, there is no performance regression when autoloading classes (in fact, it is slightly improved, since one additional //zend_function_call// call is removed). 
- 
-=== Functions === 
- 
-1000 functions were generated and placed in a single file. The following test script was used to test if there was any change to function call time for a defined function: 
- 
-<file php benchmark_functions.php> 
-<?php 
-require_once 'functions.php'; 
- 
-$start = microtime(true); 
-for ($i = 0; $i < 1000; $i++) { 
-        $func = 'test' . $i; 
-        $func(); 
-} 
-$end = microtime(true); 
- 
-echo "Completed in " . ($end - $start) . " seconds\n"; 
-?> 
-</file> 
- 
-  * Master: average 0.000216 Seconds to call 1000 functions 
-  * Proposal: average 0.000218 Seconds to call 1000 functions 
- 
- 
-Therefore, there is no performance regression to normal function calls (defined functions). The margin of error for this test was on the order of +-0.00004 seconds. 
- 
-=== Constants === 
- 
-1000 constants were generated and placed in a single file. The following test script was used to test if there was any change to function call time for a defined function: 
- 
-<file php benchmark_constants.php> 
-<?php 
-require_once 'constants.php'; 
- 
-$start = microtime(true); 
-for ($i = 0; $i < 1000; $i++) { 
-        $name = 'test' . $i; 
-        constant($name); 
-} 
-$end = microtime(true); 
- 
-echo "Completed in " . ($end - $start) . " seconds\n"; 
-?> 
-</file> 
- 
-  * Master: average 0.000228 Seconds to call 1000 constants 
-  * Proposal: average 0.000221 Seconds to call 1000 constants 
- 
-Therefore, there is no performance regression to normal constant lookups (defined constants). 
  
 ==== Userland Backwards Compatibility ==== ==== Userland Backwards Compatibility ====
Line 219: Line 171:
 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 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()//.+This means that calls to //spl_autoload_functions()// will include any autoloader (which indicates support for //php\AUTOLOAD_CLASS//) registered through //php\autoload_register()//. However, all autoloaders registered via //spl_autoload_register// will set the //pass_type// flag to //0//, meaning that only a single argument will be passed to the callback. This is for compatiblity.
  
 === __autoload() === === __autoload() ===
Line 258: Line 210:
 This implementation greatly simplifies the (internal) handling of autoloading in general. This implementation greatly simplifies the (internal) handling of autoloading in general.
  
-==== Why support multiple "types" for a single autoloader? ====+==== Why not support multiple "types" for a single autoloader? ====
  
-It is more of "why not" questionSupporting multiple types of autoloaded constructs in single callback can result in more flexible architecture for end users.+Existing autoloaders may support second parameterIf we attempted to provide the type as a second argument to an existing autoloader, it could clash. This happens with the existing //spl_autoload()// autoloader. 
 + 
 +==== What Filename Conventions Does This Support? ==== 
 + 
 +None, and all. This proposal presently implements no type of file loading handler. 
 + 
 +The only thing that is implemented is the ability to register a callback to attempt to load function or constant (or class) when it is missing. How the callback maps structures to files is outside of the scope of this RFC. 
 + 
 +==== Doesn't This Complicate The Engine? ==== 
 + 
 +Nope! The reason is that the current autoloading mechanism for classes is extremely fragile as is. 
 + 
 +For example, the implementation hinges on a global variable which sets the php-level callback to call on autoload. This requires setting up a //zend_fcall_info// struct, and a //zend_fcall_info_cache// struct, as well as dispatching a function internally to autoload. [[https://github.com/php/php-src/blob/9e17094cf4dde60432569246a9a59e48783530bb/Zend/zend_execute_API.c#L1066|The Current Implementation]]. 
 + 
 +The implementation of //spl_autoload_call// and //spl_autoload_register// are also extremely complicated. [[https://github.com/php/php-src/blob/9e17094cf4dde60432569246a9a59e48783530bb/ext/spl/php_spl.c#L466|The current SPL implementation]]. 
 + 
 +This refactor cleans both of these pieces up significantly.
  
 ===== Backward Incompatible Changes ===== ===== Backward Incompatible Changes =====
Line 290: Line 258:
 ===== Proposed PHP Version(s) ===== ===== Proposed PHP Version(s) =====
  
-PHP 5.6.x+PHP 7.1.x
  
 ===== SAPIs Impacted ===== ===== SAPIs Impacted =====
Line 299: Line 267:
  
 See Backward Incompatible Changes 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 ===== ===== php.ini Defaults =====
Line 349: Line 310:
 ===== Patches and Tests ===== ===== Patches and Tests =====
  
-There is a proof-of-concept patch against PHP-5.6 (currently master) [[https://github.com/ircmaxell/php-src/compare/function-autoloading|Github Branch]]. Note that this includes the accepted but not merged implementation of [[rfc:use_function|RFC: Use Function]]. +patch will be created shortly
- +
-This patch is not production ready, but serves as a demonstration of the functionality.+
  
 ===== References ===== ===== References =====
Line 369: Line 328:
   * 2013-08-29 0.1 Initial Creation   * 2013-08-29 0.1 Initial Creation
   * 2013-08-30 0.2 Add performance section and basic benchmarks   * 2013-08-30 0.2 Add performance section and basic benchmarks
 +  * 2015-09-04 0.3 Re-proposed
rfc/function_autoloading.1377865732.txt.gz · Last modified: 2017/09/22 13:28 (external edit)