Returning Values by ReferenceUsing the return construct to send values and variable references back from a function is all well and good, but sometimes you want to return multiple values from a function. You could use an array to do this, which we'll explore in Chapter 8, "Working with Arrays and Hashtables," or you can return values back through the parameter stack. Call-time Pass-by-ref
One of the simpler ways to pass
function sample_byref_calltime($a) {
$a .= ' (modified by ref!)';
}
$foo = 'I am a string';
sample_byref_calltime(&$foo);
echo $foo;
The ampersand (&) placed in the parameter call causes the actual zval used by $foo , rather than a copy of its contents, to be sent to the function. This allows the function to modify the value in place and effectively return information through its passed parameter. If sample_byref_calltime() hadn't been called with the ampersand placed in front of $foo , the changes made inside the function would not have affected the original variable. Repeating this endeavor in C requires nothing particularly special. Create the following function after sample_long() in your sample.c source file:
PHP_FUNCTION(sample_byref_calltime)
{
zval *a;
int addtl_len = sizeof(" (modified by ref!)") - 1;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &a) == FAILURE) {
RETURN_NULL();
}
if (!a->is_ref) {
/* parameter was not passed by reference,
* leave without doing anything
*/
return;
}
/* Make sure the variable is a string */
convert_to_string(a);
/* Enlarge a's buffer to hold the additional data */
Z_STRVAL_P(a) = erealloc(Z_STRVAL_P(a),
Z_STRLEN_P(a) + addtl_len + 1);
memcpy(Z_STRVAL_P(a) + Z_STRLEN_P(a),
" (modified by ref!)", addtl_len + 1);
Z_STRLEN_P(a) += addtl_len;
}
As always, this function needs to be added to the php_sample_functions structure:
PHP_FE(sample_byref_calltime, NULL) Compile-time Pass-by-refThe more common way to pass by reference is by using compile-time pass-by-ref. Here, the parameters to a function are declared to be for reference use only and attempts to pass constants or intermediate valuessuch as the result of a function callwill result in an error because there is nowhere for the function to store the resulting value back into. A userspace compile-time pass-by-ref function might look something like the following:
function sample_byref_compiletime(&$a) {
$a .= ' (modified by ref!)';
}
$foo = 'I am a string';
sample_byref_compiletime($foo);
echo $foo;
As you can see, this varies from the calltime version only in the placement of the referencing ampersand. When looking at this function in C, the implementation in terms of function code is entirely identical. The only true difference is in how it is declared in the php_sample_functions block:
PHP_FE(sample_byref_compiletime, php_sample_byref_arginfo) where php_sample_byref_arginfo is a (verbosely named) constant structure which you'll obviously need to define before this entry will compile.
Note The check for is_ref could actually be left out of the compile-time version because it will always failand not exitbut it causes no harm to leave it in for now.
In Zend Engine 1 (PHP4), this will be a simple
char*
list made up of a length byte followed by a set of flags pertaining to each of a function's parameters in
static unsigned char php_sample_byref_arginfo[] =
{ 1, BYREF_FORCE };
Here, the 1 indicates that the vector only contains argument info for one parameter. The argument-specific arg
Table 6.2. Zend Engine 1 Arg Info Constants
In Zend Engine 2 (PHP5+), you'll use a much more
First the arg info struct is declared using one of two macros. The simpler macro, ZEND_BEGIN_ARG_INFO() , takes two parameters:
ZEND_BEGIN_ARG_INFO(name, pass_rest_by_reference) name is quite simply how this struct will be referred to within the extension, in this case: php_sample_byref_arginfo . pass_rest_by_reference takes on the same meaning here as using BYREF_FORCE_REST as the last element of a Zend Engine 1 arg info vector. If this parameter is set to 1, all arguments not explicitly described within the struct will be assumed to be compile-time pass-by-ref arguments. The alternative begin macro, which introduces two new options not found in the Zend Engine 1 version, is ZEND_BEGIN_ARG_INFO_EX() :
ZEND_BEGIN_ARG_INFO_EX(name, pass_rest_by_reference, return_reference,
required_num_args)
name and pass_rest_by_reference have the same meanings here of course. return_reference , as you saw earlier in the chapter, gives a hint to Zend that your function will be overriding return_value_ptr with your own zval. The final argument, required_num_args , is another shortcut hint to Zend that allows it to skip certain function calls entirely when that function's prototype is known to be incompatible with how its being called. After you have a suitable begin macro in place, it may be followed by zero or more ZEND_ARG_*INFO elements. The types and usages of these macros are shown in Table 6.3. Lastly, all arg info structs using the Zend Engine 2 macros must terminate their list using ZEND_END_ARG_INFO() . For your sample function, you might select a final structure that looks like the following: Table 6.3. ZEND_ARG_INFO Family of Macros
ZEND_BEGIN_ARG_INFO(php_sample_byref_arginfo, 0)
ZEND_ARG_PASS_INFO(1)
ZEND_END_ARG_INFO()
In order to make extensions that are compatible with both ZE1 and ZE2, it's necessary to use an #ifdef statement and define the same arg_info structure for both, in this case:
#ifdef ZEND_ENGINE_2
static
ZEND_BEGIN_ARG_INFO(php_sample_byref_arginfo, 0)
ZEND_ARG_PASS_INFO(1)
ZEND_END_ARG_INFO()
#else /* ZE 1 */
static unsigned char php_sample_byref_arginfo[] =
{ 1, BYREF_FORCE };
#endif
Now that all the pieces are gathered together, it's time to create an actual compile-time pass-by-reference implementation. First let's put the block defining php_sample_byref_arginfo for ZE1 and ZE2 into the header file php_sample.h . Next, you could take two approaches: One approach would be to copy and paste the PHP_FUNCTION(sample_byref_calltime) implementation and rename it to PHP_FUNCTION(sample_byref_compiletime) , and then add a PHP_FE(sample_byref_compiletime, php_sample_byref_arginfo) line to php_sample_functions .
This approach is straightforward and probably less prone to confusion when making changes
This time, rather than making a duplicate of PHP_FUNCTION(sample_byref_calltime) , add a single line to php_sample_functions :
PHP_FALIAS(sample_byref_compiletime, sample_byref_calltime,
php_sample_byref_arginfo)
As you'll recall from Chapter 5, this creates a userspace function called sample_byref_compiletime() with an internal implementation using sample_byref_calltime() 's code. The addition of php_sample_byref_arginfo makes this version unique. |