Skip to content

Commit 1239b26

Browse files
committed
Add clone() function
1 parent b527f3b commit 1239b26

4 files changed

+91
-1
lines changed

Zend/zend_builtin_functions.c

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,84 @@ zend_result zend_startup_builtin_functions(void) /* {{{ */
6969
}
7070
/* }}} */
7171

72+
ZEND_FUNCTION(clone)
73+
{
74+
zend_object *zobj;
75+
zval *args;
76+
uint32_t argc;
77+
HashTable *named_params;
78+
79+
ZEND_PARSE_PARAMETERS_START(1, -1)
80+
Z_PARAM_OBJ(zobj)
81+
Z_PARAM_VARIADIC_WITH_NAMED(args, argc, named_params);
82+
ZEND_PARSE_PARAMETERS_END();
83+
84+
zend_class_entry *scope = zend_get_executed_scope();
85+
86+
zend_class_entry *ce = zobj->ce;
87+
zend_function *clone = ce->clone;
88+
89+
if (UNEXPECTED(zobj->handlers->clone_obj == NULL)) {
90+
zend_throw_error(NULL, "Trying to clone an uncloneable object of class %s", ZSTR_VAL(ce->name));
91+
RETURN_THROWS();
92+
}
93+
94+
if (clone && !(clone->common.fn_flags & ZEND_ACC_PUBLIC)) {
95+
if (clone->common.scope != scope) {
96+
if (UNEXPECTED(clone->common.fn_flags & ZEND_ACC_PRIVATE)
97+
|| UNEXPECTED(!zend_check_protected(zend_get_function_root_class(clone), scope))) {
98+
zend_throw_error(NULL, "Call to %s %s::__clone() from %s%s",
99+
zend_visibility_string(clone->common.fn_flags), ZSTR_VAL(clone->common.scope->name),
100+
scope ? "scope " : "global scope",
101+
scope ? ZSTR_VAL(scope->name) : ""
102+
);
103+
RETURN_THROWS();
104+
}
105+
}
106+
}
107+
108+
zend_object *cloned;
109+
if (zobj->handlers->clone_obj_with) {
110+
HashTable *params = named_params;
111+
if (UNEXPECTED(argc > 0)) {
112+
HashTable params;
113+
zend_hash_init(&params, argc + (named_params ? zend_hash_num_elements(named_params) : 0), NULL, ZVAL_PTR_DTOR, false);
114+
for (uint32_t i = 0; i < argc; i++) {
115+
zend_string *key = zend_long_to_str(i);
116+
zend_hash_update(&params, key, &args[i]);
117+
zend_string_release(key);
118+
}
119+
if (named_params != NULL) {
120+
zend_string *key;
121+
zval *val;
122+
ZEND_HASH_FOREACH_STR_KEY_VAL(named_params, key, val) {
123+
zend_hash_update(&params, key, val);
124+
} ZEND_HASH_FOREACH_END();
125+
}
126+
cloned = zobj->handlers->clone_obj_with(zobj, scope, &params);
127+
zend_hash_destroy(&params);
128+
} else {
129+
cloned = zobj->handlers->clone_obj_with(zobj, scope, params);
130+
}
131+
} else {
132+
if (UNEXPECTED(named_params || argc > 0)) {
133+
zend_throw_error(NULL, "Trying to clone an object with updated properties that is not compatible %s", ZSTR_VAL(ce->name));
134+
RETURN_THROWS();
135+
}
136+
cloned = zobj->handlers->clone_obj(zobj);
137+
}
138+
139+
if (EG(exception)) {
140+
if (cloned) {
141+
OBJ_RELEASE(cloned);
142+
}
143+
144+
RETURN_THROWS();
145+
}
146+
147+
RETURN_OBJ(cloned);
148+
}
149+
72150
ZEND_FUNCTION(exit)
73151
{
74152
zend_string *str = NULL;

Zend/zend_builtin_functions.stub.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ class stdClass
77
{
88
}
99

10+
function _clone(object $object, mixed ...$updatedProperties): object {}
11+
1012
function exit(string|int $status = 0): never {}
1113

1214
/** @alias exit */

Zend/zend_builtin_functions_arginfo.h

Lines changed: 8 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build/gen_stub.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -983,6 +983,9 @@ class FunctionName implements FunctionOrMethodName {
983983
private /* readonly */ Name $name;
984984

985985
public function __construct(Name $name) {
986+
if ($name->name === '_clone') {
987+
$name = new Name('clone', $name->getAttributes());
988+
}
986989
$this->name = $name;
987990
}
988991

0 commit comments

Comments
 (0)