Skip to content

Commit e890a1c

Browse files
committed
Add __equals magic method and equals object handler
1 parent 12dc8e7 commit e890a1c

14 files changed

+3029
-22
lines changed

Zend/zend.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ struct _zend_class_entry {
140140
union _zend_function *__tostring;
141141
union _zend_function *__debugInfo;
142142
union _zend_function *__compareTo;
143+
union _zend_function *__equals;
143144
union _zend_function *serialize_func;
144145
union _zend_function *unserialize_func;
145146

Zend/zend_API.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2158,7 +2158,7 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio
21582158
int count=0, unload=0;
21592159
HashTable *target_function_table = function_table;
21602160
int error_type;
2161-
zend_function *ctor = NULL, *dtor = NULL, *clone = NULL, *__get = NULL, *__set = NULL, *__unset = NULL, *__isset = NULL, *__call = NULL, *__callstatic = NULL, *__tostring = NULL, *__debugInfo = NULL, *__compareTo = NULL;
2161+
zend_function *ctor = NULL, *dtor = NULL, *clone = NULL, *__get = NULL, *__set = NULL, *__unset = NULL, *__isset = NULL, *__call = NULL, *__callstatic = NULL, *__tostring = NULL, *__debugInfo = NULL, *__compareTo = NULL, *__equals = NULL;
21622162
zend_string *lowercase_name;
21632163
size_t fname_len;
21642164
const char *lc_class_name = NULL;
@@ -2350,6 +2350,8 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio
23502350
__tostring = reg_function;
23512351
} else if (zend_string_equals_literal(lowercase_name, ZEND_COMPARETO_FUNC_NAME)) {
23522352
__compareTo = reg_function;
2353+
} else if (zend_string_equals_literal(lowercase_name, ZEND_EQUALS_FUNC_NAME)) {
2354+
__equals = reg_function;
23532355
} else if (zend_string_equals_literal(lowercase_name, ZEND_GET_FUNC_NAME)) {
23542356
__get = reg_function;
23552357
scope->ce_flags |= ZEND_ACC_USE_GUARDS;
@@ -2405,6 +2407,7 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio
24052407
scope->__isset = __isset;
24062408
scope->__debugInfo = __debugInfo;
24072409
scope->__compareTo = __compareTo;
2410+
scope->__equals = __equals;
24082411
if (ctor) {
24092412
ctor->common.fn_flags |= ZEND_ACC_CTOR;
24102413
if (ctor->common.fn_flags & ZEND_ACC_STATIC) {
@@ -2477,6 +2480,11 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio
24772480
zend_error(error_type, "Method %s::%s() cannot be static", ZSTR_VAL(scope->name), ZSTR_VAL(__compareTo->common.function_name));
24782481
}
24792482
}
2483+
if (__equals) {
2484+
if (__equals->common.fn_flags & ZEND_ACC_STATIC) {
2485+
zend_error(error_type, "Method %s::%s() cannot be static", ZSTR_VAL(scope->name), ZSTR_VAL(__equals->common.function_name));
2486+
}
2487+
}
24802488
if (ctor && ctor->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE && ctor->common.fn_flags & ZEND_ACC_CTOR) {
24812489
zend_error_noreturn(E_CORE_ERROR, "Constructor %s::%s() cannot declare a return type", ZSTR_VAL(scope->name), ZSTR_VAL(ctor->common.function_name));
24822490
}

Zend/zend_API.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ typedef struct _zend_fcall_info_cache {
215215
class_container.__isset = handle_propisset; \
216216
class_container.__debugInfo = NULL; \
217217
class_container.__compareTo = NULL; \
218+
class_container.__equals = NULL; \
218219
class_container.serialize_func = NULL; \
219220
class_container.unserialize_func = NULL; \
220221
class_container.parent = NULL; \

Zend/zend_compile.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1764,6 +1764,7 @@ ZEND_API void zend_initialize_class_data(zend_class_entry *ce, zend_bool nullify
17641764
ce->unserialize_func = NULL;
17651765
ce->__debugInfo = NULL;
17661766
ce->__compareTo = NULL;
1767+
ce->__equals = NULL;
17671768
if (ce->type == ZEND_INTERNAL_CLASS) {
17681769
ce->info.internal.module = NULL;
17691770
ce->info.internal.builtin_functions = NULL;
@@ -5823,6 +5824,11 @@ void zend_begin_method_decl(zend_op_array *op_array, zend_string *name, zend_boo
58235824
zend_error(E_WARNING, "The magic method __compareTo() must have "
58245825
"public visibility and cannot be static");
58255826
}
5827+
} else if (zend_string_equals_literal(lcname, ZEND_EQUALS_FUNC_NAME)) {
5828+
if (!is_public || is_static) {
5829+
zend_error(E_WARNING, "The magic method __equals() must have "
5830+
"public visibility and cannot be static");
5831+
}
58265832
}
58275833
} else {
58285834
if (!in_trait && zend_string_equals_ci(lcname, ce->name)) {
@@ -5898,6 +5904,12 @@ void zend_begin_method_decl(zend_op_array *op_array, zend_string *name, zend_boo
58985904
"public visibility and cannot be static");
58995905
}
59005906
ce->__compareTo = (zend_function *) op_array;
5907+
} else if (zend_string_equals_literal(lcname, ZEND_EQUALS_FUNC_NAME)) {
5908+
if (!is_public || is_static) {
5909+
zend_error(E_WARNING, "The magic method __equals() must have "
5910+
"public visibility and cannot be static");
5911+
}
5912+
ce->__equals = (zend_function *) op_array;
59015913
} else if (!is_static) {
59025914
op_array->fn_flags |= ZEND_ACC_ALLOW_STATIC;
59035915
}

Zend/zend_compile.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -978,6 +978,7 @@ END_EXTERN_C()
978978
#define ZEND_INVOKE_FUNC_NAME "__invoke"
979979
#define ZEND_DEBUGINFO_FUNC_NAME "__debuginfo"
980980
#define ZEND_COMPARETO_FUNC_NAME "__compareto"
981+
#define ZEND_EQUALS_FUNC_NAME "__equals"
981982

982983
/* The following constants may be combined in CG(compiler_options)
983984
* to change the default compiler behavior */

Zend/zend_inheritance.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,9 @@ static void do_inherit_parent_constructor(zend_class_entry *ce) /* {{{ */
141141
if (EXPECTED(!ce->__compareTo)) {
142142
ce->__compareTo = ce->parent->__compareTo;
143143
}
144+
if (EXPECTED(!ce->__equals)) {
145+
ce->__equals = ce->parent->__equals;
146+
}
144147

145148
if (ce->constructor) {
146149
if (ce->parent->constructor && UNEXPECTED(ce->parent->constructor->common.fn_flags & ZEND_ACC_FINAL)) {
@@ -1149,6 +1152,8 @@ static void zend_add_magic_methods(zend_class_entry* ce, zend_string* mname, zen
11491152
ce->__debugInfo = fe;
11501153
} else if (zend_string_equals_literal(mname, ZEND_COMPARETO_FUNC_NAME)) {
11511154
ce->__compareTo = fe;
1155+
} else if (zend_string_equals_literal(mname, ZEND_EQUALS_FUNC_NAME)) {
1156+
ce->__equals = fe;
11521157
} else if (ZSTR_LEN(ce->name) == ZSTR_LEN(mname)) {
11531158
zend_string *lowercase_name = zend_string_tolower(ce->name);
11541159
lowercase_name = zend_new_interned_string(lowercase_name);

Zend/zend_object_handlers.c

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1551,21 +1551,35 @@ ZEND_API zend_function *zend_std_get_constructor(zend_object *zobj) /* {{{ */
15511551
}
15521552
/* }}} */
15531553

1554+
ZEND_API int zend_std_equals(zval *result, zval *op1, zval *op2) /* {{{ */
1555+
{
1556+
if (Z_TYPE_P(op1) == IS_OBJECT) {
1557+
zend_class_entry *ce = Z_OBJCE_P(op1);
1558+
1559+
if (ce->__equals) {
1560+
zend_call_method_with_1_params(op1, ce, &ce->__equals, ZEND_EQUALS_FUNC_NAME, result, op2);
1561+
1562+
if (Z_TYPE_P(result) != IS_UNDEF) {
1563+
return SUCCESS;
1564+
}
1565+
}
1566+
}
1567+
1568+
return FAILURE;
1569+
}
1570+
/* }}} */
1571+
15541572
ZEND_API int zend_std_compare(zval *result, zval *op1, zval *op2) /* {{{ */
15551573
{
15561574
if (Z_TYPE_P(op1) == IS_OBJECT) {
15571575
zend_class_entry *ce = Z_OBJCE_P(op1);
15581576

15591577
if (ce->__compareTo) {
15601578
zend_call_method_with_1_params(op1, ce, &ce->__compareTo, ZEND_COMPARETO_FUNC_NAME, result, op2);
1561-
1562-
/* Returning null indicates that the comparison could not be made or
1563-
* isn't supported, falling back to default behaviour. */
1564-
if (Z_TYPE_P(result) == IS_NULL || Z_TYPE_P(result) == IS_UNDEF) {
1565-
return FAILURE;
1579+
1580+
if (Z_TYPE_P(result) != IS_UNDEF) {
1581+
return SUCCESS;
15661582
}
1567-
1568-
return SUCCESS;
15691583
}
15701584
}
15711585

@@ -1886,6 +1900,7 @@ ZEND_API const zend_object_handlers std_object_handlers = {
18861900
zend_std_get_gc, /* get_gc */
18871901
NULL, /* do_operation */
18881902
zend_std_compare, /* compare */
1903+
zend_std_equals, /* equals */
18891904
};
18901905

18911906
/*

Zend/zend_object_handlers.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,11 @@ typedef zend_object* (*zend_object_clone_obj_t)(zval *object);
112112
* Must be defined and must return a non-NULL value. */
113113
typedef zend_string *(*zend_object_get_class_name_t)(const zend_object *object);
114114

115+
/* Comparison and equality
116+
*/
115117
typedef int (*zend_object_compare_t)(zval *object1, zval *object2);
116118
typedef int (*zend_object_compare_zvals_t)(zval *result, zval *op1, zval *op2);
119+
typedef int (*zend_object_equals_t)(zval *result, zval *op1, zval *op2);
117120

118121
/* Cast an object to some other type.
119122
* readobj and retval must point to distinct zvals.
@@ -162,6 +165,7 @@ struct _zend_object_handlers {
162165
zend_object_get_gc_t get_gc;
163166
zend_object_do_operation_t do_operation;
164167
zend_object_compare_zvals_t compare;
168+
zend_object_equals_t equals;
165169
};
166170

167171
BEGIN_EXTERN_C()

Zend/zend_operators.c

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2038,6 +2038,15 @@ ZEND_API int ZEND_FASTCALL compare_function(zval *result, zval *op1, zval *op2)
20382038
return SUCCESS;
20392039

20402040
case TYPE_PAIR(IS_OBJECT, IS_NULL):
2041+
if (Z_OBJ_HANDLER_P(op1, equals)) {
2042+
if (Z_OBJ_HANDLER_P(op1, equals)(result, op1, op2) == SUCCESS) {
2043+
if (i_zend_is_true(result)) {
2044+
ZVAL_LONG(result, 0);
2045+
return SUCCESS;
2046+
}
2047+
}
2048+
}
2049+
20412050
if (Z_OBJ_HANDLER_P(op1, compare)) {
20422051
if (Z_OBJ_HANDLER_P(op1, compare)(result, op1, op2) == SUCCESS) {
20432052
convert_compare_result_to_long(result);
@@ -2049,6 +2058,15 @@ ZEND_API int ZEND_FASTCALL compare_function(zval *result, zval *op1, zval *op2)
20492058
return SUCCESS;
20502059

20512060
case TYPE_PAIR(IS_NULL, IS_OBJECT):
2061+
if (Z_OBJ_HANDLER_P(op2, equals)) {
2062+
if (Z_OBJ_HANDLER_P(op2, equals)(result, op2, op1) == SUCCESS) {
2063+
if (i_zend_is_true(result)) {
2064+
ZVAL_LONG(result, 0);
2065+
return SUCCESS;
2066+
}
2067+
}
2068+
}
2069+
20522070
if (Z_OBJ_HANDLER_P(op2, compare)) {
20532071
if (Z_OBJ_HANDLER_P(op2, compare)(result, op2, op1) == SUCCESS) {
20542072
convert_compare_result_to_long(result);
@@ -2069,17 +2087,29 @@ ZEND_API int ZEND_FASTCALL compare_function(zval *result, zval *op1, zval *op2)
20692087
continue;
20702088
}
20712089

2072-
if (Z_TYPE_P(op1) == IS_OBJECT && Z_OBJ_HANDLER_P(op1, compare)) {
2090+
if (Z_TYPE_P(op1) == IS_OBJECT) {
2091+
if (Z_OBJ_HANDLER_P(op1, equals) && Z_OBJ_HANDLER_P(op1, equals)(result, op1, op2) == SUCCESS) {
2092+
if (i_zend_is_true(result)) {
2093+
ZVAL_LONG(result, 0);
2094+
return SUCCESS;
2095+
}
2096+
}
20732097

2074-
/* If compare fails, we want to fall through to give the
2075-
* other comparison functions a chance to run. */
2076-
if (Z_OBJ_HANDLER_P(op1, compare)(result, op1, op2) == SUCCESS) {
2098+
if (Z_OBJ_HANDLER_P(op1, compare) && Z_OBJ_HANDLER_P(op1, compare)(result, op1, op2) == SUCCESS) {
20772099
convert_compare_result_to_long(result);
20782100
return SUCCESS;
20792101
}
2102+
}
2103+
2104+
if (Z_TYPE_P(op2) == IS_OBJECT) {
2105+
if (Z_OBJ_HANDLER_P(op2, equals) && Z_OBJ_HANDLER_P(op2, equals)(result, op2, op1) == SUCCESS) {
2106+
if (i_zend_is_true(result)) {
2107+
ZVAL_LONG(result, 0);
2108+
return SUCCESS;
2109+
}
2110+
}
20802111

2081-
} else if (Z_TYPE_P(op2) == IS_OBJECT && Z_OBJ_HANDLER_P(op2, compare)) {
2082-
if (Z_OBJ_HANDLER_P(op2, compare)(result, op1, op2) == SUCCESS) {
2112+
if (Z_OBJ_HANDLER_P(op2, compare) && Z_OBJ_HANDLER_P(op2, compare)(result, op2, op1) == SUCCESS) {
20832113
convert_compare_result_to_long(result);
20842114
Z_LVAL_P(result) *= -1;
20852115
return SUCCESS;
@@ -2097,6 +2127,7 @@ ZEND_API int ZEND_FASTCALL compare_function(zval *result, zval *op1, zval *op2)
20972127
return SUCCESS;
20982128
}
20992129
}
2130+
21002131
if (Z_TYPE_P(op1) == IS_OBJECT) {
21012132
if (Z_OBJ_HT_P(op1)->get) {
21022133
zval rv;
@@ -2116,6 +2147,7 @@ ZEND_API int ZEND_FASTCALL compare_function(zval *result, zval *op1, zval *op2)
21162147
return ret;
21172148
}
21182149
}
2150+
21192151
if (Z_TYPE_P(op2) == IS_OBJECT) {
21202152
if (Z_OBJ_HT_P(op2)->get) {
21212153
zval rv;
@@ -2138,6 +2170,7 @@ ZEND_API int ZEND_FASTCALL compare_function(zval *result, zval *op1, zval *op2)
21382170
return SUCCESS;
21392171
}
21402172
}
2173+
21412174
if (!converted) {
21422175
if (Z_TYPE_P(op1) == IS_NULL || Z_TYPE_P(op1) == IS_FALSE) {
21432176
ZVAL_LONG(result, zval_is_true(op2) ? -1 : 0);
@@ -2246,20 +2279,37 @@ ZEND_API int ZEND_FASTCALL is_not_identical_function(zval *result, zval *op1, zv
22462279

22472280
ZEND_API int ZEND_FASTCALL is_equal_function(zval *result, zval *op1, zval *op2) /* {{{ */
22482281
{
2282+
if (Z_TYPE_P(op1) == IS_OBJECT && Z_OBJ_HANDLER_P(op1, equals)) {
2283+
if (Z_OBJ_HANDLER_P(op1, equals)(result, op1, op2) == SUCCESS) {
2284+
convert_to_boolean(result);
2285+
return SUCCESS;
2286+
}
2287+
}
2288+
2289+
if (Z_TYPE_P(op2) == IS_OBJECT && Z_OBJ_HANDLER_P(op2, equals)) {
2290+
if (Z_OBJ_HANDLER_P(op2, equals)(result, op2, op1) == SUCCESS) {
2291+
convert_to_boolean(result);
2292+
return SUCCESS;
2293+
}
2294+
}
2295+
22492296
if (compare_function(result, op1, op2) == FAILURE) {
22502297
return FAILURE;
22512298
}
2299+
22522300
ZVAL_BOOL(result, (Z_LVAL_P(result) == 0));
22532301
return SUCCESS;
22542302
}
22552303
/* }}} */
22562304

22572305
ZEND_API int ZEND_FASTCALL is_not_equal_function(zval *result, zval *op1, zval *op2) /* {{{ */
22582306
{
2259-
if (compare_function(result, op1, op2) == FAILURE) {
2307+
if (is_equal_function(result, op1, op2) == FAILURE) {
22602308
return FAILURE;
22612309
}
2262-
ZVAL_BOOL(result, (Z_LVAL_P(result) != 0));
2310+
2311+
/* */
2312+
ZVAL_BOOL(result, Z_TYPE_P(result) == IS_TRUE ? 0 : 1);
22632313
return SUCCESS;
22642314
}
22652315
/* }}} */

Zend/zend_operators.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -753,8 +753,8 @@ static zend_always_inline int fast_equal_check_function(zval *op1, zval *op2)
753753
return zend_fast_equal_strings(Z_STR_P(op1), Z_STR_P(op2));
754754
}
755755
}
756-
compare_function(&result, op1, op2);
757-
return Z_LVAL(result) == 0;
756+
is_equal_function(&result, op1, op2);
757+
return Z_TYPE(result) == IS_TRUE;
758758
}
759759

760760
static zend_always_inline int fast_equal_check_long(zval *op1, zval *op2)
@@ -763,8 +763,8 @@ static zend_always_inline int fast_equal_check_long(zval *op1, zval *op2)
763763
if (EXPECTED(Z_TYPE_P(op2) == IS_LONG)) {
764764
return Z_LVAL_P(op1) == Z_LVAL_P(op2);
765765
}
766-
compare_function(&result, op1, op2);
767-
return Z_LVAL(result) == 0;
766+
is_equal_function(&result, op1, op2);
767+
return Z_TYPE(result) == IS_TRUE;
768768
}
769769

770770
static zend_always_inline int fast_equal_check_string(zval *op1, zval *op2)
@@ -773,8 +773,8 @@ static zend_always_inline int fast_equal_check_string(zval *op1, zval *op2)
773773
if (EXPECTED(Z_TYPE_P(op2) == IS_STRING)) {
774774
return zend_fast_equal_strings(Z_STR_P(op1), Z_STR_P(op2));
775775
}
776-
compare_function(&result, op1, op2);
777-
return Z_LVAL(result) == 0;
776+
is_equal_function(&result, op1, op2);
777+
return Z_TYPE(result) == IS_TRUE;
778778
}
779779

780780
static zend_always_inline int fast_is_identical_function(zval *op1, zval *op2)

0 commit comments

Comments
 (0)