Per RFC:
https://wiki.php.net/rfc/propertygetsetsyntax
Detailed Implementation Notes Document:
https://wiki.php.net/rfc/propertygetsetsyntax-as-implemented
Getters/Setters has been built. This is my first patch to the php core. Here is what has been
implemented:
Static getters/setters syntax
static public $Hours {
get { return self::$_Hours; }
set { self::$_Hours = $value; }
}
Asymmetrical getters/setters syntax
public $Hours {
get { return $this->_Hours; }
protected set { $this->_Hours = $value; }
}
Default getter/setter implementations (auto-backing)
public $Hours { /* Implements protected $__Hours; */
get; /* { return $this->__Hours; }
set; /* { $this->__Hours = $value; }
}
Interfaces (Requires implementation by implementing classes)
private $Hours {
get;
set;
}
Overloading Properties - Properties are inherited and can be over-ridden in child classes provided
the parent has not specified final on an accessor.
Traits
No changes necessary, implementation worked without further changes necessary
Errors
Updated errors to refer to getter/setter implementation when related
Note
* This does not interfere with typical __get()/__set() functionality which will take effect
whenever there is not a specific accessor available.
* There are also 25 unit tests, testing all of this functionality
* There is also a patch to change the reflection extension to expose new functions for
accessors.
What are the next steps to get this added to some future release? Attached is a patch against
~/trunk
Index: Zend/zend.c
===================================================================
--- Zend/zend.c (revision 324439)
+++ Zend/zend.c (working copy)
@@ -1343,6 +1343,16 @@
}
/* }}} */
+/* Returns a new string of combined strings */
+char *strcatalloc(const char *a, const char *b) /* {{{ */
+{
+ char *out = estrndup(a, strlen(a) + strlen(b)+1);
+ memcpy(&out[strlen(a)], b, strlen(b)+1);
+ return out;
+}
+/* }}} */
+
+
/*
* Local variables:
* tab-width: 4
Index: Zend/zend.h
===================================================================
--- Zend/zend.h (revision 324439)
+++ Zend/zend.h (working copy)
@@ -469,6 +469,8 @@
HashTable function_table;
HashTable properties_info;
+ HashTable accessors;
+
zval **default_properties_table;
zval **default_static_members_table;
zval **static_members_table;
@@ -671,6 +673,8 @@
#define ZEND_PUTS_EX(str) write_func((str), strlen((str)))
#define ZEND_PUTC(c) zend_write(&(c), 1), (c)
+extern char *strcatalloc(const char *a, const char *b);
+
BEGIN_EXTERN_C()
extern ZEND_API int (*zend_printf)(const char *format, ...) ZEND_ATTRIBUTE_PTR_FORMAT(printf, 1, 2);
extern ZEND_API zend_write_func_t zend_write;
Index: Zend/zend_compile.c
===================================================================
--- Zend/zend_compile.c (revision 324439)
+++ Zend/zend_compile.c (working copy)
@@ -724,6 +724,69 @@
} else {
zend_do_fetch_class(&class_node, class_name TSRMLS_CC);
}
+
+ /* Handle parent::$Area accessors (normal and static) */
+ if(class_node.op_type == IS_VAR && class_node.EA == ZEND_FETCH_CLASS_PARENT && CG(active_class_entry) && CG(active_class_entry)->parent) {
+
+ const char *member_name = CG(active_op_array)->vars[result->u.op.var].name;
+ zend_accessor_info **aipp;
+
+ /* If the member_name is an accessor */
+ if(zend_hash_find((const HashTable *)&CG(active_class_entry)->parent->accessors, member_name, strlen(member_name)+1, (void**)&aipp) == SUCCESS) {
+ znode zn_parent, zn_func, zn_arg_list;
+
+ MAKE_ZNODE(zn_parent, "parent");
+ Z_LVAL(zn_arg_list.u.constant) = 0;
+
+ char *fname = strcatalloc("__get", member_name);
+ MAKE_ZNODE(zn_func, fname);
+ efree(fname);
+
+ /* We assume we will be used as a getter, if zend_do_assign() is called, it will backpatch as calling a setter */
+ zend_do_begin_class_member_function_call(&zn_parent, &zn_func TSRMLS_CC);
+ zend_do_end_function_call(&zn_func, result, &zn_arg_list, 1, 0 TSRMLS_CC);
+ zend_do_extended_fcall_end(TSRMLS_C);
+ return;
+ }
+ }
+ /* Handle Shape::$Area static accessor */
+ if(class_node.op_type == IS_CONST && result->op_type == IS_CV) {
+ zend_class_entry **classpp;
+ char *lcname = zend_str_tolower_dup(Z_STRVAL(class_node.u.constant), Z_STRLEN(class_node.u.constant));
+
+ if(zend_hash_find(CG(class_table), lcname, Z_STRLEN(class_node.u.constant)+1, (void**)&classpp) == SUCCESS) {
+ efree(lcname);
+
+ lcname = strcatalloc("__get", CG(active_op_array)->vars[result->u.op.var].name);
+ uint lcname_len = strlen(lcname);
+ zend_str_tolower(lcname, lcname_len);
+
+ ulong hash_value = zend_hash_func(lcname, lcname_len+1);
+ zend_function *fbc;
+
+ if(zend_hash_quick_find(&(*classpp)->function_table, lcname, lcname_len+1, hash_value, (void**)&fbc) == SUCCESS) {
+ efree(lcname);
+
+ if(!(fbc->common.fn_flags & ZEND_ACC_STATIC))
+ zend_error(E_COMPILE_ERROR, "Cannot access non-static accessor %s::$%s in a static manner.", (*classpp)->name, ZEND_ACC_NAME(fbc));
+
+ znode zn_class, zn_func, zn_arg_list;
+ MAKE_ZNODE(zn_class, Z_STRVAL(class_node.u.constant));
+ MAKE_ZNODE(zn_func, fbc->common.function_name);
+ Z_LVAL(zn_arg_list.u.constant) = 0;
+
+ /* We assume we will be used as a getter, if zend_do_assign() is called, it will backpatch as calling a setter */
+ zend_do_begin_class_member_function_call(&zn_class, &zn_func TSRMLS_CC);
+ zend_do_end_function_call(&zn_func, result, &zn_arg_list, 1, 0 TSRMLS_CC);
+ zend_do_extended_fcall_end(TSRMLS_C);
+ zval_dtor(&class_node.u.constant);
+ return;
+ }
+ }
+ efree(lcname);
+ }
+
+
zend_stack_top(&CG(bp_stack), (void **) &fetch_list_ptr);
if (result->op_type == IS_CV) {
init_op(&opline TSRMLS_CC);
@@ -963,6 +1026,37 @@
last_op = &CG(active_op_array)->opcodes[last_op_number-n-1];
+ /* assigning to function call, re-negotiate the opcodes to call a single parameter function */
+ if(last_op->opcode == ZEND_DO_FCALL_BY_NAME && last_op_number >= 1 && (last_op-1)->opcode == ZEND_INIT_STATIC_METHOD_CALL) {
+ znode zn_class, zn_func, zn_arg_list;
+
+ switch((last_op-1)->op1_type) {
+ case IS_VAR:
+ if((last_op-1)->extended_value == ZEND_FETCH_CLASS_PARENT) {
+ MAKE_ZNODE(zn_class, "parent");
+ }
+ break;
+ case IS_CONST:
+ MAKE_ZNODE(zn_class, Z_STRVAL(CG(active_op_array)->literals[(last_op-1)->op1.constant].constant)); /* Capture class name */
+ break;
+ }
+
+ MAKE_ZNODE(zn_func, Z_STRVAL(CG(active_op_array)->literals[(last_op-1)->op2.constant].constant)); /* Capture function name */
+ Z_STRVAL(zn_func.u.constant)[2] = 's'; /* Change from __getXXX() to __setXXX() */
+ Z_LVAL(zn_arg_list.u.constant) = 1;
+
+ /* Clear ZEND_INIT_STATIC_METHOD_CALL and ZEND_DO_FCALL_BY_NAME oplines */
+ MAKE_NOP(last_op);
+ MAKE_NOP((last_op-1));
+
+ /* Replace with Class::__setXXX() call */
+ zend_do_begin_class_member_function_call(&zn_class, &zn_func TSRMLS_CC);
+ zend_do_pass_param(value, ZEND_SEND_VAL, Z_LVAL(result->u.constant) TSRMLS_CC);
+ zend_do_end_function_call(&zn_func, result, &zn_arg_list, 1, 0 TSRMLS_CC);
+ zend_do_extended_fcall_end(TSRMLS_C);
+ return;
+ }
+
if (last_op->result_type == IS_VAR &&
last_op->result.var == variable->u.op.var) {
if (last_op->opcode == ZEND_FETCH_OBJ_W) {
@@ -1512,6 +1606,16 @@
&& (Z_LVAL(new_modifier->u.constant) & ZEND_ACC_FINAL)) {
zend_error(E_COMPILE_ERROR, "Multiple final modifiers are not allowed");
}
+ if ((Z_LVAL(current_access_type->u.constant) & ZEND_ACC_READONLY) && (Z_LVAL(new_modifier->u.constant) & ZEND_ACC_READONLY)) {
+ zend_error(E_COMPILE_ERROR, "Multiple read-only modifiers are not allowed");
+ }
+ if ((Z_LVAL(current_access_type->u.constant) & ZEND_ACC_WRITEONLY) && (Z_LVAL(new_modifier->u.constant) & ZEND_ACC_WRITEONLY)) {
+ zend_error(E_COMPILE_ERROR, "Multiple write-only modifiers are not allowed");
+ }
+ if ( ((Z_LVAL(current_access_type->u.constant) & ZEND_ACC_READONLY) && (Z_LVAL(new_modifier->u.constant) & ZEND_ACC_WRITEONLY))
+ || ((Z_LVAL(current_access_type->u.constant) & ZEND_ACC_WRITEONLY) && (Z_LVAL(new_modifier->u.constant) & ZEND_ACC_READONLY)) ) {
+ zend_error(E_COMPILE_ERROR, "read-only and write-only modifiers are mutually exclusive");
+ }
if (((Z_LVAL(current_access_type->u.constant) | Z_LVAL(new_modifier->u.constant)) & (ZEND_ACC_ABSTRACT | ZEND_ACC_FINAL)) == (ZEND_ACC_ABSTRACT | ZEND_ACC_FINAL)) {
zend_error(E_COMPILE_ERROR, "Cannot use the final modifier on an abstract class member");
}
@@ -1519,6 +1623,165 @@
}
/* }}} */
+void zend_declare_accessor(znode *var_name TSRMLS_DC) { /* {{{ */
+ /* Generate Hash Value for Variable */
+ ulong hash_value = zend_hash_func(Z_STRVAL(var_name->u.constant), Z_STRLEN(var_name->u.constant)+1);
+ zend_accessor_info **aipp, *ai;
+
+ /* Locate or create accessor_info structure */
+ if(zend_hash_quick_find(&CG(active_class_entry)->accessors, Z_STRVAL(var_name->u.constant), Z_STRLEN(var_name->u.constant)+1, hash_value, (void**) &aipp) != SUCCESS) {
+ ai = emalloc(sizeof(zend_accessor_info));
+ memset(ai, 0, sizeof(zend_accessor_info));
+ ai->flags = CG(access_type);
+ ai->doc_comment = CG(doc_comment);
+ ai->doc_comment_len = CG(doc_comment_len);
+ CG(doc_comment) = NULL;
+ CG(doc_comment_len) = 0;
+ zend_hash_quick_update(&CG(active_class_entry)->accessors, Z_STRVAL(var_name->u.constant), Z_STRLEN(var_name->u.constant)+1, hash_value, (void**)&ai, sizeof(zend_accessor_info*), NULL);
+ }
+}
+/* }}} */
+
+void zend_do_begin_accessor_declaration(znode *function_token, znode *var_name, znode *modifiers TSRMLS_DC) /* {{{ */
+{
+ /* Generate Hash Value for Variable */
+ ulong hash_value = zend_hash_func(Z_STRVAL(var_name->u.constant), Z_STRLEN(var_name->u.constant)+1);
+ zend_accessor_info **aipp, *ai;
+
+ /* Locate or create accessor_info structure */
+ if(zend_hash_quick_find(&CG(active_class_entry)->accessors, Z_STRVAL(var_name->u.constant), Z_STRLEN(var_name->u.constant)+1, hash_value, (void**) &aipp) == SUCCESS) {
+ ai = *aipp;
+ } else {
+ zend_error(E_COMPILE_ERROR, "Unable to locate accessor structure '%s', please report this to php-internals.", Z_STRVAL(var_name->u.constant));
+ }
+
+ /* Inherit flags from outer accessor definition */
+ Z_LVAL(modifiers->u.constant) |= (ai->flags & ZEND_ACC_STATIC);
+ Z_LVAL(modifiers->u.constant) &= ~ZEND_ACC_IS_ACCESSOR;
+
+ if(strcasecmp("get", function_token->u.constant.value.str.val) == 0) {
+ modifiers->u.constant.value.lval |= ZEND_ACC_IS_GETTER;
+ /* Convert type and variable name to __getHours() */
+ char *tmp = strcatalloc("__get", Z_STRVAL(var_name->u.constant));
+ efree(Z_STRVAL(function_token->u.constant));
+ Z_STRVAL(function_token->u.constant) = tmp;
+ Z_STRLEN(function_token->u.constant) = strlen(tmp);
+
+ /* Declare Function */
+ zend_do_begin_function_declaration(function_token, function_token, 1, ZEND_RETURN_VAL, modifiers TSRMLS_CC);
+ } else if(strcasecmp("set", function_token->u.constant.value.str.val) == 0) {
+ modifiers->u.constant.value.lval |= ZEND_ACC_IS_SETTER;
+
+ /* Convert type and variable name to __setHours() */
+ char *tmp = strcatalloc("__set", Z_STRVAL(var_name->u.constant));
+ efree(Z_STRVAL(function_token->u.constant));
+ Z_STRVAL(function_token->u.constant) = tmp;
+ Z_STRLEN(function_token->u.constant) = strlen(tmp);
+
+ /* Declare Function */
+ zend_do_begin_function_declaration(function_token, function_token, 1, ZEND_RETURN_VAL, modifiers TSRMLS_CC);
+
+ /* Add $value parameter to __setHours() */
+ znode unused_node, unused_node2, value_node;
+ unused_node.op_type = unused_node2.op_type = IS_UNUSED;
+ unused_node.u.op.num = unused_node2.u.op.num = 1;
+
+ Z_STRVAL(value_node.u.constant) = estrndup("value", 5);
+ Z_STRLEN(value_node.u.constant) = 5;
+
+ zend_do_receive_arg(ZEND_RECV, &value_node, &unused_node, NULL, &unused_node2, 0 TSRMLS_CC);
+ } else {
+ zend_error(E_COMPILE_ERROR, "Unknown accessor '%s', expecting get or set for variable $%s", function_token->u.constant.value.str.val, var_name->u.constant.value.str.val);
+ }
+
+ zend_function *func = (zend_function*)CG(active_op_array);
+
+ /* Ensure we are not defining an accessor contrary to read-only/write-only */
+ if((func->common.fn_flags & ZEND_ACC_IS_GETTER) && (ai->flags & ZEND_ACC_WRITEONLY)) {
+ zend_error(E_COMPILE_ERROR, "Cannot define getter for write-only property $%s", var_name->u.constant.value.str.val);
+ } else if((func->common.fn_flags & ZEND_ACC_IS_SETTER) && (ai->flags & ZEND_ACC_READONLY)) {
+ zend_error(E_COMPILE_ERROR, "Cannot define setter for read-only property $%s", var_name->u.constant.value.str.val);
+ }
+
+ if(func->common.fn_flags & ZEND_ACC_IS_GETTER) {
+ ai->getter = func;
+ } else {
+ ai->setter = func;
+ }
+}
+/* }}} */
+
+void zend_do_end_accessor_declaration(znode *function_token, znode *var_name, znode *modifiers, const znode *body TSRMLS_DC) /* {{{ */
+{
+ /* If we have no function body, create an automatic body */
+ if(body == NULL && (CG(active_class_entry)->ce_flags & ZEND_ACC_INTERFACE) == 0) {
+ char *int_var_name = strcatalloc("__", Z_STRVAL(var_name->u.constant));
+
+ zend_property_info **zpi;
+
+ znode zn_this_rv, zn_this;
+ MAKE_ZNODE(zn_this, "this");
+
+ znode zn_prop_rv, zn_prop;
+ MAKE_ZNODE(zn_prop, int_var_name);
+
+ if(strstr(CG(active_op_array)->function_name, "get") != NULL) {
+ if(zend_hash_find(&CG(active_class_entry)->properties_info, Z_STRVAL(zn_prop.u.constant), Z_STRLEN(zn_prop.u.constant)+1, (void**) &zpi) != SUCCESS) {
+ zend_do_declare_property(&zn_prop, NULL, ZEND_ACC_PROTECTED TSRMLS_CC);
+ }
+
+ zend_do_extended_info(TSRMLS_C);
+
+ /* Fetch $this */
+ zend_do_begin_variable_parse(TSRMLS_C);
+ fetch_simple_variable(&zn_this_rv, &zn_this, 1 TSRMLS_CC);
+
+ /* Fetch Internal Variable Name */
+ znode zn_prop_rv;
+ MAKE_ZNODE(zn_prop, int_var_name);
+ zend_do_fetch_property(&zn_prop_rv, &zn_this_rv, &zn_prop TSRMLS_CC);
+
+ /* Return value fetched */
+ zend_do_return(&zn_prop_rv, 1 TSRMLS_CC);
+
+ } else if(strstr(CG(active_op_array)->function_name, "set") != NULL) {
+ if(zend_hash_find(&CG(active_class_entry)->properties_info, Z_STRVAL(zn_prop.u.constant), Z_STRLEN(zn_prop.u.constant)+1, (void**) &zpi) != SUCCESS) {
+ zend_do_declare_property(&zn_prop, NULL, ZEND_ACC_PROTECTED TSRMLS_CC);
+ }
+
+ zend_do_extended_info(TSRMLS_C);
+ zend_do_begin_variable_parse(TSRMLS_C);
+
+ /* Fetch $this */
+ fetch_simple_variable(&zn_this_rv, &zn_this, 1 TSRMLS_CC);
+
+ /* Fetch Internal Variable Name */
+ zend_do_fetch_property(&zn_prop_rv, &zn_this_rv, &zn_prop TSRMLS_CC);
+
+ znode zn_value_rv, zn_value;
+ MAKE_ZNODE(zn_value, "value");
+
+ /* Fetch $value */
+ zend_do_begin_variable_parse(TSRMLS_C);
+ fetch_simple_variable(&zn_value_rv, &zn_value, 1 TSRMLS_CC);
+ zend_do_end_variable_parse(&zn_value_rv, BP_VAR_R, 0 TSRMLS_CC);
+
+ zend_check_writable_variable(&zn_prop_rv);
+
+ /* Assign $value to $this->[Internal Value] */
+ znode zn_assign_rv;
+ zend_do_assign(&zn_assign_rv, &zn_prop_rv, &zn_value_rv TSRMLS_CC);
+
+ zend_do_free(&zn_assign_rv TSRMLS_CC);
+ }
+ efree(int_var_name);
+ }
+
+ zend_do_end_function_declaration(function_token TSRMLS_CC);
+}
+/* }}} */
+
+
void zend_do_begin_function_declaration(znode *function_token, znode *function_name, int is_method, int return_reference, znode *fn_flags_znode TSRMLS_DC) /* {{{ */
{
zend_op_array op_array;
@@ -1532,12 +1795,17 @@
if (is_method) {
if (CG(active_class_entry)->ce_flags & ZEND_ACC_INTERFACE) {
- if ((Z_LVAL(fn_flags_znode->u.constant) & ~(ZEND_ACC_STATIC|ZEND_ACC_PUBLIC))) {
+ if ((Z_LVAL(fn_flags_znode->u.constant) & ~(ZEND_ACC_STATIC|ZEND_ACC_PUBLIC|ZEND_ACC_IS_ACCESSOR))) {
zend_error(E_COMPILE_ERROR, "Access type for interface method %s::%s() must be omitted", CG(active_class_entry)->name, function_name->u.constant.value.str.val);
}
Z_LVAL(fn_flags_znode->u.constant) |= ZEND_ACC_ABSTRACT; /* propagates to the rest of the parser */
}
fn_flags = Z_LVAL(fn_flags_znode->u.constant); /* must be done *after* the above check */
+
+ if(fn_flags & ZEND_ACC_READONLY && !(fn_flags & ZEND_ACC_IS_ACCESSOR))
+ zend_error(E_COMPILE_ERROR, "Method %s::%s() cannot be defined read-only, not permitted for methods.", CG(active_class_entry)->name, function_name->u.constant.value.str.val);
+ if(fn_flags & ZEND_ACC_WRITEONLY && !(fn_flags & ZEND_ACC_IS_ACCESSOR))
+ zend_error(E_COMPILE_ERROR, "Method %s::%s() cannot be defined write-only, not permitted for methods.", CG(active_class_entry)->name, function_name->u.constant.value.str.val);
} else {
fn_flags = 0;
}
@@ -2904,6 +3172,17 @@
}
/* }}} */
+char *zend_accessor_type_string(zend_uint fn_flags) /* {{{ */
+{
+ if (fn_flags & ZEND_ACC_IS_GETTER) {
+ return "get";
+ } else if (fn_flags & ZEND_ACC_IS_SETTER) {
+ return "set";
+ }
+ return "access";
+}
+/* }}} */
+
static void do_inherit_method(zend_function *function) /* {{{ */
{
/* The class entry of the derived function intentionally remains the same
@@ -3216,19 +3495,31 @@
}
if (parent_flags & ZEND_ACC_FINAL) {
+ if (parent_flags & ZEND_ACC_IS_ACCESSOR) {
+ zend_error(E_COMPILE_ERROR, "Cannot override final property %ster %s::$%s", zend_accessor_type_string(child->common.fn_flags), ZEND_FN_SCOPE_NAME(parent), ZEND_ACC_NAME(child));
+ } else {
zend_error(E_COMPILE_ERROR, "Cannot override final method %s::%s()", ZEND_FN_SCOPE_NAME(parent), child->common.function_name);
}
+ }
child_flags = child->common.fn_flags;
/* You cannot change from static to non static and vice versa.
*/
if ((child_flags & ZEND_ACC_STATIC) != (parent_flags & ZEND_ACC_STATIC)) {
if (child->common.fn_flags & ZEND_ACC_STATIC) {
+ if(child->common.fn_flags & ZEND_ACC_IS_ACCESSOR) {
+ zend_error(E_COMPILE_ERROR, "Cannot make non static accessor %s::$%s static in class %s", ZEND_FN_SCOPE_NAME(parent), ZEND_ACC_NAME(child), ZEND_FN_SCOPE_NAME(child));
+ } else {
zend_error(E_COMPILE_ERROR, "Cannot make non static method %s::%s() static in class %s", ZEND_FN_SCOPE_NAME(parent), child->common.function_name, ZEND_FN_SCOPE_NAME(child));
+ }
+ } else {
+ if(child->common.fn_flags & ZEND_ACC_IS_ACCESSOR) {
+ zend_error(E_COMPILE_ERROR, "Cannot make static accessor %s::$%s non static in class %s", ZEND_FN_SCOPE_NAME(parent), ZEND_ACC_NAME(child), ZEND_FN_SCOPE_NAME(child));
} else {
zend_error(E_COMPILE_ERROR, "Cannot make static method %s::%s() non static in class %s", ZEND_FN_SCOPE_NAME(parent), child->common.function_name, ZEND_FN_SCOPE_NAME(child));
}
}
+ }
/* Disallow making an inherited method abstract. */
if ((child_flags & ZEND_ACC_ABSTRACT) && !(parent_flags & ZEND_ACC_ABSTRACT)) {
@@ -3241,7 +3532,11 @@
/* Prevent derived classes from restricting access that was available in parent classes
*/
if ((child_flags & ZEND_ACC_PPP_MASK) > (parent_flags & ZEND_ACC_PPP_MASK)) {
+ if (child_flags & ZEND_ACC_IS_ACCESSOR) {
+ zend_error(E_COMPILE_ERROR, "Access level to %ster %s::$%s must be %s (as in class %s)%s", zend_accessor_type_string(child->common.fn_flags), ZEND_FN_SCOPE_NAME(child), ZEND_ACC_NAME(child), zend_visibility_string(parent_flags), ZEND_FN_SCOPE_NAME(parent), (parent_flags&ZEND_ACC_PUBLIC) ? "" : " or weaker");
+ } else {
zend_error(E_COMPILE_ERROR, "Access level to %s::%s() must be %s (as in class %s)%s", ZEND_FN_SCOPE_NAME(child), child->common.function_name, zend_visibility_string(parent_flags), ZEND_FN_SCOPE_NAME(parent), (parent_flags&ZEND_ACC_PUBLIC) ? "" : " or weaker");
+ }
} else if (((child_flags & ZEND_ACC_PPP_MASK) < (parent_flags & ZEND_ACC_PPP_MASK))
&& ((parent_flags & ZEND_ACC_PPP_MASK) & ZEND_ACC_PRIVATE)) {
child->common.fn_flags |= ZEND_ACC_CHANGED;
@@ -3404,6 +3699,38 @@
((void (*)(void *)) zval_add_ref)
#endif
+/* Updates the index of accessors, called after the function_table has been modified en mass */
+static inline void zend_do_update_accessors(zend_class_entry *ce TSRMLS_DC) /* {{{ */
+{
+ zend_function *func;
+
+ for (zend_hash_internal_pointer_reset(&ce->function_table);
+ zend_hash_get_current_data(&ce->function_table, (void *) &func) == SUCCESS;
+ zend_hash_move_forward(&ce->function_table)) {
+ if (func->common.fn_flags & ZEND_ACC_IS_ACCESSOR) {
+ const char *varname = ZEND_ACC_NAME(func);
+ ulong hash_value = zend_hash_func(varname, strlen(varname)+1);
+
+ zend_accessor_info **aipp, *ai;
+
+ if(zend_hash_quick_find(&ce->accessors, varname, strlen(varname)+1, hash_value, (void**) &aipp) != SUCCESS) {
+ ai = emalloc(sizeof(zend_accessor_info));
+ memset(ai, 0, sizeof(zend_accessor_info));
+ zend_hash_quick_update(&ce->accessors, varname, strlen(varname)+1, hash_value, (void**)&ai, sizeof(zend_accessor_info*), NULL);
+ } else {
+ ai = *aipp;
+ }
+
+ if(func->common.fn_flags & ZEND_ACC_IS_GETTER) {
+ ai->getter = func;
+ } else {
+ ai->setter = func;
+ }
+ }
+ }
+}
+/* }}} */
+
ZEND_API void zend_do_inheritance(zend_class_entry *ce, zend_class_entry *parent_ce TSRMLS_DC) /* {{{ */
{
zend_property_info *property_info;
@@ -3527,6 +3854,8 @@
zend_verify_abstract_class(ce TSRMLS_CC);
}
ce->ce_flags |= parent_ce->ce_flags & ZEND_HAS_STATIC_IN_METHODS;
+
+ zend_do_update_accessors(ce TSRMLS_CC);
}
/* }}} */
@@ -4141,6 +4470,22 @@
}
/* }}} */
+static inline zend_op *find_previous_op(zend_uchar opcode TSRMLS_DC) /* {{{ */
+{
+ int last_op_number = get_next_op_number(CG(active_op_array));
+ zend_op *last_op;
+ int n = 0;
+
+ while (last_op_number - n > 0) {
+ last_op = &CG(active_op_array)->opcodes[last_op_number-n-1];
+ if(last_op->opcode == opcode)
+ return last_op;
+ n++;
+ }
+ return NULL;
+}
+/* }}} */
+
static void zend_traits_register_private_property(zend_class_entry *ce, const char *name, int name_len, zend_property_info *old_info, zval *property TSRMLS_DC) /* {{{ */
{
char *priv_name;
@@ -4376,6 +4721,7 @@
if (ce->ce_flags & ZEND_ACC_IMPLICIT_ABSTRACT_CLASS) {
ce->ce_flags -= ZEND_ACC_IMPLICIT_ABSTRACT_CLASS;
}
+ zend_do_update_accessors(ce TSRMLS_CC);
}
/* }}} */
@@ -5015,6 +5361,19 @@
opline->extended_value = parent_class_name->u.op.var;
opline->opcode = ZEND_DECLARE_INHERITED_CLASS;
+
+ /* Locate parent_class_name and parent_class_entry to assign */
+ zend_op *fetch_class = find_previous_op(ZEND_FETCH_CLASS);
+ if(fetch_class != NULL && fetch_class->op2_type == IS_CONST) {
+ zend_class_entry **parent_cepp = NULL;
+ zval *parent_class_zv = &CG(active_op_array)->literals[fetch_class->op2.constant].constant;
+ char *lc_parent_class = zend_str_tolower_dup(Z_STRVAL_P(parent_class_zv), Z_STRLEN_P(parent_class_zv));
+
+ if (zend_hash_find(CG(class_table), lc_parent_class, strlen(lc_parent_class)+1, (void **) &parent_cepp)==SUCCESS) {
+ new_class_entry->parent = *parent_cepp;
+ }
+ efree(lc_parent_class);
+ }
} else {
opline->opcode = ZEND_DECLARE_CLASS;
}
@@ -5240,6 +5599,14 @@
zend_error(E_COMPILE_ERROR, "Properties cannot be declared abstract");
}
+ if (access_type & ZEND_ACC_READONLY) {
+ zend_error(E_COMPILE_ERROR, "Properties cannot be declared read-only");
+ }
+
+ if (access_type & ZEND_ACC_WRITEONLY) {
+ zend_error(E_COMPILE_ERROR, "Properties cannot be declared write-only");
+ }
+
if (access_type & ZEND_ACC_FINAL) {
zend_error(E_COMPILE_ERROR, "Cannot declare property %s::$%s final, the final modifier is allowed only for methods and classes",
CG(active_class_entry)->name, var_name->u.constant.value.str.val);
@@ -6732,6 +7099,7 @@
zend_hash_init_ex(&ce->properties_info, 0, NULL, (dtor_func_t) (persistent_hashes ? zend_destroy_property_info_internal : zend_destroy_property_info), persistent_hashes, 0);
zend_hash_init_ex(&ce->constants_table, 0, NULL, zval_ptr_dtor_func, persistent_hashes, 0);
zend_hash_init_ex(&ce->function_table, 0, NULL, ZEND_FUNCTION_DTOR, persistent_hashes, 0);
+ zend_hash_init_ex(&ce->accessors, 0, NULL, ZEND_ACCESSOR_DTOR, persistent_hashes, 0);
if (ce->type == ZEND_INTERNAL_CLASS) {
#ifdef ZTS
Index: Zend/zend_compile.h
===================================================================
--- Zend/zend_compile.h (revision 324439)
+++ Zend/zend_compile.h (working copy)
@@ -34,6 +34,14 @@
#define FREE_PNODE(znode) zval_dtor(&znode->u.constant);
+#define MAKE_ZNODE(zn, str) \
+ { \
+ zn.op_type = IS_CONST; \
+ INIT_PZVAL(&zn.u.constant); \
+ ZVAL_STRINGL(&zn.u.constant, str, strlen(str), 1); \
+ zn.EA = 0; \
+ }
+
#define SET_UNUSED(op) op ## _type = IS_UNUSED
#define INC_BPC(op_array) if (op_array->fn_flags & ZEND_ACC_INTERACTIVE) { (CG(context).backpatch_count++); }
@@ -207,7 +215,16 @@
#define ZEND_ACC_RETURN_REFERENCE 0x4000000
#define ZEND_ACC_DONE_PASS_TWO 0x8000000
+#define ZEND_ACC_IS_GETTER 0x10000000
+#define ZEND_ACC_IS_SETTER 0x20000000
+#define ZEND_ACC_IS_ACCESSOR 0x30000000 /* Mask */
+
+#define ZEND_ACC_READONLY 0x40000000
+#define ZEND_ACC_WRITEONLY 0x80000000
+
+
char *zend_visibility_string(zend_uint fn_flags);
+char *zend_accessor_type_string(zend_uint fn_flags);
typedef struct _zend_property_info {
@@ -341,6 +358,15 @@
zend_internal_function internal_function;
} zend_function;
+#define ZEND_ACC_NAME(func) &func->common.function_name[5]
+
+typedef struct _zend_accessor_info {
+ zend_uint flags;
+ const char *doc_comment;
+ int doc_comment_len;
+ zend_function *getter;
+ zend_function *setter;
+} zend_accessor_info;
typedef struct _zend_function_state {
zend_function *function;
@@ -491,6 +517,10 @@
void zend_do_return(znode *expr, int do_end_vparse TSRMLS_DC);
void zend_do_handle_exception(TSRMLS_D);
+
+void zend_do_begin_accessor_declaration(znode *function_token, znode *var_name, znode *modifiers TSRMLS_DC);
+void zend_do_end_accessor_declaration(znode *function_token, znode *var_name, znode *modifiers, const znode *body TSRMLS_DC);
+
void zend_do_begin_lambda_function_declaration(znode *result, znode *function_token, int return_reference, int is_static TSRMLS_DC);
void zend_do_fetch_lexical_variable(znode *varname, zend_bool is_ref TSRMLS_DC);
@@ -625,6 +655,8 @@
void zend_resolve_goto_label(zend_op_array *op_array, zend_op *opline, int pass2 TSRMLS_DC);
void zend_release_labels(TSRMLS_D);
+static inline zend_op *find_previous_op(zend_uchar opcode TSRMLS_DC);
+
ZEND_API void function_add_ref(zend_function *function);
#define INITIAL_OP_ARRAY_SIZE 64
@@ -648,6 +680,7 @@
ZEND_API int zend_cleanup_function_data_full(zend_function *function TSRMLS_DC);
ZEND_API void destroy_zend_function(zend_function *function TSRMLS_DC);
+ZEND_API void zend_accessor_dtor(zend_accessor_info **ai);
ZEND_API void zend_function_dtor(zend_function *function);
ZEND_API void destroy_zend_class(zend_class_entry **pce);
void zend_class_add_ref(zend_class_entry **ce);
@@ -655,6 +688,7 @@
ZEND_API void zend_mangle_property_name(char **dest, int *dest_length, const char *src1, int src1_length, const char *src2, int src2_length, int internal);
ZEND_API int zend_unmangle_property_name(const char *mangled_property, int mangled_property_len, const char **class_name, const char **prop_name);
+#define ZEND_ACCESSOR_DTOR (void (*)(void *)) zend_accessor_dtor
#define ZEND_FUNCTION_DTOR (void (*)(void *)) zend_function_dtor
#define ZEND_CLASS_DTOR (void (*)(void *)) destroy_zend_class
Index: Zend/zend_execute_API.c
===================================================================
--- Zend/zend_execute_API.c (revision 324439)
+++ Zend/zend_execute_API.c (working copy)
@@ -1624,33 +1624,53 @@
#define MAX_ABSTRACT_INFO_CNT 3
#define MAX_ABSTRACT_INFO_FMT "%s%s%s%s"
+#define MAX_ABS_ACCESSOR_INFO_FMT "%s%s%s%s%s%s"
+
#define DISPLAY_ABSTRACT_FN(idx) \
ai.afn[idx] ? ZEND_FN_SCOPE_NAME(ai.afn[idx]) : "", \
ai.afn[idx] ? "::" : "", \
ai.afn[idx] ? ai.afn[idx]->common.function_name : "", \
- ai.afn[idx] && ai.afn[idx + 1] ? ", " : (ai.afn[idx] && ai.cnt > MAX_ABSTRACT_INFO_CNT ? ", ..." : "")
+ ai.afn[idx] && ai.afn[idx + 1] ? ", " : (ai.afn[idx] && ai.afn_cnt > MAX_ABSTRACT_INFO_CNT ? ", ..." : "")
+
+#define DISPLAY_ABS_ACCESSOR_FN(idx) \
+ ai.abs_acc[idx] ? zend_accessor_type_string(ai.abs_acc[idx]->common.fn_flags) : "", \
+ ai.abs_acc[idx] ? " " : "", \
+ ai.abs_acc[idx] ? ZEND_FN_SCOPE_NAME(ai.abs_acc[idx]) : "", \
+ ai.abs_acc[idx] ? "::$" : "", \
+ ai.abs_acc[idx] ? ZEND_ACC_NAME(ai.abs_acc[idx]) : "", \
+ ai.abs_acc[idx] && ai.abs_acc[idx + 1] ? ", " : (ai.abs_acc[idx] && ai.abs_acc_count > MAX_ABSTRACT_INFO_CNT ? ", ..." : "")
typedef struct _zend_abstract_info {
zend_function *afn[MAX_ABSTRACT_INFO_CNT + 1];
- int cnt;
+ int afn_cnt;
+
+ zend_function *abs_acc[MAX_ABSTRACT_INFO_CNT + 1];
+ int abs_acc_count;
+
int ctor;
} zend_abstract_info;
static int zend_verify_abstract_class_function(zend_function *fn, zend_abstract_info *ai TSRMLS_DC) /* {{{ */
{
if (fn->common.fn_flags & ZEND_ACC_ABSTRACT) {
- if (ai->cnt < MAX_ABSTRACT_INFO_CNT) {
- ai->afn[ai->cnt] = fn;
+ if(fn->common.fn_flags & ZEND_ACC_IS_ACCESSOR) {
+ if (ai->abs_acc_count < MAX_ABSTRACT_INFO_CNT)
+ ai->abs_acc[ai->abs_acc_count] = fn;
+ ai->abs_acc_count++;
+ } else {
+ if (ai->afn_cnt < MAX_ABSTRACT_INFO_CNT) {
+ ai->afn[ai->afn_cnt] = fn;
}
if (fn->common.fn_flags & ZEND_ACC_CTOR) {
if (!ai->ctor) {
- ai->cnt++;
+ ai->afn_cnt++;
ai->ctor = 1;
} else {
- ai->afn[ai->cnt] = NULL;
+ ai->afn[ai->afn_cnt] = NULL;
}
} else {
- ai->cnt++;
+ ai->afn_cnt++;
+ }
}
}
return 0;
@@ -1666,15 +1686,24 @@
zend_hash_apply_with_argument(&ce->function_table, (apply_func_arg_t) zend_verify_abstract_class_function, &ai TSRMLS_CC);
- if (ai.cnt) {
+ if (ai.afn_cnt) {
zend_error(E_ERROR, "Class %s contains %d abstract method%s and must therefore be declared abstract or implement the remaining methods (" MAX_ABSTRACT_INFO_FMT MAX_ABSTRACT_INFO_FMT MAX_ABSTRACT_INFO_FMT ")",
- ce->name, ai.cnt,
- ai.cnt > 1 ? "s" : "",
+ ce->name, ai.afn_cnt,
+ ai.afn_cnt > 1 ? "s" : "",
DISPLAY_ABSTRACT_FN(0),
DISPLAY_ABSTRACT_FN(1),
DISPLAY_ABSTRACT_FN(2)
);
}
+ if (ai.abs_acc_count) {
+ zend_error(E_ERROR, "Class %s contains %d abstract accessor%s and must be declared abstract or implement the remaining accessors (" MAX_ABS_ACCESSOR_INFO_FMT MAX_ABS_ACCESSOR_INFO_FMT MAX_ABS_ACCESSOR_INFO_FMT ")",
+ ce->name, ai.abs_acc_count,
+ ai.abs_acc_count > 1 ? "s" : "",
+ DISPLAY_ABS_ACCESSOR_FN(0),
+ DISPLAY_ABS_ACCESSOR_FN(1),
+ DISPLAY_ABS_ACCESSOR_FN(2)
+ );
+ }
}
}
/* }}} */
Index: Zend/zend_globals.h
===================================================================
--- Zend/zend_globals.h (revision 324439)
+++ Zend/zend_globals.h (working copy)
@@ -124,6 +124,7 @@
zend_bool increment_lineno;
znode implementing_class;
+ znode *accessor_node;
zend_uint access_type;
Index: Zend/zend_language_parser.y
===================================================================
--- Zend/zend_language_parser.y (revision 324439)
+++ Zend/zend_language_parser.y (working copy)
@@ -178,6 +178,8 @@
%token T_HALT_COMPILER "__halt_compiler (T_HALT_COMPILER)"
%token T_CLASS "class (T_CLASS)"
%token T_TRAIT "trait (T_TRAIT)"
+%token T_READONLY "read-only (T_READONLY)"
+%token T_WRITEONLY "write-only (T_WRITEONLY)"
%token T_INTERFACE "interface (T_INTERFACE)"
%token T_EXTENDS "extends (T_EXTENDS)"
%token T_IMPLEMENTS "implements (T_IMPLEMENTS)"
@@ -573,7 +575,7 @@
class_statement:
- variable_modifiers { CG(access_type) = Z_LVAL($1.u.constant); } class_variable_declaration ';'
+ variable_modifiers { CG(access_type) = Z_LVAL($1.u.constant); } class_variable_accessor_declarations
| class_constant_declaration ';'
| trait_use_statement
| method_modifiers function is_reference T_STRING { zend_do_begin_function_declaration(&$2, &$4, 1, $3.op_type, &$1 TSRMLS_CC); } '('
@@ -664,6 +666,56 @@
| T_STATIC { Z_LVAL($$.u.constant) = ZEND_ACC_STATIC; }
| T_ABSTRACT { Z_LVAL($$.u.constant) = ZEND_ACC_ABSTRACT; }
| T_FINAL { Z_LVAL($$.u.constant) = ZEND_ACC_FINAL; }
+ | T_READONLY { Z_LVAL($$.u.constant) = ZEND_ACC_READONLY; }
+ | T_WRITEONLY { Z_LVAL($$.u.constant) = ZEND_ACC_WRITEONLY; }
+;
+
+accessors:
+ accessor_function
+ | accessor_function
+ accessor_function
+ | /* Empty */
+;
+
+accessor_modifiers:
+ /* empty */ { Z_LVAL($$.u.constant) = CG(access_type); }
+ | non_empty_accessor_modifiers { $$ = $1; }
+;
+
+non_empty_accessor_modifiers:
+ accessor_modifier { $$ = $1; }
+ | non_empty_accessor_modifiers accessor_modifier { Z_LVAL($$.u.constant) = zend_do_verify_access_types(&$1, &$2); }
+
+
+accessor_modifier:
+ T_PUBLIC { Z_LVAL($$.u.constant) = ZEND_ACC_PUBLIC; }
+ | T_PROTECTED { Z_LVAL($$.u.constant) = ZEND_ACC_PROTECTED; }
+ | T_PRIVATE { Z_LVAL($$.u.constant) = ZEND_ACC_PRIVATE; }
+ | T_STATIC { Z_LVAL($$.u.constant) = ZEND_ACC_STATIC; }
+ | T_FINAL { Z_LVAL($$.u.constant) = ZEND_ACC_FINAL; }
+;
+
+accessor_function:
+ accessor_modifiers T_STRING
+ { zend_do_begin_accessor_declaration(&$2, CG(accessor_node), &$1 TSRMLS_CC); }
+ '{' inner_statement_list '}'
+ { zend_do_end_accessor_declaration(&$2, CG(accessor_node), &$1, &$4 TSRMLS_CC); }
+ | accessor_modifiers T_STRING
+ {
+ zend_do_begin_accessor_declaration(&$2, CG(accessor_node), &$1 TSRMLS_CC);
+ zend_do_end_accessor_declaration(&$2, CG(accessor_node), &$1, NULL TSRMLS_CC);
+ /* efree(Z_STRVAL($2.u.constant)); */
+ }
+ ';'
+;
+
+class_variable_accessor_declarations:
+ T_VARIABLE '{'
+ { zend_declare_accessor(&$1 TSRMLS_CC); CG(accessor_node) = &$1; }
+ accessors
+ { efree($1.u.constant.value.str.val); }
+ '}'
+ | class_variable_declaration ';'
;
class_variable_declaration:
Index: Zend/zend_language_scanner.l
===================================================================
--- Zend/zend_language_scanner.l (revision 324439)
+++ Zend/zend_language_scanner.l (working copy)
@@ -1146,6 +1146,14 @@
return T_TRAIT;
}
+<ST_IN_SCRIPTING>"read-only" {
+ return T_READONLY;
+}
+
+<ST_IN_SCRIPTING>"write-only" {
+ return T_WRITEONLY;
+}
+
<ST_IN_SCRIPTING>"extends" {
return T_EXTENDS;
}
Index: Zend/zend_object_handlers.c
===================================================================
--- Zend/zend_object_handlers.c (revision 324439)
+++ Zend/zend_object_handlers.c (working copy)
@@ -51,6 +51,7 @@
if we have __call and method which is not part of the class function table is
called, we cal __call handler.
*/
+static union _zend_function *zend_std_get_method(zval **object_ptr, char *method_name, int method_len, const struct _zend_literal *key TSRMLS_DC);
ZEND_API void rebuild_object_properties(zend_object *zobj) /* {{{ */
{
@@ -131,7 +132,7 @@
}
/* }}} */
-static zval *zend_std_call_getter(zval *object, zval *member TSRMLS_DC) /* {{{ */
+static zval *zend_std_call_getter(zval *object, zval *member, zend_function *func TSRMLS_DC) /* {{{ */
{
zval *retval = NULL;
zend_class_entry *ce = Z_OBJCE_P(object);
@@ -144,7 +145,11 @@
SEPARATE_ARG_IF_REF(member);
- zend_call_method_with_1_params(&object, ce, &ce->__get, ZEND_GET_FUNC_NAME, &retval, member);
+ if (func->common.num_args == 1) {
+ zend_call_method_with_1_params(&object, ce, &func, func->common.function_name, &retval, member);
+ } else {
+ zend_call_method_with_0_params(&object, ce, &func, func->common.function_name, &retval);
+ }
zval_ptr_dtor(&member);
@@ -156,7 +161,7 @@
}
/* }}} */
-static int zend_std_call_setter(zval *object, zval *member, zval *value TSRMLS_DC) /* {{{ */
+static int zend_std_call_setter(zval *object, zval *member, zval *value, zend_function *func TSRMLS_DC) /* {{{ */
{
zval *retval = NULL;
int result;
@@ -171,7 +176,11 @@
it should return whether the call was successfull or not
*/
- zend_call_method_with_2_params(&object, ce, &ce->__set, ZEND_SET_FUNC_NAME, &retval, member, value);
+ if (func->common.num_args == 2) {
+ zend_call_method_with_2_params(&object, ce, &func, func->common.function_name, &retval, member, value);
+ } else {
+ zend_call_method_with_1_params(&object, ce, &func, func->common.function_name, &retval, value);
+ }
zval_ptr_dtor(&member);
zval_ptr_dtor(&value);
@@ -397,6 +406,62 @@
}
/* }}} */
+zend_function inline *zend_locate_getter(zval *object, zval *member, const zend_literal *key TSRMLS_DC) /* {{{ */
+{
+ zend_object *zobj = Z_OBJ_P(object);
+ zend_accessor_info **ai;
+ ulong hash_value = key ? key->hash_value : zend_get_hash_value(Z_STRVAL_P(member), Z_STRLEN_P(member) + 1);
+
+ if(zend_hash_quick_find(&zobj->ce->accessors, Z_STRVAL_P(member), Z_STRLEN_P(member) + 1, hash_value, (void**) &ai) == SUCCESS) {
+ if((*ai)->flags & ZEND_ACC_WRITEONLY) {
+ if((*ai)->setter) {
+ zend_error_noreturn(E_ERROR, "Cannot get write-only property %s::$%s.", ZEND_FN_SCOPE_NAME((*ai)->setter), Z_STRVAL_P(member));
+ }
+ } else if((*ai)->getter) {
+ /* If public getter, no access check required */
+ if((*ai)->getter->common.fn_flags & ZEND_ACC_PUBLIC) {
+ return (*ai)->getter;
+ }
+ return zend_std_get_method(&object, (char *)(*ai)->getter->common.function_name, strlen((*ai)->getter->common.function_name), NULL TSRMLS_CC);
+ }
+ if((*ai)->setter) {
+ zend_error_noreturn(E_ERROR, "Cannot get property %s::$%s, no getter defined.", ZEND_FN_SCOPE_NAME((*ai)->setter), Z_STRVAL_P(member));
+ }
+ /* This technically shouldn't ever happen, this would mean that an accessor is defined but there is no getter or setter */
+ zend_error_noreturn(E_ERROR, "Cannot get property $%s, no getter or setter defined.", Z_STRVAL_P(member));
+ }
+ return zobj->ce->__get;
+}
+/* }}} */
+
+zend_function inline *zend_locate_setter(zval *object, zval *member, zend_guard *guard, const zend_literal *key TSRMLS_DC) /* {{{ */
+{
+ zend_object *zobj = Z_OBJ_P(object);
+ zend_accessor_info **ai;
+ ulong hash_value = key ? key->hash_value : zend_get_hash_value(Z_STRVAL_P(member), Z_STRLEN_P(member) + 1);
+
+ if(zend_hash_quick_find(&zobj->ce->accessors, Z_STRVAL_P(member), Z_STRLEN_P(member) + 1, hash_value, (void**) &ai) == SUCCESS) {
+ if((*ai)->flags & ZEND_ACC_READONLY) {
+ if((*ai)->getter) {
+ zend_error_noreturn(E_ERROR, "Cannot set read-only property %s::$%s.", ZEND_FN_SCOPE_NAME((*ai)->getter), Z_STRVAL_P(member));
+ }
+ } else if((*ai)->setter) {
+ /* If public setter, no access check required */
+ if((*ai)->setter->common.fn_flags & ZEND_ACC_PUBLIC) {
+ return (*ai)->setter;
+ }
+ return zend_std_get_method(&object, (char *)(*ai)->setter->common.function_name, strlen((*ai)->setter->common.function_name), NULL TSRMLS_CC);
+ }
+ if((*ai)->getter) {
+ zend_error_noreturn(E_ERROR, "Cannot set property %s::$%s, no setter defined.", ZEND_FN_SCOPE_NAME((*ai)->getter), Z_STRVAL_P(member));
+ }
+ /* This technically shouldn't ever happen, this would mean that an accessor is defined but there is no getter or setter */
+ zend_error_noreturn(E_ERROR, "Cannot set property $%s, no getter or setter defined.", Z_STRVAL_P(member));
+ }
+ return zobj->ce->__set;
+}
+/* }}} */
+
zval *zend_std_read_property(zval *object, zval *member, int type, const zend_literal *key TSRMLS_DC) /* {{{ */
{
zend_object *zobj;
@@ -434,18 +499,19 @@
(*(retval = &zobj->properties_table[property_info->offset]) == NULL)) :
(UNEXPECTED(!zobj->properties) ||
UNEXPECTED(zend_hash_quick_find(zobj->properties, property_info->name, property_info->name_length+1, property_info->h, (void **) &retval) == FAILURE)))) {
+
zend_guard *guard = NULL;
+ zend_function *getter = zend_locate_getter(object, member, key TSRMLS_CC);
- if (zobj->ce->__get &&
- zend_get_property_guard(zobj, property_info, member, &guard) == SUCCESS &&
- !guard->in_get) {
+ if ( getter != NULL && zend_get_property_guard(zobj, property_info, member, &guard) == SUCCESS && !guard->in_get) {
/* have getter - try with it! */
+
Z_ADDREF_P(object);
if (PZVAL_IS_REF(object)) {
SEPARATE_ZVAL(&object);
}
guard->in_get = 1; /* prevent circular getting */
- rv = zend_std_call_getter(object, member TSRMLS_CC);
+ rv = zend_std_call_getter(object, member, getter TSRMLS_CC);
guard->in_get = 0;
if (rv) {
@@ -556,15 +622,17 @@
} else {
zend_guard *guard = NULL;
- if (zobj->ce->__set &&
- zend_get_property_guard(zobj, property_info, member, &guard) == SUCCESS &&
- !guard->in_set) {
+ zend_get_property_guard(zobj, property_info, member, &guard);
+
+ zend_function *setter = zend_locate_setter(object, member, guard, key TSRMLS_CC);
+
+ if (setter && guard && !guard->in_set) {
Z_ADDREF_P(object);
if (PZVAL_IS_REF(object)) {
SEPARATE_ZVAL(&object);
}
guard->in_set = 1; /* prevent circular setting */
- if (zend_std_call_setter(object, member, value TSRMLS_CC) != SUCCESS) {
+ if (zend_std_call_setter(object, member, value, setter TSRMLS_CC) != SUCCESS) {
/* for now, just ignore it - __set should take care of warnings, etc. */
}
guard->in_set = 0;
@@ -1026,9 +1094,13 @@
if (zobj->ce->__call) {
fbc = zend_get_user_call_function(zobj->ce, method_name, method_len);
} else {
+ if(fbc->op_array.fn_flags & ZEND_ACC_IS_ACCESSOR) {
+ zend_error_noreturn(E_ERROR, "Cannot %s %s property %s::$%s from context '%s'", zend_accessor_type_string(fbc->op_array.fn_flags), zend_visibility_string(fbc->common.fn_flags), ZEND_FN_SCOPE_NAME(fbc), ZEND_ACC_NAME(fbc), EG(scope) ? EG(scope)->name : "");
+ } else {
zend_error_noreturn(E_ERROR, "Call to %s method %s::%s() from context '%s'", zend_visibility_string(fbc->common.fn_flags), ZEND_FN_SCOPE_NAME(fbc), method_name, EG(scope) ? EG(scope)->name : "");
}
}
+ }
} else {
/* Ensure that we haven't overridden a private function and end up calling
* the overriding public function...
@@ -1052,11 +1124,15 @@
if (zobj->ce->__call) {
fbc = zend_get_user_call_function(zobj->ce, method_name, method_len);
} else {
+ if(fbc->op_array.fn_flags & ZEND_ACC_IS_ACCESSOR) {
+ zend_error_noreturn(E_ERROR, "Cannot %s %s property %s::$%s from context '%s'", zend_accessor_type_string(fbc->common.fn_flags), zend_visibility_string(fbc->common.fn_flags), ZEND_FN_SCOPE_NAME(fbc), ZEND_ACC_NAME(fbc), EG(scope) ? EG(scope)->name : "");
+ } else {
zend_error_noreturn(E_ERROR, "Call to %s method %s::%s() from context '%s'", zend_visibility_string(fbc->common.fn_flags), ZEND_FN_SCOPE_NAME(fbc), method_name, EG(scope) ? EG(scope)->name : "");
}
}
}
}
+ }
if (UNEXPECTED(!key)) {
free_alloca(lc_method_name, use_heap);
@@ -1193,9 +1269,13 @@
if (ce->__callstatic) {
fbc = zend_get_user_callstatic_function(ce, function_name_strval, function_name_strlen);
} else {
+ if(fbc->op_array.fn_flags & ZEND_ACC_IS_ACCESSOR) {
+ zend_error_noreturn(E_ERROR, "Cannot %s %s property %s::$%s from context '%s'", zend_accessor_type_string(fbc->common.fn_flags), zend_visibility_string(fbc->common.fn_flags), ZEND_FN_SCOPE_NAME(fbc), ZEND_ACC_NAME(fbc), EG(scope) ? EG(scope)->name : "");
+ } else {
zend_error_noreturn(E_ERROR, "Call to %s method %s::%s() from context '%s'", zend_visibility_string(fbc->common.fn_flags), ZEND_FN_SCOPE_NAME(fbc), function_name_strval, EG(scope) ? EG(scope)->name : "");
}
}
+ }
} else if ((fbc->common.fn_flags & ZEND_ACC_PROTECTED)) {
/* Ensure that if we're calling a protected function, we're allowed to do so.
*/
@@ -1203,10 +1283,14 @@
if (ce->__callstatic) {
fbc = zend_get_user_callstatic_function(ce, function_name_strval, function_name_strlen);
} else {
+ if(fbc->op_array.fn_flags & ZEND_ACC_IS_ACCESSOR) {
+ zend_error_noreturn(E_ERROR, "Cannot %s %s property %s::$%s from context '%s'", zend_accessor_type_string(fbc->common.fn_flags), zend_visibility_string(fbc->common.fn_flags), ZEND_FN_SCOPE_NAME(fbc), ZEND_ACC_NAME(fbc), EG(scope) ? EG(scope)->name : "");
+ } else {
zend_error_noreturn(E_ERROR, "Call to %s method %s::%s() from context '%s'", zend_visibility_string(fbc->common.fn_flags), ZEND_FN_SCOPE_NAME(fbc), function_name_strval, EG(scope) ? EG(scope)->name : "");
}
}
}
+ }
if (UNEXPECTED(!key)) {
free_alloca(lc_function_name, use_heap);
@@ -1412,9 +1496,12 @@
result = zend_is_true(rv);
zval_ptr_dtor(&rv);
if (has_set_exists && result) {
- if (EXPECTED(!EG(exception)) && zobj->ce->__get && !guard->in_get) {
+
+ zend_function *getter = zend_locate_getter(object, member, key TSRMLS_CC);
+
+ if (EXPECTED(!EG(exception)) && getter && !guard->in_get) {
guard->in_get = 1;
- rv = zend_std_call_getter(object, member TSRMLS_CC);
+ rv = zend_std_call_getter(object, member, getter TSRMLS_CC);
guard->in_get = 0;
if (rv) {
Z_ADDREF_P(rv);
Index: Zend/zend_object_handlers.h
===================================================================
--- Zend/zend_object_handlers.h (revision 324439)
+++ Zend/zend_object_handlers.h (working copy)
@@ -156,6 +156,7 @@
ZEND_API HashTable *zend_std_get_debug_info(zval *object, int *is_temp 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 zval *zend_std_read_property(zval *object, zval *member, int type, const struct _zend_literal *key TSRMLS_DC);
ZEND_API void rebuild_object_properties(zend_object *zobj);
Index: Zend/zend_opcode.c
===================================================================
--- Zend/zend_opcode.c (revision 324439)
+++ Zend/zend_opcode.c (working copy)
@@ -124,6 +124,14 @@
destroy_zend_function(function TSRMLS_CC);
}
+ZEND_API void zend_accessor_dtor(zend_accessor_info **ai)
+{
+ if((*ai)->doc_comment) {
+ efree((void*)(*ai)->doc_comment);
+ }
+ efree(*ai);
+}
+
static void zend_cleanup_op_array_data(zend_op_array *op_array)
{
if (op_array->static_variables) {
@@ -291,6 +299,7 @@
efree(ce->default_static_members_table);
}
zend_hash_destroy(&ce->properties_info);
+ zend_hash_destroy(&ce->accessors);
str_efree(ce->name);
zend_hash_destroy(&ce->function_table);
zend_hash_destroy(&ce->constants_table);