diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 11a140e845335..e3ed5ddd36d6f 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -4189,9 +4189,8 @@ 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; diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index 3339f3992da35..cdcab60901130 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -76,6 +76,9 @@ ZEND_API ZEND_COLD void zend_verify_return_error( ZEND_API bool zend_verify_ref_array_assignable(zend_reference *ref); ZEND_API bool zend_value_instanceof_static(zval *zv); +extern zend_op_array* ZEND_FASTCALL zend_include_or_eval(zval *inc_filename, int type); + +#define ZEND_FAKE_OP_ARRAY ((zend_op_array*)(zend_intptr_t)-1) #define ZEND_REF_TYPE_SOURCES(ref) \ (ref)->sources diff --git a/ext/spl/php_spl.c b/ext/spl/php_spl.c index 5981d2385f0c7..ca2081cef735d 100644 --- a/ext/spl/php_spl.c +++ b/ext/spl/php_spl.c @@ -413,6 +413,36 @@ static bool autoload_func_info_equals( } static zend_class_entry *spl_perform_autoload(zend_string *class_name, zend_string *lc_name) { + /* classmap provides for classname => path resolution without needing to invoke a user function */ + if (SPL_G(autoload_classmap)) { + zval *mf = zend_hash_find_deref(SPL_G(autoload_classmap), lc_name); + if (mf) { + if (Z_TYPE_P(mf) != IS_STRING) { + zend_type_error("Error during autoloading from classmap. Entry \"%s\" expected a string value, %s given", ZSTR_VAL(lc_name), zend_zval_type_name(mf)); + return NULL; + } + + zend_op_array *op_array = zend_include_or_eval(mf, ZEND_REQUIRE); + if (op_array != NULL && op_array != ZEND_FAKE_OP_ARRAY) { + destroy_op_array(op_array); + efree_size(op_array, sizeof(zend_op_array)); + } + + if (EG(exception)) { + return NULL; + } + + zend_class_entry *ce = zend_hash_find_ptr(EG(class_table), lc_name); + if (ce) { + return ce; + } + + /* if the item is in the classmap it must be valid after including the file */ + zend_throw_error(NULL, "Error during autoloading from classmap. Entry \"%s\" failed to load the class from \"%s\" (Class undefined after file included)", ZSTR_VAL(lc_name), Z_STRVAL_P(mf)); + return NULL; + } + } + if (!SPL_G(autoload_functions)) { return NULL; } @@ -625,6 +655,38 @@ PHP_FUNCTION(spl_autoload_functions) } } /* }}} */ + +/* {{{ Assign an array of name => path mappings to the autoloader */ +PHP_FUNCTION(autoload_set_classmap) +{ + HashTable *ht; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ARRAY_HT(ht) + ZEND_PARSE_PARAMETERS_END(); + + if (SPL_G(autoload_classmap)) { + zend_throw_error(NULL, "Classmap Autoloader can only be initialized once per request."); + RETURN_THROWS(); + } + + GC_TRY_ADDREF(ht); + SPL_G(autoload_classmap) = ht; +} /* }}} */ + +/* {{{ Assign an array of name => path mappings to the autoloader */ +PHP_FUNCTION(autoload_get_classmap) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + if (SPL_G(autoload_classmap)) { + GC_TRY_ADDREF(SPL_G(autoload_classmap)); + RETURN_ARR(SPL_G(autoload_classmap)); + } + + RETURN_EMPTY_ARRAY(); +} /* }}} */ + /* {{{ Return hash id for given object */ PHP_FUNCTION(spl_object_hash) { @@ -732,6 +794,7 @@ PHP_RINIT_FUNCTION(spl) /* {{{ */ { SPL_G(autoload_extensions) = NULL; SPL_G(autoload_functions) = NULL; + SPL_G(autoload_classmap) = NULL; SPL_G(hash_mask_init) = 0; return SUCCESS; } /* }}} */ @@ -742,6 +805,10 @@ PHP_RSHUTDOWN_FUNCTION(spl) /* {{{ */ zend_string_release_ex(SPL_G(autoload_extensions), 0); SPL_G(autoload_extensions) = NULL; } + if (SPL_G(autoload_classmap)) { + zend_array_release(SPL_G(autoload_classmap)); + SPL_G(autoload_classmap) = NULL; + } if (SPL_G(autoload_functions)) { zend_hash_destroy(SPL_G(autoload_functions)); FREE_HASHTABLE(SPL_G(autoload_functions)); diff --git a/ext/spl/php_spl.h b/ext/spl/php_spl.h index 8009cbbb06e9a..3313e28e7ee55 100644 --- a/ext/spl/php_spl.h +++ b/ext/spl/php_spl.h @@ -54,6 +54,7 @@ PHP_MINFO_FUNCTION(spl); ZEND_BEGIN_MODULE_GLOBALS(spl) zend_string *autoload_extensions; HashTable *autoload_functions; + HashTable *autoload_classmap; intptr_t hash_mask_handle; intptr_t hash_mask_handlers; int hash_mask_init; diff --git a/ext/spl/php_spl.stub.php b/ext/spl/php_spl.stub.php index e7cc10dbc3510..81b9d33d24710 100644 --- a/ext/spl/php_spl.stub.php +++ b/ext/spl/php_spl.stub.php @@ -34,3 +34,7 @@ function iterator_apply(Traversable $iterator, callable $callback, ?array $args function iterator_count(Traversable $iterator): int {} function iterator_to_array(Traversable $iterator, bool $preserve_keys = true): array {} + +function autoload_set_classmap(array $mapping): void {} + +function autoload_get_classmap(): array {} \ No newline at end of file diff --git a/ext/spl/php_spl_arginfo.h b/ext/spl/php_spl_arginfo.h index bd2a180764f67..d4400de0a4934 100644 --- a/ext/spl/php_spl_arginfo.h +++ b/ext/spl/php_spl_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 54d193c03c2652ce40adabd10d88666a86e32728 */ + * Stub hash: dc37ffc6559b505fa389a6b0445d00a210e8eddf */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_class_implements, 0, 1, MAY_BE_ARRAY|MAY_BE_FALSE) ZEND_ARG_INFO(0, object_or_class) @@ -61,6 +61,12 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_iterator_to_array, 0, 1, IS_ARRA ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, preserve_keys, _IS_BOOL, 0, "true") ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_autoload_set_classmap, 0, 1, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, mapping, IS_ARRAY, 0) +ZEND_END_ARG_INFO() + +#define arginfo_autoload_get_classmap arginfo_spl_autoload_functions + ZEND_FUNCTION(class_implements); ZEND_FUNCTION(class_parents); @@ -77,6 +83,8 @@ ZEND_FUNCTION(spl_object_id); ZEND_FUNCTION(iterator_apply); ZEND_FUNCTION(iterator_count); ZEND_FUNCTION(iterator_to_array); +ZEND_FUNCTION(autoload_set_classmap); +ZEND_FUNCTION(autoload_get_classmap); static const zend_function_entry ext_functions[] = { @@ -95,5 +103,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(iterator_apply, arginfo_iterator_apply) ZEND_FE(iterator_count, arginfo_iterator_count) ZEND_FE(iterator_to_array, arginfo_iterator_to_array) + ZEND_FE(autoload_set_classmap, arginfo_autoload_set_classmap) + ZEND_FE(autoload_get_classmap, arginfo_autoload_get_classmap) ZEND_FE_END }; diff --git a/ext/spl/tests/autoload_set_classmap_001.phpt b/ext/spl/tests/autoload_set_classmap_001.phpt new file mode 100644 index 0000000000000..a94816d333a96 --- /dev/null +++ b/ext/spl/tests/autoload_set_classmap_001.phpt @@ -0,0 +1,40 @@ +--TEST-- +autoload_set_classmap Basic Operation +--FILE-- + +--EXPECTF-- +Example +Foo\NamespacedExample +Array +( + [example] => %s/example.php + [foo\namespacedexample] => %s/namespacedexample.php +) \ No newline at end of file diff --git a/ext/spl/tests/autoload_set_classmap_002.phpt b/ext/spl/tests/autoload_set_classmap_002.phpt new file mode 100644 index 0000000000000..6450968f63a2e --- /dev/null +++ b/ext/spl/tests/autoload_set_classmap_002.phpt @@ -0,0 +1,16 @@ +--TEST-- +autoload_set_classmap Reject non-string values +--FILE-- + 123]); + new Foo(); +} +catch (\Throwable $ex) { + echo get_class($ex) . ' - ' . $ex->getMessage(); +} + +?> +--EXPECT-- +TypeError - Error during autoloading from classmap. Entry "foo" expected a string value, int given diff --git a/ext/spl/tests/autoload_set_classmap_003.phpt b/ext/spl/tests/autoload_set_classmap_003.phpt new file mode 100644 index 0000000000000..1dc7bfd3cdf9d --- /dev/null +++ b/ext/spl/tests/autoload_set_classmap_003.phpt @@ -0,0 +1,20 @@ +--TEST-- +autoload_set_classmap Throw error if mapped file did not contain class +--FILE-- + $tmp_file]); + +try { + new Foo(); +} +catch (\Throwable $ex) { + echo get_class($ex) . ' - ' . $ex->getMessage(); +} + +?> +--EXPECTF-- +Error - Error during autoloading from classmap. Entry "foo" failed to load the class from "%s/ThisFileIsEmpty.php" (Class undefined after file included) diff --git a/ext/spl/tests/autoload_set_classmap_004.phpt b/ext/spl/tests/autoload_set_classmap_004.phpt new file mode 100644 index 0000000000000..dce73f50aef2d --- /dev/null +++ b/ext/spl/tests/autoload_set_classmap_004.phpt @@ -0,0 +1,16 @@ +--TEST-- +autoload_set_classmap Map can only be initialized once +--FILE-- + '/Foo']); + autoload_set_classmap(['foo' => '/Foo']); +} +catch (\Throwable $ex) { + echo get_class($ex) . ' - ' . $ex->getMessage(); +} + +?> +--EXPECT-- +Error - Classmap Autoloader can only be initialized once per request.