-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Add ReflectionFunctionAbstract::getParameter() and hasParameter() #10431
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
base: master
Are you sure you want to change the base?
Changes from all commits
87f8884
44c8db9
618cd84
c921efd
88392c0
479bec6
4d9cead
ba2a03d
eb9c500
cc3c7a4
4ace6d0
279a8da
2b975b0
0e0438d
60b962c
9a8f3a0
78b3d8d
3162d1d
29fb4ad
54a666b
bafb1ab
2984667
99ea19c
28bbb66
e9309a1
23aac25
47bbe4b
ddb9e30
01aa789
0a2f4e3
627c9f8
da0a602
8d389b9
ceda3c1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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"); | ||
ollieread marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Besides an empty string, additional null bytes should also checked the following way:
(code is copy pasted from ext/dom, but there are plenty of other examples as well) |
||
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\"", | ||
kocsismate marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wait, do these functions use zero index for parameters? I think it's not a good idea, since error messages use a 1-index ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah we had this discussion earlier. It might make sense to send an email to internals, but I also think using 1-indexed here is better. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, if we change that here @kocsismate we would probably want to change the return value of getPosition(), which I would imagine is going to break a lot of stuff. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My opinion is that we should use zero index to be consistent with getPosition and the parameter array. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I agree, as we're used to dealing with 0-index arrays. Shall I just continue as is for now, and then send something to the ML? What specifically should I be mentioning? This PR and the whole discussion over index vs position? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would prefer a discussion on the ML before anything is done here. And the discussion should be, here are two new helper methods which take either the name of a parameter or its position. However there is contention between should this be 1 indexed or 0 indexed due to the existing API. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Have started one on there @Girgias, sorry about the delay. |
||
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; | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
--TEST-- | ||
ReflectionFunction::getParameter() | ||
--FILE-- | ||
<?php | ||
function foo(string $bar) {} | ||
|
||
$function = new ReflectionFunction('sort'); | ||
var_dump($function->getParameter('array')); | ||
var_dump($function->getParameter('flags')); | ||
var_dump($function->getParameter(0)); | ||
var_dump($function->getParameter(1)); | ||
ollieread marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
$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" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
--TEST-- | ||
ReflectionFunction::getParameter() errors | ||
--FILE-- | ||
<?php | ||
$function = new ReflectionFunction('sort'); | ||
function foo(string $bar) {} | ||
function noParams() {} | ||
|
||
try { | ||
var_dump($function->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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
--TEST-- | ||
ReflectionFunction::hasParameter() | ||
--FILE-- | ||
<?php | ||
function foo(string $bar) {} | ||
|
||
$function = new ReflectionFunction('sort'); | ||
var_dump($function->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) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
--TEST-- | ||
ReflectionFunction::hasParameter() errors | ||
--FILE-- | ||
<?php | ||
function foo(string $bar) {} | ||
|
||
$function = new ReflectionFunction('sort'); | ||
|
||
try { | ||
var_dump($function->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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the null byte check should also be done here as well