helly Mon Nov 24 15:57:55 2003 EDT Added files: /php-src/tests/classes array_access_001.phpt array_access_002.phpt array_access_003.phpt array_access_004.phpt
Modified files: /ZendEngine2 zend_execute.c zend_interfaces.c zend_interfaces.h zend_operators.c zend_operators.h zend_object_handlers.c Log: Add new interface ArrayAccess to use objects as Arrays
Index: ZendEngine2/zend_execute.c diff -u ZendEngine2/zend_execute.c:1.552 ZendEngine2/zend_execute.c:1.553 --- ZendEngine2/zend_execute.c:1.552 Mon Nov 24 13:13:29 2003 +++ ZendEngine2/zend_execute.c Mon Nov 24 15:57:53 2003 @@ -17,7 +17,7 @@ +----------------------------------------------------------------------+ */ -/* $Id: zend_execute.c,v 1.552 2003/11/24 18:13:29 andi Exp $ */ +/* $Id: zend_execute.c,v 1.553 2003/11/24 20:57:53 helly Exp $ */ #define ZEND_INTENSIVE_DEBUGGING 0 @@ -920,13 +920,17 @@ } break; case IS_OBJECT: - if (type == BP_VAR_R) { + if (type == BP_VAR_R || type == BP_VAR_RW) { if (!Z_OBJ_HT_P(container)->read_dimension) { zend_error(E_ERROR, "Cannot use object as array"); } else { zval *dim = get_zval_ptr(op2, Ts, &EG(free_op2), BP_VAR_R); zval *overloaded_result = Z_OBJ_HT_P(container)->read_dimension(container, dim TSRMLS_CC); + if (type == BP_VAR_RW && !overloaded_result->is_ref) { + zend_error(E_ERROR, "Objects used as arrays in post/pre increment/decrement must return values by reference"); + } + *retval = &overloaded_result; AI_USE_PTR(T(result->u.var).var); FREE_OP(Ts, op2, EG(free_op2)); Index: ZendEngine2/zend_interfaces.c diff -u ZendEngine2/zend_interfaces.c:1.8 ZendEngine2/zend_interfaces.c:1.9 --- ZendEngine2/zend_interfaces.c:1.8 Mon Nov 17 19:18:48 2003 +++ ZendEngine2/zend_interfaces.c Mon Nov 24 15:57:53 2003 @@ -16,7 +16,7 @@ +----------------------------------------------------------------------+ */ -/* $Id: zend_interfaces.c,v 1.8 2003/11/18 00:18:48 helly Exp $ */ +/* $Id: zend_interfaces.c,v 1.9 2003/11/24 20:57:53 helly Exp $ */ #include "zend.h" #include "zend_API.h" @@ -25,6 +25,7 @@ zend_class_entry *zend_ce_traversable; zend_class_entry *zend_ce_aggregate; zend_class_entry *zend_ce_iterator; +zend_class_entry *zend_ce_arrayaccess; /* {{{ zend_call_method Only returns the returned zval if retval_ptr != NULL */ @@ -344,6 +345,13 @@ } /* }}} */ +/* {{{ zend_implement_arrayaccess */ +static int zend_implement_arrayaccess(zend_class_entry *interface, zend_class_entry *class_type TSRMLS_DC) +{ + return SUCCESS; +} +/* }}}*/ + /* {{{ function tables */ zend_function_entry zend_funcs_aggregate[] = { ZEND_ABSTRACT_ME(iterator, getIterator, NULL) @@ -360,6 +368,26 @@ }; zend_function_entry *zend_funcs_traversable = NULL; + +static +ZEND_BEGIN_ARG_INFO(arginfo_arrayaccess_offset, 0) + ZEND_ARG_INFO(0, offset) +ZEND_END_ARG_INFO(); + +static +ZEND_BEGIN_ARG_INFO(arginfo_arrayaccess_offset_value, 0) + ZEND_ARG_INFO(0, offset) + ZEND_ARG_INFO(0, value) +ZEND_END_ARG_INFO(); + +zend_function_entry zend_funcs_arrayaccess[] = { + ZEND_ABSTRACT_ME(arrayaccess, offsetExists, arginfo_arrayaccess_offset) + ZEND_ABSTRACT_ME(arrayaccess, offsetGet, arginfo_arrayaccess_offset) + ZEND_ABSTRACT_ME(arrayaccess, offsetSet, arginfo_arrayaccess_offset_value) + ZEND_ABSTRACT_ME(arrayaccess, offsetUnset, arginfo_arrayaccess_offset) + {NULL, NULL, NULL} +}; + /* }}} */ #define REGISTER_ITERATOR_INTERFACE(class_name, class_name_str) \ @@ -383,6 +411,8 @@ REGISTER_ITERATOR_INTERFACE(iterator, Iterator); REGISTER_ITERATOR_IMPLEMENT(iterator, traversable); + + REGISTER_ITERATOR_INTERFACE(arrayaccess, ArrayAccess); } /* }}} */ Index: ZendEngine2/zend_interfaces.h diff -u ZendEngine2/zend_interfaces.h:1.2 ZendEngine2/zend_interfaces.h:1.3 --- ZendEngine2/zend_interfaces.h:1.2 Fri Nov 7 04:33:06 2003 +++ ZendEngine2/zend_interfaces.h Mon Nov 24 15:57:53 2003 @@ -16,7 +16,7 @@ +----------------------------------------------------------------------+ */ -/* $Id: zend_interfaces.h,v 1.2 2003/11/07 09:33:06 helly Exp $ */ +/* $Id: zend_interfaces.h,v 1.3 2003/11/24 20:57:53 helly Exp $ */ #include "zend.h" #include "zend_API.h" @@ -24,6 +24,7 @@ ZEND_API zend_class_entry *zend_ce_traversable; ZEND_API zend_class_entry *zend_ce_aggregate; ZEND_API zend_class_entry *zend_ce_iterator; +ZEND_API zend_class_entry *zend_ce_arrayaccess; ZEND_API zval* zend_call_method(zval **object_pp, zend_class_entry *obj_ce, zend_function **fn_proxy, char *function_name, int function_name_len, zval **retval_ptr_ptr, int param_count, zval* arg1, zval* arg2 TSRMLS_DC); Index: ZendEngine2/zend_operators.c diff -u ZendEngine2/zend_operators.c:1.167 ZendEngine2/zend_operators.c:1.168 --- ZendEngine2/zend_operators.c:1.167 Thu Nov 13 15:53:55 2003 +++ ZendEngine2/zend_operators.c Mon Nov 24 15:57:53 2003 @@ -17,7 +17,7 @@ +----------------------------------------------------------------------+ */ -/* $Id: zend_operators.c,v 1.167 2003/11/13 20:53:55 moriyoshi Exp $ */ +/* $Id: zend_operators.c,v 1.168 2003/11/24 20:57:53 helly Exp $ */ #include <ctype.h> @@ -1410,7 +1410,7 @@ } -ZEND_API zend_bool instanceof_function(zend_class_entry *instance_ce, zend_class_entry *ce TSRMLS_DC) +ZEND_API zend_bool instanceof_function_ex(zend_class_entry *instance_ce, zend_class_entry *ce, zend_bool interfaces_only TSRMLS_DC) { zend_uint i; @@ -1419,16 +1419,23 @@ return 1; } } - while (instance_ce) { - if (instance_ce == ce) { - return 1; + if (!interfaces_only) { + while (instance_ce) { + if (instance_ce == ce) { + return 1; + } + instance_ce = instance_ce->parent; } - instance_ce = instance_ce->parent; } return 0; } +ZEND_API zend_bool instanceof_function(zend_class_entry *instance_ce, zend_class_entry *ce TSRMLS_DC) +{ + return instanceof_function_ex(instance_ce, ce, 0 TSRMLS_CC); +} + #define LOWER_CASE 1 #define UPPER_CASE 2 #define NUMERIC 3 Index: ZendEngine2/zend_operators.h diff -u ZendEngine2/zend_operators.h:1.81 ZendEngine2/zend_operators.h:1.82 --- ZendEngine2/zend_operators.h:1.81 Wed Oct 29 12:45:56 2003 +++ ZendEngine2/zend_operators.h Mon Nov 24 15:57:54 2003 @@ -17,7 +17,7 @@ +----------------------------------------------------------------------+ */ -/* $Id: zend_operators.h,v 1.81 2003/10/29 17:45:56 moriyoshi Exp $ */ +/* $Id: zend_operators.h,v 1.82 2003/11/24 20:57:54 helly Exp $ */ #ifndef ZEND_OPERATORS_H #define ZEND_OPERATORS_H @@ -59,6 +59,7 @@ ZEND_API int is_smaller_function(zval *result, zval *op1, zval *op2 TSRMLS_DC); ZEND_API int is_smaller_or_equal_function(zval *result, zval *op1, zval *op2 TSRMLS_DC); +ZEND_API zend_bool instanceof_function_ex(zend_class_entry *instance_ce, zend_class_entry *ce, zend_bool interfaces_only TSRMLS_DC); ZEND_API zend_bool instanceof_function(zend_class_entry *instance_ce, zend_class_entry *ce TSRMLS_DC); static inline zend_bool is_numeric_string(char *str, int length, long *lval, double *dval, zend_bool allow_errors) Index: ZendEngine2/zend_object_handlers.c diff -u ZendEngine2/zend_object_handlers.c:1.76 ZendEngine2/zend_object_handlers.c:1.77 --- ZendEngine2/zend_object_handlers.c:1.76 Mon Nov 10 15:44:50 2003 +++ ZendEngine2/zend_object_handlers.c Mon Nov 24 15:57:54 2003 @@ -17,7 +17,7 @@ +----------------------------------------------------------------------+ */ -/* $Id: zend_object_handlers.c,v 1.76 2003/11/10 20:44:50 helly Exp $ */ +/* $Id: zend_object_handlers.c,v 1.77 2003/11/24 20:57:54 helly Exp $ */ #include "zend.h" #include "zend_globals.h" @@ -26,6 +26,7 @@ #include "zend_objects.h" #include "zend_objects_API.h" #include "zend_object_handlers.h" +#include "zend_interfaces.h" #define DEBUG_OBJECT_HANDLERS 0 @@ -352,44 +353,49 @@ zval *zend_std_read_dimension(zval *object, zval *offset TSRMLS_DC) { -#if 1 - zend_error(E_ERROR, "Cannot use object as array"); -#else - zend_printf("Fetching from object: "); - zend_print_zval(object, 0); - - zend_printf("\n the offset: "); - zend_print_zval(offset, 0); - - zend_printf("\n"); -#endif - return EG(uninitialized_zval_ptr); + zend_class_entry *ce = Z_OBJCE_P(object); + zval *retval; + + if (instanceof_function_ex(ce, zend_ce_arrayaccess, 1 TSRMLS_CC)) { + zend_call_method_with_1_params(&object, ce, NULL, "offsetget", &retval, offset); + if (retval->refcount > 0) { /* Should always be the case */ + retval->refcount--; + } + return retval; + } else { + zend_error(E_ERROR, "Cannot use object of type %s as array", ce->name); + return 0; + } } static void zend_std_write_dimension(zval *object, zval *offset, zval *value TSRMLS_DC) { -#if 1 - zend_error(E_ERROR, "Cannot use object as array"); -#else - zend_printf("Assigning to object: "); - zend_print_zval(object, 0); - - zend_printf("\n with offset: "); - zend_print_zval(offset, 0); - - zend_printf("\n the value: "); - zend_print_zval(value, 0); - - zend_printf("\n"); -#endif + zend_class_entry *ce = Z_OBJCE_P(object); + + if (instanceof_function_ex(ce, zend_ce_arrayaccess, 1 TSRMLS_CC)) { + zend_call_method_with_2_params(&object, ce, NULL, "offsetset", NULL, offset, value); + } else { + zend_error(E_ERROR, "Cannot use object of type %s as array", ce->name); + } } static int zend_std_has_dimension(zval *object, zval *offset, int check_empty TSRMLS_DC) { - zend_error(E_ERROR, "Cannot use object as array"); - return 0; + zend_class_entry *ce = Z_OBJCE_P(object); + zval *retval; + int result; + + if (instanceof_function_ex(ce, zend_ce_arrayaccess, 1 TSRMLS_CC)) { + zend_call_method_with_1_params(&object, ce, NULL, "offsetexists", &retval, offset); + result = i_zend_is_true(retval); + zval_ptr_dtor(&retval); + return result; + } else { + zend_error(E_ERROR, "Cannot use object of type %s as array", ce->name); + return 0; + } } @@ -463,7 +469,15 @@ static void zend_std_unset_dimension(zval *object, zval *offset TSRMLS_DC) { - zend_error(E_ERROR, "Cannot use object as array"); + zend_class_entry *ce = Z_OBJCE_P(object); + zval *retval; + + if (instanceof_function_ex(ce, zend_ce_arrayaccess, 1 TSRMLS_CC)) { + zend_call_method_with_1_params(&object, ce, NULL, "offsetunset", &retval, offset); + zval_ptr_dtor(&retval); + } else { + zend_error(E_ERROR, "Cannot use object of type %s as array", ce->name); + } } Index: php-src/tests/classes/array_access_001.phpt +++ php-src/tests/classes/array_access_001.phpt --TEST-- ZE2 ArrayAccess --SKIPIF-- <?php if (!class_exists('ArrayAccess')) die('skip ArrayAccess not present'); ?> --FILE-- <?php class Object implements ArrayAccess { public $a = array('1st', 1, 2=>'3rd', '4th'=>4); function offsetExists($index) { echo __METHOD__ . "($index)\n"; return array_key_exists($index, $this->a); } function offsetGet($index) { echo __METHOD__ . "($index)\n"; return $this->a[$index]; } function offsetSet($index, $newval) { echo __METHOD__ . "($index,$newval)\n"; return $this->a[$index] = $newval; } function offsetUnset($index) { echo __METHOD__ . "($index)\n"; unset($this->a[$index]); } } $obj = new Object; var_dump($obj->a); echo "===EMPTY===\n"; var_dump(empty($obj[0])); var_dump(empty($obj[1])); var_dump(empty($obj[2])); var_dump(empty($obj['4th'])); var_dump(empty($obj['5th'])); var_dump(empty($obj[6])); echo "===isset===\n"; var_dump(isset($obj[0])); var_dump(isset($obj[1])); var_dump(isset($obj[2])); var_dump(isset($obj['4th'])); var_dump(isset($obj['5th'])); var_dump(isset($obj[6])); echo "===offsetGet===\n"; var_dump($obj[0]); var_dump($obj[1]); var_dump($obj[2]); var_dump($obj['4th']); var_dump($obj['5th']); var_dump($obj[6]); echo "===offsetSet===\n"; echo "WRITE 1\n"; $obj[1] = 'Changed 1'; var_dump($obj[1]); echo "WRITE 2\n"; $obj['4th'] = 'Changed 4th'; var_dump($obj['4th']); echo "WRITE 3\n"; $obj['5th'] = 'Added 5th'; var_dump($obj['5th']); echo "WRITE 4\n"; $obj[6] = 'Added 6'; var_dump($obj[6]); var_dump($obj[0]); var_dump($obj[2]); $x = $obj[6] = 'changed 6'; var_dump($obj[6]); var_dump($x); echo "===unset===\n"; var_dump($obj->a); unset($obj[2]); unset($obj['4th']); unset($obj[7]); unset($obj['8th']); var_dump($obj->a); ?> ===DONE=== --EXPECTF-- array(4) { [0]=> string(3) "1st" [1]=> int(1) [2]=> string(3) "3rd" ["4th"]=> int(4) } ===EMPTY=== object::offsetExists(0) bool(false) object::offsetExists(1) bool(false) object::offsetExists(2) bool(false) object::offsetExists(4th) bool(false) object::offsetExists(5th) bool(true) object::offsetExists(6) bool(true) ===isset=== object::offsetExists(0) bool(true) object::offsetExists(1) bool(true) object::offsetExists(2) bool(true) object::offsetExists(4th) bool(true) object::offsetExists(5th) bool(false) object::offsetExists(6) bool(false) ===offsetGet=== object::offsetGet(0) string(3) "1st" object::offsetGet(1) int(1) object::offsetGet(2) string(3) "3rd" object::offsetGet(4th) int(4) object::offsetGet(5th) Notice: Undefined index: 5th in %sarray_access_001.php on line %d NULL object::offsetGet(6) Notice: Undefined offset: 6 in %sarray_access_001.php on line %d NULL ===offsetSet=== WRITE 1 object::offsetSet(1,Changed 1) object::offsetGet(1) string(9) "Changed 1" WRITE 2 object::offsetSet(4th,Changed 4th) object::offsetGet(4th) string(11) "Changed 4th" WRITE 3 object::offsetSet(5th,Added 5th) object::offsetGet(5th) string(9) "Added 5th" WRITE 4 object::offsetSet(6,Added 6) object::offsetGet(6) string(7) "Added 6" object::offsetGet(0) string(3) "1st" object::offsetGet(2) string(3) "3rd" object::offsetSet(6,changed 6) object::offsetGet(6) string(9) "changed 6" string(9) "changed 6" ===unset=== array(6) { [0]=> string(3) "1st" [1]=> string(9) "Changed 1" [2]=> string(3) "3rd" ["4th"]=> string(11) "Changed 4th" ["5th"]=> string(9) "Added 5th" [6]=> string(9) "changed 6" } object::offsetUnset(2) object::offsetUnset(4th) object::offsetUnset(7) object::offsetUnset(8th) array(4) { [0]=> string(3) "1st" [1]=> string(9) "Changed 1" ["5th"]=> string(9) "Added 5th" [6]=> string(9) "changed 6" } ===DONE=== Index: php-src/tests/classes/array_access_002.phpt +++ php-src/tests/classes/array_access_002.phpt --TEST-- ZE2 ArrayAccess::offsetSet without return --SKIPIF-- <?php if (!class_exists('ArrayAccess')) die('skip ArrayAccess not present'); ?> --FILE-- <?php class Object implements ArrayAccess { public $a = array('1st', 1, 2=>'3rd', '4th'=>4); function offsetExists($index) { echo __METHOD__ . "($index)\n"; return array_key_exists($index, $this->a); } function offsetGet($index) { echo __METHOD__ . "($index)\n"; return $this->a[$index]; } function offsetSet($index, $newval) { echo __METHOD__ . "($index,$newval)\n"; /*return*/ $this->a[$index] = $newval; } function offsetUnset($index) { echo __METHOD__ . "($index)\n"; unset($this->a[$index]); } } $obj = new Object; var_dump($obj->a); echo "===EMPTY===\n"; var_dump(empty($obj[0])); var_dump(empty($obj[1])); var_dump(empty($obj[2])); var_dump(empty($obj['4th'])); var_dump(empty($obj['5th'])); var_dump(empty($obj[6])); echo "===isset===\n"; var_dump(isset($obj[0])); var_dump(isset($obj[1])); var_dump(isset($obj[2])); var_dump(isset($obj['4th'])); var_dump(isset($obj['5th'])); var_dump(isset($obj[6])); echo "===offsetGet===\n"; var_dump($obj[0]); var_dump($obj[1]); var_dump($obj[2]); var_dump($obj['4th']); var_dump($obj['5th']); var_dump($obj[6]); echo "===offsetSet===\n"; echo "WRITE 1\n"; $obj[1] = 'Changed 1'; var_dump($obj[1]); echo "WRITE 2\n"; $obj['4th'] = 'Changed 4th'; var_dump($obj['4th']); echo "WRITE 3\n"; $obj['5th'] = 'Added 5th'; var_dump($obj['5th']); echo "WRITE 4\n"; $obj[6] = 'Added 6'; var_dump($obj[6]); var_dump($obj[0]); var_dump($obj[2]); $x = $obj[6] = 'changed 6'; var_dump($obj[6]); var_dump($x); echo "===unset===\n"; var_dump($obj->a); unset($obj[2]); unset($obj['4th']); unset($obj[7]); unset($obj['8th']); var_dump($obj->a); ?> ===DONE=== --EXPECTF-- array(4) { [0]=> string(3) "1st" [1]=> int(1) [2]=> string(3) "3rd" ["4th"]=> int(4) } ===EMPTY=== object::offsetExists(0) bool(false) object::offsetExists(1) bool(false) object::offsetExists(2) bool(false) object::offsetExists(4th) bool(false) object::offsetExists(5th) bool(true) object::offsetExists(6) bool(true) ===isset=== object::offsetExists(0) bool(true) object::offsetExists(1) bool(true) object::offsetExists(2) bool(true) object::offsetExists(4th) bool(true) object::offsetExists(5th) bool(false) object::offsetExists(6) bool(false) ===offsetGet=== object::offsetGet(0) string(3) "1st" object::offsetGet(1) int(1) object::offsetGet(2) string(3) "3rd" object::offsetGet(4th) int(4) object::offsetGet(5th) Notice: Undefined index: 5th in %sarray_access_002.php on line %d NULL object::offsetGet(6) Notice: Undefined offset: 6 in %sarray_access_002.php on line %d NULL ===offsetSet=== WRITE 1 object::offsetSet(1,Changed 1) object::offsetGet(1) string(9) "Changed 1" WRITE 2 object::offsetSet(4th,Changed 4th) object::offsetGet(4th) string(11) "Changed 4th" WRITE 3 object::offsetSet(5th,Added 5th) object::offsetGet(5th) string(9) "Added 5th" WRITE 4 object::offsetSet(6,Added 6) object::offsetGet(6) string(7) "Added 6" object::offsetGet(0) string(3) "1st" object::offsetGet(2) string(3) "3rd" object::offsetSet(6,changed 6) object::offsetGet(6) string(9) "changed 6" string(9) "changed 6" ===unset=== array(6) { [0]=> string(3) "1st" [1]=> string(9) "Changed 1" [2]=> string(3) "3rd" ["4th"]=> string(11) "Changed 4th" ["5th"]=> string(9) "Added 5th" [6]=> string(9) "changed 6" } object::offsetUnset(2) object::offsetUnset(4th) object::offsetUnset(7) object::offsetUnset(8th) array(4) { [0]=> string(3) "1st" [1]=> string(9) "Changed 1" ["5th"]=> string(9) "Added 5th" [6]=> string(9) "changed 6" } ===DONE=== Index: php-src/tests/classes/array_access_003.phpt +++ php-src/tests/classes/array_access_003.phpt --TEST-- ZE2 ArrayAccess::offsetGet ambiguties --SKIPIF-- <?php if (!class_exists('ArrayAccess')) die('skip ArrayAccess not present'); ?> --FILE-- <?php class Object implements ArrayAccess { public $a = array('1st', 1, 2=>'3rd', '4th'=>4); function offsetExists($index) { echo __METHOD__ . "($index)\n"; return array_key_exists($index, $this->a); } function &offsetGet($index) { echo __METHOD__ . "($index)\n"; switch($index) { case 1: $a = 'foo'; return $a . 'Bar'; case 2: static $a=1; return $a; } return $this->a[$index]; } function offsetSet($index, $newval) { echo __METHOD__ . "($index,$newval)\n"; if ($index==3) { $this->cnt = $newval; } return $this->a[$index] = $newval; } function offsetUnset($index) { echo __METHOD__ . "($index)\n"; unset($this->a[$index]); } } $obj = new Object; var_dump($obj[1]); var_dump($obj[2]); $obj[2]++; var_dump($obj[2]); ?> ===DONE=== --EXPECTF-- object::offsetGet(1) string(6) "fooBar" object::offsetGet(2) int(1) object::offsetGet(2) object::offsetGet(2) int(2) ===DONE=== Index: php-src/tests/classes/array_access_004.phpt +++ php-src/tests/classes/array_access_004.phpt --TEST-- ZE2 ArrayAccess::offsetGet ambiguties --SKIPIF-- <?php if (!class_exists('ArrayAccess')) die('skip ArrayAccess not present'); ?> --FILE-- <?php class Object implements ArrayAccess { public $a = array('1st', 1, 2=>'3rd', '4th'=>4); function offsetExists($index) { echo __METHOD__ . "($index)\n"; return array_key_exists($index, $this->a); } function offsetGet($index) { echo __METHOD__ . "($index)\n"; switch($index) { case 1: $a = 'foo'; return $a . 'Bar'; case 2: static $a=1; return $a; } return $this->a[$index]; } function offsetSet($index, $newval) { echo __METHOD__ . "($index,$newval)\n"; if ($index==3) { $this->cnt = $newval; } return $this->a[$index] = $newval; } function offsetUnset($index) { echo __METHOD__ . "($index)\n"; unset($this->a[$index]); } } $obj = new Object; var_dump($obj[1]); var_dump($obj[2]); $obj[2]++; var_dump($obj[2]); ?> ===DONE=== --EXPECTF-- object::offsetGet(1) string(6) "fooBar" object::offsetGet(2) int(1) object::offsetGet(2) Fatal error: Objects used as arrays in post/pre increment/decrement must return values by reference in %sarray_access_004.php on line %d
-- PHP CVS Mailing List (http://www.php.net/) To unsubscribe, visit: http://www.php.net/unsub.php