I work for a company that is using php and would like to contribute a few helper functions that we find useful. They are most helpful for anyone doing OO work (especially if using singletons) who isn't ready to move to php5. Two of the three are trivial to incorporate, but for the third I'm looking for some advice. I'm also not sure if I should get a cvs account for this stuff or if posting it here is sufficient inspiration for some good natured soul to get the functions into php. :) I'll start with the easy ones:
============ ref_id(mixed var) ============ ref_id returns a value that is the same for any variables that are references to eachother, but different for any variables that are not references to eachother. In the file system analogy, this corresponds to doing an "ls -i". In some cases it is useful to check if an object really is the object it is expected to be. A forgotten "&" can lead to hours of debugging that can be reduced with appropriate ref_id checks. Here is an example: <?php $x = 5; $y = 7; $z =& $x; if (ref_id($x) == ref_id($y)) { print "Changing x or y changes the other.\n"; } if (ref_id($x) == ref_id($z)) { print "Changing x or z changes the other.\n"; } ?> causes the output: Changing x or z changes the other. Here's the code: /* {{{ proto int ref_id(mixed var) * Returns the pointer of the variable */ PHP_FUNCTION(ref_id) { zval **arg; if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &arg) == FAILURE) { WRONG_PARAM_COUNT; } RETURN_LONG((int)*arg); } /* }}} */ PHP_FE(ref_id, first_arg_force_ref) ============ ref_count(mixed var) ============ ref_count returns the number of references for the given variable. In the file system analogy, this corresponds to the second column of the output of "ls -l". In some cases it is useful to check if an object really is the object it is expected to be. A forgotten "&" can lead to hours of debugging that can be reduced with appropriate ref_count checks. Here is an example: <?php function f($x, $y) { if (ref_count($x) > 1) { print "Changing x affects another var.\n"; } else { print "x is strictly local.\n"; } if (ref_count($y) > 1) { print "Changing y affects another var.\n"; } else { print "y is strictly local.\n"; } } $x = 5; $y = 7; f($x, &$y); ?> causes the output: x is strictly local. Changing y affects another var. (Ordinarily, the "&" would appear in the parameter list to ensure a reference is passed, but in certain situations it is preferable to place the "&" in the calling code.) Here's the code: /* {{{ proto int ref_count(mixed var) * Returns the number of references to the variable */ PHP_FUNCTION(ref_count) { zval **arg; if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &arg) == FAILURE) { WRONG_PARAM_COUNT; } /* "-1" because a reference is created just to call this function */ RETURN_LONG(ZVAL_REFCOUNT(*arg)-1); } /* }}} */ PHP_FE(ref_count, first_arg_force_ref) ============ (dis)allow_copies(mixed var) ============ When the design calls for a singleton, the design calls for an asserting copy constructor. It can take hours to discover that the reason a singleton is returning strange values is that there are accidentally multiple instances of the singleton class. The bug may be a missing "&" in what should be "$singleton =& getSingleton();" or the bug may be a missing "&" in the declaration or calling of a function. The ill effects are often seen in a largely unrelated piece of code. In C++, the answer is to declare a copy constructor and leave it unimplemented. In php, disallow_copies($x) makes the following code an error: "$y = $x;". Likewise "f($x)" where f does not take a reference would be an error. For completeness, allow_copies($x) returns a variable to "normal", ie "$y = $x" is OK again. Here is the code, but see below for a major issue. /* {{{ proto void allow_copies(mixed var) * Causes php to not generate an error if the script later tries to copy * the variable */ PHP_FUNCTION(allow_copies) { zval **arg; if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &arg) == FAILURE) { WRONG_PARAM_COUNT; } ZVAL_ALLOW_COPIES(*arg) = 1; } /* }}} */ PHP_FE(allow_copies, first_arg_force_ref) /* {{{ proto void disallow_copies(mixed var) * Causes php to generate an error if the script later tries to copy * the variable */ PHP_FUNCTION(disallow_copies) { zval **arg; if (ZEND_NUM_ARGS() != 1 || zend_get_parameters_ex(1, &arg) == FAILURE) { WRONG_PARAM_COUNT; } ZVAL_ALLOW_COPIES(*arg) = 0; } /* }}} */ PHP_FE(disallow_copies, first_arg_force_ref) In zend.h: struct _zval_struct { /* Variable information */ zvalue_value value; /* value */ zend_uchar type; /* active type */ zend_uchar is_ref; zend_ushort refcount; zend_bool allow_copies; /* NEW NEW NEW */ }; #define ZVAL_ALLOW_COPIES(pz) ((pz)->allow_copies) #define INIT_PZVAL(z) \ (z)->allow_copies = 1; /* NEW NEW NEW */ \ (z)->refcount = 1; \ (z)->is_ref = 0; In zend.c: zval_used_for_init.is_ref = 0; zval_used_for_init.refcount = 1; zval_used_for_init.allow_copies = 1; /* NEW NEW NEW */ zval_used_for_init.type = IS_NULL; In zend_variable.c: ZEND_API int _zval_copy_ctor(zval *zvalue ZEND_FILE_LINE_DC) { /* This is a notice instead of error because of the issue discussed below */ if (!zvalue->allow_copies) { zend_error(E_NOTICE, "Cannot copy value for which allow_copies is false"); } ... Whew! This email is a lot longer than I realized it would be and I'm only just now getting to the problem. Hopefully, at least a couple of people are reading this part. "In theory," the code above should be a complete implementation of the feature. In practice, I found that a lot of the code in php doesn't initialize the variables used. That may be fine for a particular piece of code that only relies on a couple of members of the struct, but it makes it hard to add functionality later. Here's an example from my notes of part of my effort to fix this: Replace any call to ALLOC_ZVAL(z) that is not immediately followed by a line of the form "*z = whatever" or INIT_PZVAL(z) with ALLOC_INIT_ZVAL(z). vi `find . -name "*.[hc]" | xargs grep -l 'ALLOC_ZVAL'` That helped and so did initializing the 5 cases of bare zend_constant variables and the many bare znode variables, but eventually I gave up tracking down every last piece of code that lacked initialization. If I was on the tip with a cvs account, I would have at least felt it was worthwhile. (Who knows; maybe uninitialized values are contributing to bugs in php...) As it was, it was a throw-away effort. My boss suggested keeping a separate table instead of adding to the zval struct and just ignoring the cases in which an unitialized zval is looked for in the table. We agreed that isn't as clean and has worse performance. We also agreed that the best course is to write to the php development community for guidance. So... any thoughts? Thanks! Todd Ruth -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: http://www.php.net/unsub.php