Skip to content

@ircmaxell /gist:1966809
Created

Embed URL

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Scalar Casting Patch, Version 3
Index: Zend/zend.h
===================================================================
--- Zend/zend.h (revision 323850)
+++ Zend/zend.h (working copy)
@@ -486,6 +486,10 @@
union _zend_function *__call;
union _zend_function *__callstatic;
union _zend_function *__tostring;
+ union _zend_function *__toint;
+ union _zend_function *__tofloat;
+ union _zend_function *__toarray;
+ union _zend_function *__toscalar;
union _zend_function *serialize_func;
union _zend_function *unserialize_func;
Index: Zend/zend_object_handlers.c
===================================================================
--- Zend/zend_object_handlers.c (revision 323850)
+++ Zend/zend_object_handlers.c (working copy)
@@ -1482,6 +1482,49 @@
}
/* }}} */
+ZEND_API int zend_std_cast_object(zval *readobj, zval *writeobj, int type TSRMLS_DC) /* {{{ */
+{
+ zend_class_entry *ce = Z_OBJCE_P(readobj);
+ zval *retval = NULL;
+ switch (type) {
+ case IS_LONG:
+ if (ce->__toint) {
+ zend_call_method_with_0_params(&readobj, ce, &ce->__toint, "__toint", &retval);
+ }
+ break;
+ case IS_DOUBLE:
+ if (ce->__tofloat) {
+ zend_call_method_with_0_params(&readobj, ce, &ce->__tofloat, "__tofloat", &retval);
+ }
+ break;
+ case IS_ARRAY:
+ if (ce->__toarray) {
+ zend_call_method_with_0_params(&readobj, ce, &ce->__toarray, "__toarray", &retval);
+ }
+ break;
+ }
+ if (retval) {
+ ZVAL_ZVAL(writeobj, retval, 1, 1);
+ return SUCCESS;
+ }
+ return zend_std_cast_object_tostring(readobj, writeobj, type TSRMLS_CC);
+
+}
+/* }}} */
+
+ZEND_API zval *zend_std_cast_object_get(zval *readobj TSRMLS_DC) /* {{{ */
+{
+ zval *retval;
+ zend_class_entry *ce = Z_OBJCE_P(readobj);
+ ALLOC_INIT_ZVAL(retval);
+ if (ce->__toscalar && zend_call_method_with_0_params(&readobj, ce, &ce->__toscalar, "__toscalar", &retval)) {
+ return retval;
+ }
+ ZVAL_NULL(retval);
+ return retval;
+}
+/* }}} */
+
ZEND_API int zend_std_cast_object_tostring(zval *readobj, zval *writeobj, int type TSRMLS_DC) /* {{{ */
{
zval *retval;
@@ -1589,7 +1632,7 @@
zend_std_read_dimension, /* read_dimension */
zend_std_write_dimension, /* write_dimension */
zend_std_get_property_ptr_ptr, /* get_property_ptr_ptr */
- NULL, /* get */
+ zend_std_cast_object_get, /* get */
NULL, /* set */
zend_std_has_property, /* has_property */
zend_std_unset_property, /* unset_property */
@@ -1602,7 +1645,7 @@
zend_std_object_get_class, /* get_class_entry */
zend_std_object_get_class_name, /* get_class_name */
zend_std_compare_objects, /* compare_objects */
- zend_std_cast_object_tostring, /* cast_object */
+ zend_std_cast_object, /* cast_object */
NULL, /* count_elements */
NULL, /* get_debug_info */
zend_std_get_closure, /* get_closure */
Index: Zend/zend_compile.c
===================================================================
--- Zend/zend_compile.c (revision 323850)
+++ Zend/zend_compile.c (working copy)
@@ -1617,6 +1617,22 @@
if (fn_flags & ((ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC) ^ ZEND_ACC_PUBLIC)) {
zend_error(E_WARNING, "The magic method __toString() must have public visibility and cannot be static");
}
+ } else if ((name_len == sizeof(ZEND_TOINT_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_TOINT_FUNC_NAME, sizeof(ZEND_TOINT_FUNC_NAME)-1))) {
+ if (fn_flags & ((ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC) ^ ZEND_ACC_PUBLIC)) {
+ zend_error(E_WARNING, "The magic method __toInt() must have public visibility and cannot be static");
+ }
+ } else if ((name_len == sizeof(ZEND_TOFLOAT_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_TOFLOAT_FUNC_NAME, sizeof(ZEND_TOFLOAT_FUNC_NAME)-1))) {
+ if (fn_flags & ((ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC) ^ ZEND_ACC_PUBLIC)) {
+ zend_error(E_WARNING, "The magic method __toFloat() must have public visibility and cannot be static");
+ }
+ } else if ((name_len == sizeof(ZEND_TOARRAY_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_TOARRAY_FUNC_NAME, sizeof(ZEND_TOARRAY_FUNC_NAME)-1))) {
+ if (fn_flags & ((ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC) ^ ZEND_ACC_PUBLIC)) {
+ zend_error(E_WARNING, "The magic method __toArray() must have public visibility and cannot be static");
+ }
+ } else if ((name_len == sizeof(ZEND_TOSCALAR_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_TOSCALAR_FUNC_NAME, sizeof(ZEND_TOSCALAR_FUNC_NAME)-1))) {
+ if (fn_flags & ((ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC) ^ ZEND_ACC_PUBLIC)) {
+ zend_error(E_WARNING, "The magic method __toScalar() must have public visibility and cannot be static");
+ }
}
} else {
char *class_lcname;
@@ -1673,6 +1689,26 @@
zend_error(E_WARNING, "The magic method __toString() must have public visibility and cannot be static");
}
CG(active_class_entry)->__tostring = (zend_function *) CG(active_op_array);
+ } else if ((name_len == sizeof(ZEND_TOINT_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_TOINT_FUNC_NAME, sizeof(ZEND_TOINT_FUNC_NAME)-1))) {
+ if (fn_flags & ((ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC) ^ ZEND_ACC_PUBLIC)) {
+ zend_error(E_WARNING, "The magic method __toInt() must have public visibility and cannot be static");
+ }
+ CG(active_class_entry)->__toint = (zend_function *) CG(active_op_array);
+ } else if ((name_len == sizeof(ZEND_TOFLOAT_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_TOFLOAT_FUNC_NAME, sizeof(ZEND_TOFLOAT_FUNC_NAME)-1))) {
+ if (fn_flags & ((ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC) ^ ZEND_ACC_PUBLIC)) {
+ zend_error(E_WARNING, "The magic method __toFloat() must have public visibility and cannot be static");
+ }
+ CG(active_class_entry)->__tofloat = (zend_function *) CG(active_op_array);
+ } else if ((name_len == sizeof(ZEND_TOARRAY_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_TOARRAY_FUNC_NAME, sizeof(ZEND_TOARRAY_FUNC_NAME)-1))) {
+ if (fn_flags & ((ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC) ^ ZEND_ACC_PUBLIC)) {
+ zend_error(E_WARNING, "The magic method __toArray() must have public visibility and cannot be static");
+ }
+ CG(active_class_entry)->__toarray = (zend_function *) CG(active_op_array);
+ } else if ((name_len == sizeof(ZEND_TOSCALAR_FUNC_NAME)-1) && (!memcmp(lcname, ZEND_TOSCALAR_FUNC_NAME, sizeof(ZEND_TOSCALAR_FUNC_NAME)-1))) {
+ if (fn_flags & ((ZEND_ACC_PPP_MASK | ZEND_ACC_STATIC) ^ ZEND_ACC_PUBLIC)) {
+ zend_error(E_WARNING, "The magic method __toScalar() must have public visibility and cannot be static");
+ }
+ CG(active_class_entry)->__toscalar = (zend_function *) CG(active_op_array);
} else if (!(fn_flags & ZEND_ACC_STATIC)) {
CG(active_op_array)->fn_flags |= ZEND_ACC_ALLOW_STATIC;
}
@@ -2839,6 +2875,18 @@
if (!ce->__tostring) {
ce->__tostring = ce->parent->__tostring;
}
+ if (!ce->__toint) {
+ ce->__toint = ce->parent->__toint;
+ }
+ if (!ce->__tofloat) {
+ ce->__tofloat = ce->parent->__tofloat;
+ }
+ if (!ce->__toarray) {
+ ce->__toarray = ce->parent->__toarray;
+ }
+ if (!ce->__toscalar) {
+ ce->__toscalar = ce->parent->__toscalar;
+ }
if (!ce->clone) {
ce->clone = ce->parent->clone;
}
@@ -3734,6 +3782,14 @@
ce->__callstatic = fe;
} else if (!strncmp(mname, ZEND_TOSTRING_FUNC_NAME, mname_len)) {
ce->__tostring = fe;
+ } else if (!strncmp(mname, ZEND_TOINT_FUNC_NAME, mname_len)) {
+ ce->__toint = fe;
+ } else if (!strncmp(mname, ZEND_TOFLOAT_FUNC_NAME, mname_len)) {
+ ce->__tofloat = fe;
+ } else if (!strncmp(mname, ZEND_TOARRAY_FUNC_NAME, mname_len)) {
+ ce->__toarray = fe;
+ } else if (!strncmp(mname, ZEND_TOSCALAR_FUNC_NAME, mname_len)) {
+ ce->__toscalar = fe;
} else if (ce->name_length + 1 == mname_len) {
char *lowercase_name = emalloc(ce->name_length + 1);
zend_str_tolower_copy(lowercase_name, ce->name, ce->name_length);
@@ -6742,6 +6798,10 @@
ce->__call = NULL;
ce->__callstatic = NULL;
ce->__tostring = NULL;
+ ce->__toint = NULL;
+ ce->__tofloat = NULL;
+ ce->__toarray = NULL;
+ ce->__toscalar = NULL;
ce->create_object = NULL;
ce->get_iterator = NULL;
ce->iterator_funcs.funcs = NULL;
Index: Zend/zend_object_handlers.h
===================================================================
--- Zend/zend_object_handlers.h (revision 323850)
+++ Zend/zend_object_handlers.h (working copy)
@@ -154,6 +154,8 @@
ZEND_API struct _zend_property_info *zend_get_property_info(zend_class_entry *ce, zval *member, int silent TSRMLS_DC);
ZEND_API HashTable *zend_std_get_properties(zval *object TSRMLS_DC);
ZEND_API HashTable *zend_std_get_debug_info(zval *object, int *is_temp TSRMLS_DC);
+ZEND_API zval *zend_std_cast_object_get(zval *readobj TSRMLS_DC);
+ZEND_API int zend_std_cast_object(zval *readobj, zval *writeobj, int type TSRMLS_DC);
ZEND_API int zend_std_cast_object_tostring(zval *readobj, zval *writeobj, int type TSRMLS_DC);
ZEND_API void zend_std_write_property(zval *object, zval *member, zval *value, const struct _zend_literal *key TSRMLS_DC);
ZEND_API void rebuild_object_properties(zend_object *zobj);
Index: Zend/zend_compile.h
===================================================================
--- Zend/zend_compile.h (revision 323850)
+++ Zend/zend_compile.h (working copy)
@@ -829,6 +829,10 @@
#define ZEND_CALL_FUNC_NAME "__call"
#define ZEND_CALLSTATIC_FUNC_NAME "__callstatic"
#define ZEND_TOSTRING_FUNC_NAME "__tostring"
+#define ZEND_TOINT_FUNC_NAME "__toint"
+#define ZEND_TOFLOAT_FUNC_NAME "__tofloat"
+#define ZEND_TOARRAY_FUNC_NAME "__toarray"
+#define ZEND_TOSCALAR_FUNC_NAME "__toscalar"
#define ZEND_AUTOLOAD_FUNC_NAME "__autoload"
/* The following constants may be combined in CG(compiler_options)
Index: Zend/zend_API.c
===================================================================
--- Zend/zend_API.c (revision 323850)
+++ Zend/zend_API.c (working copy)
@@ -27,6 +27,7 @@
#include "zend_constants.h"
#include "zend_exceptions.h"
#include "zend_closures.h"
+#include "zend_interfaces.h"
#ifdef HAVE_STDARG_H
#include <stdarg.h>
@@ -368,9 +369,25 @@
convert_to_long_ex(arg);
*p = Z_LVAL_PP(arg);
break;
-
+ case IS_OBJECT:
+ {
+ zend_class_entry *ce = Z_OBJCE_PP(arg);
+ zval *tmp = NULL;
+ if (ce->__toint) {
+ if (Z_ISREF_PP(arg)) {
+ zend_spprintf(error, 0, "to be an int, cannot cast referenced parameter from object");
+ return "long";
+ }
+ zend_call_method_with_0_params(arg, ce, &ce->__toint, "__toint", &tmp);
+ if (tmp && Z_TYPE_P(tmp) == IS_LONG) {
+ *p = Z_LVAL_P(tmp);
+ zval_ptr_dtor(&tmp);
+ break;
+ }
+ }
+ }
+ /* Fall through intentional */
case IS_ARRAY:
- case IS_OBJECT:
case IS_RESOURCE:
default:
return "long";
@@ -402,9 +419,25 @@
convert_to_double_ex(arg);
*p = Z_DVAL_PP(arg);
break;
-
+ case IS_OBJECT:
+ {
+ zend_class_entry *ce = Z_OBJCE_PP(arg);
+ zval *tmp = NULL;
+ if (ce->__tofloat) {
+ if (Z_ISREF_PP(arg)) {
+ zend_spprintf(error, 0, "to be a float, cannot cast referenced parameter from object");
+ return "double";
+ }
+ zend_call_method_with_0_params(arg, ce, &ce->__tofloat, "__tofloat", &tmp);
+ if (tmp && Z_TYPE_P(tmp) == IS_DOUBLE) {
+ *p = Z_DVAL_P(tmp);
+ zval_ptr_dtor(&tmp);
+ break;
+ }
+ }
+ }
+ /* Fall through intentional */
case IS_ARRAY:
- case IS_OBJECT:
case IS_RESOURCE:
default:
return "double";
@@ -506,6 +539,23 @@
}
if (Z_TYPE_PP(arg) == IS_ARRAY || (c == 'A' && Z_TYPE_PP(arg) == IS_OBJECT)) {
*p = *arg;
+ } else if (Z_TYPE_PP(arg) == IS_OBJECT) {
+ zend_class_entry *ce = Z_OBJCE_PP(arg);
+ zval *tmp = NULL;
+ if (ce->__toarray) {
+ if (Z_ISREF_PP(arg)) {
+ zend_spprintf(error, 0, "to be an array, cannot cast referenced parameter from object");
+ return "array";
+ }
+ zend_call_method_with_0_params(arg, ce, &ce->__toarray, "__toarray", &tmp);
+ if (tmp && Z_TYPE_P(tmp) == IS_ARRAY) {
+ SEPARATE_ZVAL_IF_NOT_REF(arg);
+ ZVAL_ZVAL(*arg, tmp, 1, 1) ;
+ *p = *arg;
+ break;
+ }
+ }
+ return "array";
} else {
return "array";
}
@@ -526,6 +576,19 @@
if(*p == NULL) {
return "array";
}
+ } else if (Z_TYPE_PP(arg) == IS_OBJECT) {
+ zend_class_entry *ce = Z_OBJCE_PP(arg);
+ zval *tmp = NULL;
+ if (ce->__toarray) {
+ zend_call_method_with_0_params(arg, ce, &ce->__toarray, "__toarray", &tmp);
+ if (tmp && Z_TYPE_P(tmp) == IS_ARRAY) {
+ SEPARATE_ZVAL_IF_NOT_REF(arg);
+ ZVAL_ZVAL(*arg, tmp, 1, 1);
+ *p = HASH_OF(*arg);
+ break;
+ }
+ }
+ return "array";
} else {
return "array";
}
@@ -1930,7 +1993,7 @@
int count=0, unload=0, result=0;
HashTable *target_function_table = function_table;
int error_type;
- zend_function *ctor = NULL, *dtor = NULL, *clone = NULL, *__get = NULL, *__set = NULL, *__unset = NULL, *__isset = NULL, *__call = NULL, *__callstatic = NULL, *__tostring = NULL;
+ zend_function *ctor = NULL, *dtor = NULL, *clone = NULL, *__get = NULL, *__set = NULL, *__unset = NULL, *__isset = NULL, *__call = NULL, *__callstatic = NULL, *__tostring = NULL, *__toint = NULL, *__tofloat = NULL, *__toarray = NULL, *__toscalar = NULL;
const char *lowercase_name;
int fname_len;
const char *lc_class_name = NULL;
@@ -2065,6 +2128,14 @@
__callstatic = reg_function;
} else if ((fname_len == sizeof(ZEND_TOSTRING_FUNC_NAME)-1) && !memcmp(lowercase_name, ZEND_TOSTRING_FUNC_NAME, sizeof(ZEND_TOSTRING_FUNC_NAME))) {
__tostring = reg_function;
+ } else if ((fname_len == sizeof(ZEND_TOINT_FUNC_NAME)-1) && !memcmp(lowercase_name, ZEND_TOINT_FUNC_NAME, sizeof(ZEND_TOINT_FUNC_NAME))) {
+ __toint = reg_function;
+ } else if ((fname_len == sizeof(ZEND_TOFLOAT_FUNC_NAME)-1) && !memcmp(lowercase_name, ZEND_TOFLOAT_FUNC_NAME, sizeof(ZEND_TOFLOAT_FUNC_NAME))) {
+ __tofloat = reg_function;
+ } else if ((fname_len == sizeof(ZEND_TOARRAY_FUNC_NAME)-1) && !memcmp(lowercase_name, ZEND_TOARRAY_FUNC_NAME, sizeof(ZEND_TOARRAY_FUNC_NAME))) {
+ __toarray = reg_function;
+ } else if ((fname_len == sizeof(ZEND_TOSCALAR_FUNC_NAME)-1) && !memcmp(lowercase_name, ZEND_TOSCALAR_FUNC_NAME, sizeof(ZEND_TOSCALAR_FUNC_NAME))) {
+ __toscalar = reg_function;
} else if ((fname_len == sizeof(ZEND_GET_FUNC_NAME)-1) && !memcmp(lowercase_name, ZEND_GET_FUNC_NAME, sizeof(ZEND_GET_FUNC_NAME))) {
__get = reg_function;
} else if ((fname_len == sizeof(ZEND_SET_FUNC_NAME)-1) && !memcmp(lowercase_name, ZEND_SET_FUNC_NAME, sizeof(ZEND_SET_FUNC_NAME))) {
@@ -2107,6 +2178,10 @@
scope->__call = __call;
scope->__callstatic = __callstatic;
scope->__tostring = __tostring;
+ scope->__toint = __toint;
+ scope->__tofloat = __tofloat;
+ scope->__toarray = __toarray;
+ scope->__toscalar = __toscalar;
scope->__get = __get;
scope->__set = __set;
scope->__unset = __unset;
@@ -2150,6 +2225,30 @@
}
__tostring->common.fn_flags &= ~ZEND_ACC_ALLOW_STATIC;
}
+ if (__toint) {
+ if (__toint->common.fn_flags & ZEND_ACC_STATIC) {
+ zend_error(error_type, "Method %s::%s() cannot be static", scope->name, __toint->common.function_name);
+ }
+ __toint->common.fn_flags &= ~ZEND_ACC_ALLOW_STATIC;
+ }
+ if (__tofloat) {
+ if (__tofloat->common.fn_flags & ZEND_ACC_STATIC) {
+ zend_error(error_type, "Method %s::%s() cannot be static", scope->name, __tofloat->common.function_name);
+ }
+ __tofloat->common.fn_flags &= ~ZEND_ACC_ALLOW_STATIC;
+ }
+ if (__toarray) {
+ if (__toarray->common.fn_flags & ZEND_ACC_STATIC) {
+ zend_error(error_type, "Method %s::%s() cannot be static", scope->name, __toarray->common.function_name);
+ }
+ __toarray->common.fn_flags &= ~ZEND_ACC_ALLOW_STATIC;
+ }
+ if (__toscalar) {
+ if (__toscalar->common.fn_flags & ZEND_ACC_STATIC) {
+ zend_error(error_type, "Method %s::%s() cannot be static", scope->name, __toscalar->common.function_name);
+ }
+ __toscalar->common.fn_flags &= ~ZEND_ACC_ALLOW_STATIC;
+ }
if (__get) {
if (__get->common.fn_flags & ZEND_ACC_STATIC) {
zend_error(error_type, "Method %s::%s() cannot be static", scope->name, __get->common.function_name);
Index: Zend/zend_operators.c
===================================================================
--- Zend/zend_operators.c (revision 323850)
+++ Zend/zend_operators.c (working copy)
@@ -30,6 +30,7 @@
#include "zend_strtod.h"
#include "zend_exceptions.h"
#include "zend_closures.h"
+#include "zend_interfaces.h"
#if ZEND_USE_TOLOWER_L
#include <locale.h>
@@ -640,6 +641,7 @@
case IS_OBJECT:
{
zval *tmp;
+ zend_class_entry *ce = Z_OBJCE_P(op);
HashTable *ht;
ALLOC_HASHTABLE(ht);
@@ -651,6 +653,15 @@
FREE_HASHTABLE(ht);
return;
}
+ } else if (ce->__toarray) {
+ zend_call_method_with_0_params(&op, ce, &ce->__toarray, "__toarray", &tmp);
+ if (tmp && Z_TYPE_P(tmp) == IS_ARRAY) {
+ ZVAL_ZVAL(op, tmp, 1, 1);
+ zend_hash_destroy(ht);
+ FREE_HASHTABLE(ht);
+ return;
+ }
+ zend_error(E_WARNING, "%s::__toArray() must return an array", ce->name);
} else if (Z_OBJ_HT_P(op)->get_properties) {
HashTable *obj_ht = Z_OBJ_HT_P(op)->get_properties(op TSRMLS_CC);
if (obj_ht) {
Index: Zend/zend_API.h
===================================================================
--- Zend/zend_API.h (revision 323850)
+++ Zend/zend_API.h (working copy)
@@ -186,6 +186,10 @@
class_container.__call = handle_fcall; \
class_container.__callstatic = NULL; \
class_container.__tostring = NULL; \
+ class_container.__toint = NULL; \
+ class_container.__tofloat = NULL; \
+ class_container.__toarray = NULL; \
+ class_container.__toscalar = NULL; \
class_container.__get = handle_propget; \
class_container.__set = handle_propset; \
class_container.__unset = handle_propunset; \
@rmccue

Side note: #define ZEND_TOINT_FUNC_NAME "__toint" (etc) uses tabs for alignment, the other lines use spaces, so with a different tab width, these don't line up.

@ircmaxell
Owner

@rmccue: There are more than that, there's actually a number of white-space issues (I'm attributing it to the editor I used over the terminal which converts tabs to spaces in some cases). The final patch will clean up the whitespace significantly.

Thanks for the comment

Anthony

@vlucas

What about a __toBool (or __toBoolean) magic method? This would be useful for quick object comparisons without an additional method call, like:

if($obj) {
  // success
} else {
  // errors
}
@olekukonko

Here is another good reason why this patch should be considered http://stackoverflow.com/questions/16375331/increment-on-tostring .... +1 @ircmaxwell

@csurf

Hi there. I found your patch on the php rfc site. I wrote you an email a while back, don't know if you got it or not, but it basically discussed a bug/issue that I found with your patch. More specifically, there's an issue with implicit casts not working when performing arith. operations on objects, where the __toScalar function should be called but it never is. Let me know if you got my message, and, if not, i can just fill you in on the details either here or via email.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.