Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Zend/zend_execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions Zend/zend_execute.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
67 changes: 67 additions & 0 deletions ext/spl/php_spl.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;
} /* }}} */
Expand All @@ -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));
Expand Down
1 change: 1 addition & 0 deletions ext/spl/php_spl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions ext/spl/php_spl.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
12 changes: 11 additions & 1 deletion ext/spl/php_spl_arginfo.h
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -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);
Expand All @@ -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[] = {
Expand All @@ -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
};
40 changes: 40 additions & 0 deletions ext/spl/tests/autoload_set_classmap_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
--TEST--
autoload_set_classmap Basic Operation
--FILE--
<?php

$classes = ['Example', 'Foo\NamespacedExample'];
$classes_map = [];

@mkdir(sys_get_temp_dir() . '/foo');

foreach ($classes as $class) {
$p = explode("\\", $class);
$class_name = array_pop($p);
$namespace = implode('\\', $p);

$class = strtolower($class);

$class_str = '<?php ' . ($namespace ? ('namespace ' . $namespace . ';') : '') . ' class ' . $class_name . '{ }';
$path = sys_get_temp_dir() . '/' . str_replace('\\', '/', $class) . '.php';
file_put_contents($path, $class_str);

$classes_map[$class] = $path;
}

autoload_set_classmap($classes_map);

echo get_debug_type(new Example()) . "\n";
echo get_debug_type(new Foo\NamespacedExample()) . "\n";

print_r(autoload_get_classmap());

?>
--EXPECTF--
Example
Foo\NamespacedExample
Array
(
[example] => %s/example.php
[foo\namespacedexample] => %s/namespacedexample.php
)
16 changes: 16 additions & 0 deletions ext/spl/tests/autoload_set_classmap_002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
autoload_set_classmap Reject non-string values
--FILE--
<?php

try {
autoload_set_classmap(['foo' => 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
20 changes: 20 additions & 0 deletions ext/spl/tests/autoload_set_classmap_003.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
--TEST--
autoload_set_classmap Throw error if mapped file did not contain class
--FILE--
<?php

$tmp_file = sys_get_temp_dir() . '/ThisFileIsEmpty.php';
file_put_contents($tmp_file, '<?php ');

autoload_set_classmap(['foo' => $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)
16 changes: 16 additions & 0 deletions ext/spl/tests/autoload_set_classmap_004.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
autoload_set_classmap Map can only be initialized once
--FILE--
<?php

try {
autoload_set_classmap(['foo' => '/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.