Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC] Autoload Classmap #5303

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions Zend/tests/autoloading/Bar.php
@@ -0,0 +1,7 @@
<?php

class Bar {

}


6 changes: 6 additions & 0 deletions Zend/tests/autoloading/Foo.php
@@ -0,0 +1,6 @@
<?php

class Foo {

}

21 changes: 21 additions & 0 deletions Zend/tests/autoloading/autoloading_classmap_001.phpt
@@ -0,0 +1,21 @@
--TEST--
Tests autoloading from a classmap
--FILE--
<?php

autoload_classmap([
Foo::class => __DIR__ . '/Foo.php'
]);

autoload_classmap([
Bar::class => __DIR__ . '/Bar.php'
]);

new Foo();
new Bar();

echo 'Done';

?>
--EXPECT--
Done
25 changes: 25 additions & 0 deletions Zend/tests/autoloading/autoloading_classmap_002.phpt
@@ -0,0 +1,25 @@
--TEST--
Tests autoloading from a classmap still allows autoloader to run
--FILE--
<?php

autoload_classmap([
Foo::class => __DIR__ . '/Foo.php'
]);

spl_autoload_register(function($class_id) {
if ($class_id === 'Bar') {
echo "Including Bar from autoloader\n";
require_once __DIR__ . '/Bar.php';
}
});

new Bar();

echo 'Done';

?>
--EXPECTF--
Notice: Cannot find bar in the autoload classmap in %s on line %d
Including Bar from autoloader
Done
20 changes: 20 additions & 0 deletions Zend/tests/autoloading/autoloading_classmap_003.phpt
@@ -0,0 +1,20 @@
--TEST--
Tests autoloading from a classmap does not interupt autoloader if it is not set
--FILE--
<?php

spl_autoload_register(function($class_id) {
if ($class_id === 'Bar') {
echo "Including Bar from autoloader\n";
require_once __DIR__ . '/Bar.php';
}
});

new Bar();

echo 'Done';

?>
--EXPECT--
Including Bar from autoloader
Done
14 changes: 14 additions & 0 deletions Zend/tests/autoloading/autoloading_classmap_004.phpt
@@ -0,0 +1,14 @@
--TEST--
Tests autoloading from a classmap where the keys have already been lowercased
--FILE--
<?php

autoload_classmap(['bar' => __DIR__ . '/Bar.php'], true);

new Bar();

echo 'Done';

?>
--EXPECT--
Done
39 changes: 39 additions & 0 deletions Zend/zend_builtin_functions.c
Expand Up @@ -75,6 +75,7 @@ static ZEND_FUNCTION(get_extension_funcs);
static ZEND_FUNCTION(get_defined_constants);
static ZEND_FUNCTION(debug_backtrace);
static ZEND_FUNCTION(debug_print_backtrace);
static ZEND_FUNCTION(autoload_classmap);
#if ZEND_DEBUG && defined(ZTS)
static ZEND_FUNCTION(zend_thread_id);
#endif
Expand Down Expand Up @@ -139,6 +140,7 @@ static const zend_function_entry builtin_functions[] = { /* {{{ */
ZEND_FE(get_defined_constants, arginfo_get_defined_constants)
ZEND_FE(debug_backtrace, arginfo_debug_backtrace)
ZEND_FE(debug_print_backtrace, arginfo_debug_print_backtrace)
ZEND_FE(autoload_classmap, arginfo_autoload_classmap)
#if ZEND_DEBUG && defined(ZTS)
ZEND_FE(zend_thread_id, arginfo_zend_thread_id)
#endif
Expand Down Expand Up @@ -2356,3 +2358,40 @@ ZEND_FUNCTION(get_extension_funcs)
}
}
/* }}} */

/* {{{ proto array autoload_classmap()
Adds an associative array to the autoloading */
ZEND_FUNCTION(autoload_classmap)
{
HashTable *map;
zend_string *class_id;
zval *class_path;
zend_string *lc_name;
zend_bool skip_lowercasing = 0;

ZEND_PARSE_PARAMETERS_START(1, 2)
Z_PARAM_ARRAY_HT(map)
Z_PARAM_OPTIONAL
Z_PARAM_BOOL(skip_lowercasing)
ZEND_PARSE_PARAMETERS_END();

if (EG(autoload_classmap) == NULL) {
ALLOC_HASHTABLE(EG(autoload_classmap));
zend_hash_init(EG(autoload_classmap), map->nTableSize, NULL, NULL, 0);
}

/* userland can save on processing by passing a lowercase list */
if (skip_lowercasing) {
zend_hash_merge(EG(autoload_classmap), map, NULL, 1);
}
else {
ZEND_HASH_FOREACH_STR_KEY_VAL(map, class_id, class_path) {
lc_name = zend_string_tolower(class_id);
zend_hash_add_or_update(EG(autoload_classmap), lc_name, class_path, HASH_ADD);
zend_string_release_ex(lc_name, 0);
} ZEND_HASH_FOREACH_END();
}

RETURN_TRUE;
}
/* }}} */
2 changes: 2 additions & 0 deletions Zend/zend_builtin_functions.stub.php
Expand Up @@ -99,6 +99,8 @@ function extension_loaded(string $extension_name): bool {}

function get_extension_funcs(string $extension_name): array|false {}

function autoload_classmap(array $map, bool $skip_lowercasing = false): bool { }

#if ZEND_DEBUG && defined(ZTS)
function zend_thread_id(): int {}
#endif
Expand Down
5 changes: 5 additions & 0 deletions Zend/zend_builtin_functions_arginfo.h
Expand Up @@ -179,6 +179,11 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_get_extension_funcs, 0, 1, MAY_B
ZEND_ARG_TYPE_INFO(0, extension_name, IS_STRING, 0)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_autoload_classmap, 0, 1, _IS_BOOL, 0)
ZEND_ARG_TYPE_INFO(0, map, IS_ARRAY, 0)
ZEND_ARG_TYPE_INFO(0, skip_lowercasing, _IS_BOOL, 0)
ZEND_END_ARG_INFO()

#if ZEND_DEBUG && defined(ZTS)
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_thread_id, 0, 0, IS_LONG, 0)
ZEND_END_ARG_INFO()
Expand Down
2 changes: 1 addition & 1 deletion Zend/zend_execute.c
Expand Up @@ -4080,7 +4080,7 @@ static zend_never_inline zend_execute_data *zend_init_dynamic_call_array(zend_ar

#define ZEND_FAKE_OP_ARRAY ((zend_op_array*)(zend_intptr_t)-1)

static zend_never_inline zend_op_array* ZEND_FASTCALL zend_include_or_eval(zval *inc_filename, int type) /* {{{ */
zend_never_inline zend_op_array* ZEND_FASTCALL zend_include_or_eval(zval *inc_filename, int type) /* {{{ */
{
zend_op_array *new_op_array = NULL;
zval tmp_inc_filename;
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_execute.h
Expand Up @@ -48,6 +48,7 @@ ZEND_API int zend_eval_string(const char *str, zval *retval_ptr, const char *str
ZEND_API int zend_eval_stringl(const char *str, size_t str_len, zval *retval_ptr, const char *string_name);
ZEND_API int zend_eval_string_ex(const char *str, zval *retval_ptr, const char *string_name, int handle_exceptions);
ZEND_API int zend_eval_stringl_ex(const char *str, size_t str_len, zval *retval_ptr, const char *string_name, int handle_exceptions);
zend_never_inline zend_op_array* ZEND_FASTCALL zend_include_or_eval(zval *inc_filename, int type);

/* export zend_pass_function to allow comparisons against it */
extern ZEND_API const zend_internal_function zend_pass_function;
Expand Down
21 changes: 20 additions & 1 deletion Zend/zend_execute_API.c
Expand Up @@ -141,6 +141,7 @@ void init_executor(void) /* {{{ */

EG(in_autoload) = NULL;
EG(autoload_func) = NULL;
EG(autoload_classmap) = NULL;
EG(error_handling) = EH_NORMAL;
EG(flags) = EG_FLAGS_INITIAL;

Expand Down Expand Up @@ -409,6 +410,11 @@ void shutdown_executor(void) /* {{{ */
zend_hash_destroy(EG(in_autoload));
FREE_HASHTABLE(EG(in_autoload));
}

if (EG(autoload_classmap)) {
zend_hash_destroy(EG(autoload_classmap));
FREE_HASHTABLE(EG(autoload_classmap));
}

if (EG(ht_iterators) != EG(ht_iterators_slots)) {
efree(EG(ht_iterators));
Expand Down Expand Up @@ -904,12 +910,25 @@ ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string *
return NULL;
}

if (EG(autoload_classmap) != NULL) {
zval* classmap_found = zend_hash_find(EG(autoload_classmap), lc_name);

if (classmap_found != NULL) {
zend_include_or_eval(classmap_found, ZEND_REQUIRE_ONCE);
ce = zend_hash_find_ptr(EG(class_table), lc_name);
zend_string_release_ex(lc_name, 0);
return ce;
}
else {
zend_error(E_NOTICE, "Cannot find %s in the autoload classmap", ZSTR_VAL(lc_name));
}
}

if (!EG(autoload_func)) {
if (!key) {
zend_string_release_ex(lc_name, 0);
}
return NULL;

}

/* Verify class name before passing it to __autoload() */
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_globals.h
Expand Up @@ -174,6 +174,7 @@ struct _zend_executor_globals {

HashTable *in_autoload;
zend_function *autoload_func;
HashTable *autoload_classmap;
zend_bool full_tables_cleanup;

/* for extended information support */
Expand Down