diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 10eaaa9734833..7ea59246b766b 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -102,6 +102,9 @@ PHPAPI zend_class_entry *reflection_fiber_ptr; #define _DO_THROW(msg) \ zend_throw_exception(reflection_exception_ptr, msg, 0); +#define _DO_THROW_EX(msg, ...) \ + zend_throw_exception_ex(reflection_exception_ptr, 0, msg, __VA_ARGS__); + #define GET_REFLECTION_OBJECT() do { \ intern = Z_REFLECTION_P(ZEND_THIS); \ if (intern->ptr == NULL) { \ @@ -1520,6 +1523,28 @@ static int get_parameter_default(zval *result, parameter_reference *param) { } } +static zend_long get_parameter_position(zend_function *func, zend_string* arg_name, uint32_t num_args) { + struct _zend_arg_info *arg_info = func->common.arg_info; + uint32_t i; + bool internal = has_internal_arg_info(func); + + for (i = 0; i < num_args; i++) { + if (arg_info[i].name) { + if (internal) { + if (strcmp(((zend_internal_arg_info*)arg_info)[i].name, ZSTR_VAL(arg_name)) == 0) { + return i; + } + } else { + if (zend_string_equals(arg_name, arg_info[i].name)) { + return i; + } + } + } + } + + return -1; +} + /* {{{ Preventing __clone from being called */ ZEND_METHOD(ReflectionClass, __clone) { @@ -2106,7 +2131,136 @@ ZEND_METHOD(ReflectionFunctionAbstract, getNumberOfRequiredParameters) } /* }}} */ -/* {{{ Returns an array of parameter objects for this function */ +/* {{{ Returns whether a parameter exists or not */ +ZEND_METHOD(ReflectionFunctionAbstract, hasParameter) +{ + reflection_object *intern; + zend_function *fptr; + zend_string *arg_name = NULL; + zend_long position; + uint32_t num_args; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR_OR_LONG(arg_name, position) + ZEND_PARSE_PARAMETERS_END(); + + GET_REFLECTION_OBJECT_PTR(fptr); + + num_args = fptr->common.num_args; + + if (fptr->common.fn_flags & ZEND_ACC_VARIADIC) { + num_args++; + } + + if (!num_args) { + RETURN_FALSE; + } + + if (arg_name != NULL) { + if (ZSTR_LEN(arg_name) == 0) { + zend_argument_value_error(1, "must not be empty"); + RETURN_THROWS(); + } + + if (get_parameter_position(fptr, arg_name, num_args) > -1) { + RETURN_TRUE; + } + + RETURN_FALSE; + } else { + if (position < 0) { + zend_argument_value_error(1, "must be greater than or equal to 0"); + RETURN_THROWS(); + } + + RETURN_BOOL(position < num_args); + } +} +/* }}} */ + +/* {{{ Returns a parameter specified by its name */ +ZEND_METHOD(ReflectionFunctionAbstract, getParameter) +{ + reflection_object *intern; + zend_function *fptr; + zend_string *arg_name = NULL; + zend_long position; + struct _zend_arg_info *arg_info; + uint32_t num_args; + zval reflection; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_STR_OR_LONG(arg_name, position) + ZEND_PARSE_PARAMETERS_END(); + + GET_REFLECTION_OBJECT_PTR(fptr); + + num_args = fptr->common.num_args; + arg_info = fptr->common.arg_info; + + if (fptr->common.fn_flags & ZEND_ACC_VARIADIC) { + num_args++; + } + + if (num_args < 1) { + if (fptr->common.scope) { + _DO_THROW_EX("Method %s::%s() has no parameters", ZSTR_VAL(fptr->common.scope->name), ZSTR_VAL(fptr->common.function_name)); + } else { + _DO_THROW_EX("Function %s() has no parameters", ZSTR_VAL(fptr->common.function_name)); + } + + RETURN_THROWS(); + } + + if (arg_name != NULL) { + if (ZSTR_LEN(arg_name) == 0) { + zend_argument_value_error(1, "must not be empty"); + RETURN_THROWS(); + } + + position = get_parameter_position(fptr, arg_name, num_args); + + if (position == -1) { + if (fptr->common.scope) { + _DO_THROW_EX("Method %s::%s() has no parameter named \"%s\"", + ZSTR_VAL(fptr->common.scope->name), ZSTR_VAL(fptr->common.function_name), ZSTR_VAL(arg_name)); + } else { + _DO_THROW_EX("Function %s() has no parameter named \"%s\"", + ZSTR_VAL(fptr->common.function_name), ZSTR_VAL(arg_name)); + } + RETURN_THROWS(); + } + } else { + if (position < 0) { + zend_argument_value_error(1, "must be greater than or equal to 0"); + RETURN_THROWS(); + } + if (position >= num_args) { + if (fptr->common.scope) { + _DO_THROW_EX("Method %s::%s() has no parameter at offset " ZEND_LONG_FMT, + ZSTR_VAL(fptr->common.scope->name), ZSTR_VAL(fptr->common.function_name), position); + } else { + _DO_THROW_EX("Function %s() has no parameter at offset " ZEND_LONG_FMT, + ZSTR_VAL(fptr->common.function_name), position); + } + RETURN_THROWS(); + } + } + + reflection_parameter_factory( + _copy_function(fptr), + Z_ISUNDEF(intern->obj) ? NULL : &intern->obj, + &arg_info[position], + position, + position < fptr->common.required_num_args, + &reflection + ); + + RETURN_OBJ(Z_OBJ(reflection)); +} +/* }}} */ + +/* {{{ Returns the function/method specified by its name */ ZEND_METHOD(ReflectionFunctionAbstract, getParameters) { reflection_object *intern; diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index 41768b6f61b20..9e44992493984 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -112,6 +112,10 @@ public function hasTentativeReturnType(): bool {} public function getTentativeReturnType(): ?ReflectionType {} public function getAttributes(?string $name = null, int $flags = 0): array {} + + public function hasParameter(int|string $param): bool; + + public function getParameter(int|string $param): ReflectionParameter; } class ReflectionFunction extends ReflectionFunctionAbstract diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index 141bc9ec14037..57d949faa466c 100644 --- a/ext/reflection/php_reflection_arginfo.h +++ b/ext/reflection/php_reflection_arginfo.h @@ -86,6 +86,14 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionFunctionAbstract ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, flags, IS_LONG, 0, "0") ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionFunctionAbstract_hasParameter, 0, 1, _IS_BOOL, 0) + ZEND_ARG_TYPE_MASK(0, param, MAY_BE_LONG|MAY_BE_STRING, NULL) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_ReflectionFunctionAbstract_getParameter, 0, 1, ReflectionParameter, 0) + ZEND_ARG_TYPE_MASK(0, param, MAY_BE_LONG|MAY_BE_STRING, NULL) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ReflectionFunction___construct, 0, 0, 1) ZEND_ARG_OBJ_TYPE_MASK(0, function, Closure, MAY_BE_STRING, NULL) ZEND_END_ARG_INFO() @@ -647,6 +655,8 @@ ZEND_METHOD(ReflectionFunctionAbstract, getReturnType); ZEND_METHOD(ReflectionFunctionAbstract, hasTentativeReturnType); ZEND_METHOD(ReflectionFunctionAbstract, getTentativeReturnType); ZEND_METHOD(ReflectionFunctionAbstract, getAttributes); +ZEND_METHOD(ReflectionFunctionAbstract, hasParameter); +ZEND_METHOD(ReflectionFunctionAbstract, getParameter); ZEND_METHOD(ReflectionFunction, __construct); ZEND_METHOD(ReflectionFunction, __toString); ZEND_METHOD(ReflectionFunction, isAnonymous); @@ -897,6 +907,8 @@ static const zend_function_entry class_ReflectionFunctionAbstract_methods[] = { ZEND_ME(ReflectionFunctionAbstract, hasTentativeReturnType, arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionFunctionAbstract, getTentativeReturnType, arginfo_class_ReflectionFunctionAbstract_getTentativeReturnType, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionFunctionAbstract, getAttributes, arginfo_class_ReflectionFunctionAbstract_getAttributes, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionFunctionAbstract, hasParameter, arginfo_class_ReflectionFunctionAbstract_hasParameter, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionFunctionAbstract, getParameter, arginfo_class_ReflectionFunctionAbstract_getParameter, ZEND_ACC_PUBLIC) ZEND_FE_END }; diff --git a/ext/reflection/tests/ReflectionFunction_getParameter_basic.phpt b/ext/reflection/tests/ReflectionFunction_getParameter_basic.phpt new file mode 100644 index 0000000000000..b06c60bfc4dfe --- /dev/null +++ b/ext/reflection/tests/ReflectionFunction_getParameter_basic.phpt @@ -0,0 +1,41 @@ +--TEST-- +ReflectionFunction::getParameter() +--FILE-- +getParameter('array')); +var_dump($function->getParameter('flags')); +var_dump($function->getParameter(0)); +var_dump($function->getParameter(1)); + +$function = new ReflectionFunction('foo'); +var_dump($function->getParameter('bar')); +var_dump($function->getParameter(0)); +?> +--EXPECT-- +object(ReflectionParameter)#2 (1) { + ["name"]=> + string(5) "array" +} +object(ReflectionParameter)#2 (1) { + ["name"]=> + string(5) "flags" +} +object(ReflectionParameter)#2 (1) { + ["name"]=> + string(5) "array" +} +object(ReflectionParameter)#2 (1) { + ["name"]=> + string(5) "flags" +} +object(ReflectionParameter)#1 (1) { + ["name"]=> + string(3) "bar" +} +object(ReflectionParameter)#1 (1) { + ["name"]=> + string(3) "bar" +} diff --git a/ext/reflection/tests/ReflectionFunction_getParameter_error.phpt b/ext/reflection/tests/ReflectionFunction_getParameter_error.phpt new file mode 100644 index 0000000000000..a5f239bbef478 --- /dev/null +++ b/ext/reflection/tests/ReflectionFunction_getParameter_error.phpt @@ -0,0 +1,63 @@ +--TEST-- +ReflectionFunction::getParameter() errors +--FILE-- +getParameter('Array')); +} catch(ReflectionException $e) { + print($e->getMessage() . PHP_EOL); +} + +try { + var_dump($function->getParameter(-1)); +} catch(ValueError $e) { + print($e->getMessage() . PHP_EOL); +} + +try { + var_dump($function->getParameter(3)); +} catch(ReflectionException $e) { + print($e->getMessage() . PHP_EOL); +} + +$function = new ReflectionFunction('foo'); + +try { + var_dump($function->getParameter('Bar')); +} catch(ReflectionException $e) { + print($e->getMessage() . PHP_EOL); +} + +try { + var_dump($function->getParameter(-1)); +} catch(ValueError $e) { + print($e->getMessage() . PHP_EOL); +} + +try { + var_dump($function->getParameter(1)); +} catch(ReflectionException $e) { + print($e->getMessage() . PHP_EOL); +} + +$function = new ReflectionFunction('noParams'); + +try { + var_dump($function->getParameter(1)); +} catch(ReflectionException $e) { + print($e->getMessage() . PHP_EOL); +} + +?> +--EXPECT-- +Function sort() has no parameter named "Array" +ReflectionFunctionAbstract::getParameter(): Argument #1 ($param) must be greater than or equal to 0 +Function sort() has no parameter at offset 3 +Function foo() has no parameter named "Bar" +ReflectionFunctionAbstract::getParameter(): Argument #1 ($param) must be greater than or equal to 0 +Function foo() has no parameter at offset 1 +Function noParams() has no parameters diff --git a/ext/reflection/tests/ReflectionFunction_hasParameter_basic.phpt b/ext/reflection/tests/ReflectionFunction_hasParameter_basic.phpt new file mode 100644 index 0000000000000..08a011918029e --- /dev/null +++ b/ext/reflection/tests/ReflectionFunction_hasParameter_basic.phpt @@ -0,0 +1,31 @@ +--TEST-- +ReflectionFunction::hasParameter() +--FILE-- +hasParameter('array')); +var_dump($function->hasParameter('Array')); +var_dump($function->hasParameter('string')); +var_dump($function->hasParameter(0)); +var_dump($function->hasParameter(1)); + +$function = new ReflectionFunction('foo'); +var_dump($function->hasParameter('bar')); +var_dump($function->hasParameter('Bar')); +var_dump($function->hasParameter('string')); +var_dump($function->hasParameter(0)); +var_dump($function->hasParameter(1)); +?> +--EXPECT-- +bool(true) +bool(false) +bool(false) +bool(true) +bool(true) +bool(true) +bool(false) +bool(false) +bool(true) +bool(false) diff --git a/ext/reflection/tests/ReflectionFunction_hasParameter_error.phpt b/ext/reflection/tests/ReflectionFunction_hasParameter_error.phpt new file mode 100644 index 0000000000000..88bf764114097 --- /dev/null +++ b/ext/reflection/tests/ReflectionFunction_hasParameter_error.phpt @@ -0,0 +1,25 @@ +--TEST-- +ReflectionFunction::hasParameter() errors +--FILE-- +hasParameter(-1)); +} catch (ValueError $e) { + print($e->getMessage() . PHP_EOL); +} + +$function = new ReflectionFunction('foo'); + +try { + var_dump($function->hasParameter(-1)); +} catch (ValueError $e) { + print($e->getMessage() . PHP_EOL); +} +?> +--EXPECT-- +ReflectionFunctionAbstract::hasParameter(): Argument #1 ($param) must be greater than or equal to 0 +ReflectionFunctionAbstract::hasParameter(): Argument #1 ($param) must be greater than or equal to 0 diff --git a/ext/reflection/tests/ReflectionMethod_getParameter_basic.phpt b/ext/reflection/tests/ReflectionMethod_getParameter_basic.phpt new file mode 100644 index 0000000000000..313f35629aed7 --- /dev/null +++ b/ext/reflection/tests/ReflectionMethod_getParameter_basic.phpt @@ -0,0 +1,33 @@ +--TEST-- +ReflectionMethod::getParameter() +--FILE-- +getParameter('object')); +var_dump($method->getParameter(0)); + +$method = new ReflectionMethod('C', 'foo'); +var_dump($method->getParameter('bar')); +var_dump($method->getParameter(0)); +?> +--EXPECT-- +object(ReflectionParameter)#2 (1) { + ["name"]=> + string(6) "object" +} +object(ReflectionParameter)#2 (1) { + ["name"]=> + string(6) "object" +} +object(ReflectionParameter)#1 (1) { + ["name"]=> + string(3) "bar" +} +object(ReflectionParameter)#1 (1) { + ["name"]=> + string(3) "bar" +} diff --git a/ext/reflection/tests/ReflectionMethod_getParameter_error.phpt b/ext/reflection/tests/ReflectionMethod_getParameter_error.phpt new file mode 100644 index 0000000000000..340a99cbe877d --- /dev/null +++ b/ext/reflection/tests/ReflectionMethod_getParameter_error.phpt @@ -0,0 +1,64 @@ +--TEST-- +ReflectionMethod::getParameter() errors +--FILE-- +getParameter('Object')); +} catch (ReflectionException $e) { + print($e->getMessage() . PHP_EOL); +} + +try { + var_dump($method->getParameter(-1)); +} catch (ValueError $e) { + print($e->getMessage() . PHP_EOL); +} + +try { + var_dump($method->getParameter(3)); +} catch (ReflectionException $e) { + print($e->getMessage() . PHP_EOL); +} + +class C { + public function foo(string $bar) {} +} + +$method = new ReflectionMethod('C', 'foo'); + +try { + var_dump($method->getParameter('Bar')); +} catch (ReflectionException $e) { + print($e->getMessage() . PHP_EOL); +} + +try { + var_dump($method->getParameter(-1)); +} catch (ValueError $e) { + print($e->getMessage() . PHP_EOL); +} + +try { + var_dump($method->getParameter(1)); +} catch (ReflectionException $e) { + print($e->getMessage() . PHP_EOL); +} + +$method = new ReflectionMethod('WeakReference', 'get'); + +try { + var_dump($method->getParameter(1)); +} catch (ReflectionException $e) { + print($e->getMessage() . PHP_EOL); +} +?> +--EXPECT-- +Method WeakReference::create() has no parameter named "Object" +ReflectionFunctionAbstract::getParameter(): Argument #1 ($param) must be greater than or equal to 0 +Method WeakReference::create() has no parameter at offset 3 +Method C::foo() has no parameter named "Bar" +ReflectionFunctionAbstract::getParameter(): Argument #1 ($param) must be greater than or equal to 0 +Method C::foo() has no parameter at offset 1 +Method WeakReference::get() has no parameters diff --git a/ext/reflection/tests/ReflectionMethod_hasParameter_basic.phpt b/ext/reflection/tests/ReflectionMethod_hasParameter_basic.phpt new file mode 100644 index 0000000000000..717eab2da38bc --- /dev/null +++ b/ext/reflection/tests/ReflectionMethod_hasParameter_basic.phpt @@ -0,0 +1,33 @@ +--TEST-- +ReflectionMethod::hasParameter() +--FILE-- +hasParameter('object')); +var_dump($method->hasParameter('Object')); +var_dump($method->hasParameter('string')); +var_dump($method->hasParameter(0)); +var_dump($method->hasParameter(1)); + +$method = new ReflectionMethod('C', 'foo'); +var_dump($method->hasParameter('bar')); +var_dump($method->hasParameter('Bar')); +var_dump($method->hasParameter('string')); +var_dump($method->hasParameter(0)); +var_dump($method->hasParameter(1)); +?> +--EXPECT-- +bool(true) +bool(false) +bool(false) +bool(true) +bool(false) +bool(true) +bool(false) +bool(false) +bool(true) +bool(false) diff --git a/ext/reflection/tests/ReflectionMethod_hasParameter_error.phpt b/ext/reflection/tests/ReflectionMethod_hasParameter_error.phpt new file mode 100644 index 0000000000000..4faaad1e8aeaa --- /dev/null +++ b/ext/reflection/tests/ReflectionMethod_hasParameter_error.phpt @@ -0,0 +1,27 @@ +--TEST-- +ReflectionMethod::hasParameter() +--FILE-- +hasParameter(-1)); +} catch (ValueError $e) { + print($e->getMessage() . PHP_EOL); +} + +$method = new ReflectionMethod('C', 'foo'); + +try { + var_dump($method->hasParameter(-1)); +} catch (ValueError $e) { + print($e->getMessage() . PHP_EOL); +} +?> +--EXPECT-- +ReflectionFunctionAbstract::hasParameter(): Argument #1 ($param) must be greater than or equal to 0 +ReflectionFunctionAbstract::hasParameter(): Argument #1 ($param) must be greater than or equal to 0