Automatic Type Conversion with zend_parse_parameters()

As with return values, which you saw last chapter, parameter values are moved around using indirect zval references. The easiest way to get at these zval* values is using the zend_parse_parameters() function.

Calls to zend_parse_parameters() almost invariably begin with the ZEND_NUM_ARGS() macro followed by the ubiquitous TSRMLS_CC. ZEND_NUM_ARGS(), as its name suggests, returns an int representing the number of arguments actually passed to the function. Because of the way zend_parse_parameters() works internally, you'll probably never need to inspect this value directly, so just pass it on for now.

The next parameter to zend_parse_parameters() is the format parameter, which is made up of a string of letters or character sequences corresponding to the various primitive types supported by the Zend Engine. Table 7.1 shows the basic type characters.

Table 7.1. zend_parse_parameters() Type Specifiers

Type Specifier

Userspace Datatype

b

Boolean

l

Integer

d

Floating point

s

String

r

Resource

a

Array

o

Object instance

O

Object instance of a specified type

z

Non-specific zval

Z

Dereferenced non-specific zval

The remaining parameters to ZPP depend on which specific type you've requested in your format string. For the simpler types, this is a dereferenced C language primitive. For example, a long data type is extracted like such:

Although it's common for integers and longs to have the same data storage size, they cannot always be used interchangeably. Attempting to dereference an int data type into a long* parameter can lead to unexpected results, especially as 64-bit platforms become more prevalent. Always use the appropriate data type(s) as listed in Table 7.2.

Table 7.2. zend_parse_parameters() Data Types

Type specifier

C datatype(s)

b

zend_bool

l

long

d

double

s

char*, int

r

zval*

a

zval*

o

zval*

O

zval*, zend_class_entry*

z

zval*

Z

zval**

Notice that all the more complex data types actually parse out as simple zvals. For the most part this is due to the same limitation that prevents returning complex data types using RETURN_*() macros: There's really no C-space analog to these structures. What ZPP does do for your function, however, is ensure that the zval* you do receive is of the appropriate type. If necessary, it will even perform implicit conversions such as casting arrays to stdClass objects.

The s and O types are also worth pointing out because they require a pair of parameters for each invocation. You'll see O more closely when you explore the Object data type in Chapters 10, "PHP4 Objects," and 11, "PHP5 Objects." In the case of the s type, let's say you're extending the sample_hello_world() function from Chapter 5, "Your First Extension," to greet a specific person by name:

The zend_parse_parameters() function may fail due to the function being passed to too few arguments to satisfy the format string or because one of the arguments passed simply cannot be converted to the requested type. In such a case, it will automatically output an error message so your extension doesn't have to.

To request more than one parameter, extend the format specifier to include additional characters and stack the subsequent arguments onto the zend_parse_parameters() call. Parameters are parsed left to right just as they are in a userspace function declaration:

with the default argument being used when none is explicitly given. In a C implementation, optional parameters are specified in a similar manner.

To accomplish this, use the pipe character (|) in zend_parse_parameters()'s format string. Arguments to the left of the pipe will parsed from the call stackif possiblewhile any argument on the right that isn't provided will be left unmodified. For example:

Because optional parameters are not modified from their initial values unless they're provided as arguments, it's important to initialize any parameters to some default value. In most cases this will be NULL/0, though sometimesas aboveanother default is sensible.

IS_NULL Versus NULL

Every zval, even the ultra-simple IS_NULL data type, occupies a certain minimal amount of memory overhead. Beyond that, it takes a certain number of clock cycles to allocate that memory space, initialize the values, and then ultimately free it when it's deemed no longer useful.

For many functions, it makes no sense to go through this process only to find out that the parameter was flagged as unimportant by the calling scope through the use of a NULL argument. Fortunately zend_parse_parameters() allows arguments to be flagged as "NULL permissible" by appending an exclamation point to their format specifier. Consider the following two code fragments, one with the modifier and one without:

These two versions really aren't so different code wise, though the former uses nominally more processor time. In general, this feature won't be very useful, but it's good to know it's available.

Forced Separation

When a variable is passed into a function, whether by reference or not, its refcount is almost always at least 2; one reference for the variable itself, and another for the copy that was passed into the function. Before making changes to that zval (if acting on the zval directly), it's important to separate it from any non-reference set it may be part of.

This would be a tedious task were it not for the / format specifier, which automatically separates any copy-on-write referenced variable so that your function can do as it pleases. Like the NULL flag, this modifier goes after the type it means to impact. Also like the NULL flag, you won't know you need this feature until you actually have a use for it.

zend_get_arguments()

If you happen to be designing your code to work on very old versions of PHP, or you just have a function that never needs anything other than zval*s, you might consider using the zend_get_parameters() API call.

The zend_get_parameters() call differs from its newer parse counterpart in a few crucial ways. First, it performs no automatic type conversion; instead all arguments are extracted as primitive zval* data types. The simplest use of zend_get_parameters() might be something like the following:

Second, as you can see from the manually applied error message, zend_get_parameters() does not output error text on failure. It's also very poor at handling optional parameters. Specifically, if you ask it to fetch four arguments, you had better be certain that at least four arguments were provided or it will return FAILURE.

Lastly, unlike parse, this specific get variant will automatically separate any copy-on-write reference sets. If you still wanted to skip automatic separation, you could use its sibling: zend_get_parameters_ex().

In addition to not separating copy-on-write reference sets, zend_get_parameters_ex() differs in that it returns zval** pointers rather than simply zval*. Though that distinction is probably one you won't know you need until you have cause to use it, its usage is ultimately quite similar:

Notice that the _ex version does not require the ZEND_NUM_ARGS() parameter. This is due to the _ex version being added at a later time when changes to the Zend Engine made this parameter unnecessary.

In this example, you also used the WRONG_PARAM_COUNT macro, which handles displaying an E_WARNING error message and automatically leaving the function.

Handling Arbitrary Numbers of Arguments

Two more members of the zend_get_parameter family exist for extracting a set of zval* and zval** pointers in situations where either the number of parameters is prohibitively large, or will not actually be known until runtime.

Consider the var_dump() function, which will display the contents of an arbitrary number of variables passed to it:

Here, var_dump() pre-allocates a vector of zval** pointers based on the count of parameters passed into the function. It then uses zend_get_parameters_array_ex() to populate that vector in a single shot. As you can probably guess, another version of this function exists as zend_get_parameters_array() with a similar set of differences: auto-separation, zval* instead of zval**, and required ZEND_NUM_ARGS() in the first parameter.