Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make ZEND_BEGIN_ARG_INFO_EX control number of arguments, passed to a PHP extension?

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?

like image 410
Andrey Tserkus Avatar asked Jan 31 '13 21:01

Andrey Tserkus


1 Answers

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:

  • You'd have to use a handful of zend_fetch_class calls and class entries to type hint your object arguments.
  • It wouldn't enable reflection.
  • You wouldn't be able to declare arguments passed by reference.
  • It would obviously produce less clean code.

For obvious reasons, you'll want to make sure both your arginfo declaration and your zend_parse_parameters call matches.

like image 95
netcoder Avatar answered Sep 21 '22 03:09

netcoder