I'm developing a PHP extension, using C. So far I'm working on the proper validation of arguments, passed to the extension's function from PHP userspace.
The macro ZEND_BEGIN_ARG_INFO_EX
can be used to provide Zend Engine with information about the function's arguments. The 4th parameter of the macro, named as required_num_args
, let the engine automatically control the number of arguments, removing this hassle from me. However, I couldn't find the way to make it work: the engine always runs the extension's function without any warnings, even if a PHP script doesn't pass there enough arguments.
Here is my definition of function arguments:
ZEND_BEGIN_ARG_INFO_EX(test_func_swt_arginfo, 0, 0, 3)
ZEND_ARG_INFO(1, firstArg)
ZEND_ARG_ARRAY_INFO(0, secondArg, true)
ZEND_ARG_OBJ_INFO(1, thirdArg, SomeClass, false)
ZEND_END_ARG_INFO()
Here is my definition of the functions, exported by the PHP extension:
static const zend_function_entry test_func_functions[] = {
PHP_FE(sample_with_types, test_func_swt_arginfo)
PHP_FE_END
};
Here is my function:
PHP_FUNCTION(sample_with_types)
{
RETURN_TRUE;
}
Here is the PHP script I run:
<?php
sample_with_types();
Expected result: PHP shows error/warning/exception, something like "not enough arguments are passed to the function"; the function doesn't execute.
Actual result: the function executes and returns true
.
How can I properly configure the function arguments structure, so that Zend Engine check the number of arguments automatically? Or do I mistake the purpose of required_num_args
argument in ZEND_BEGIN_ARG_INFO_EX
macro?
As far as I know, this is not what ZEND_BEGIN_ARG_INFO_EX
is for.
ZEND_BEGIN_ARG_INFO_EX
is a PHP 5 addition is used for producing cleaner code, enabling type hinting, pass-by-reference and reflection. Consider the following arginfo declarations for your actual function that just returns true:
ZEND_BEGIN_ARG_INFO_EX(arginfo_test, 0, 0, 3)
ZEND_ARG_INFO(0, firstArg)
ZEND_ARG_OBJ_INFO(0, objNonNull, stdClass, 0)
ZEND_ARG_OBJ_INFO(0, obj, stdClass, 1)
ZEND_ARG_OBJ_INFO(1, objByRef, stdClass, 1)
ZEND_END_ARG_INFO()
It has the following effect:
sample_with_types(); // ok
sample_with_types(1, null); // error: arg #2 should be stdClass
sample_with_types(1, new stdClass, null); // ok
sample_with_types(1, new stdClass, 1); // error: arg #3 should be stdClass
sample_with_types(1, new stdClass, null, 2); // error: arg #4 must be reference
Additionally, it provides reflection capabilities to your function:
$ref = new ReflectionFunction('sample_with_types');
var_dump($ref->getParameters());
...giving output similar to:
array(4) {
[0]=>
&object(ReflectionParameter)#2 (1) {
["name"]=>
string(8) "firstArg"
}
[1]=>
&object(ReflectionParameter)#3 (1) {
["name"]=>
string(10) "objNonNull"
}
[2]=>
&object(ReflectionParameter)#4 (1) {
["name"]=>
string(3) "obj"
}
[3]=>
&object(ReflectionParameter)#5 (1) {
["name"]=>
string(8) "objByRef"
}
}
If you omit the arginfo, ReflectionFunction::getParameters()
returns an empty array instead.
The required_num_args
macro parameter is used specifically for reflection, and denotes how many parameters will be marked required when reflecting the function.
If you need to make the arguments required and not just mark them as required when using reflection, you still have to use zend_parse_parameters
, which in most cases, you will still need to get the actual values of the arguments:
PHP_FUNCTION(sample_with_types)
{
long arg1;
zval *arg2 = NULL, *arg3 = NULL, *arg4 = NULL;
zend_class_entry ce2, ce3, ce4;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "looo", &arg1,
&arg2, &ce2, &arg3, &ce3, &arg4, &ce4) == FAILURE)
{
return;
}
RETURN_TRUE;
}
Note how I used "looo"
(generic object types) and not "lOO!O!"
(specific object types with null specifiers) in the above. The type hinting already has been specified with arginfo, so there's no need to do it twice.
So, without arginfo:
zend_fetch_class
calls and class entries to type hint your object arguments.For obvious reasons, you'll want to make sure both your arginfo declaration and your zend_parse_parameters
call matches.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With