Skip to content

RFC: Clone with v2 #18747

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Prev Previous commit
Next Next commit
zend_builtin_functions: Add withProperties parameter to clone() f…
…unction
  • Loading branch information
TimWolla committed Jun 30, 2025
commit df4a52423ead3fd5a533891262de5e2b4d79793e
71 changes: 71 additions & 0 deletions Zend/tests/clone/clone_with_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
--TEST--
Clone with basic
--FILE--
<?php

class Dummy { }

$x = new stdClass();

$foo = 'FOO';
$bar = new Dummy();
$array = [
'baz' => 'BAZ',
'array' => [1, 2, 3],
];

var_dump(clone $x);
var_dump(clone($x));
var_dump(clone($x, [ 'foo' => $foo, 'bar' => $bar ]));
var_dump(clone($x, $array));
var_dump(clone($x, [ 'obj' => $x ]));

var_dump(clone($x, [
'abc',
'def',
new Dummy(),
'named' => 'value',
]));

?>
--EXPECTF--
object(stdClass)#%d (0) {
}
object(stdClass)#%d (0) {
}
object(stdClass)#%d (2) {
["foo"]=>
string(3) "FOO"
["bar"]=>
object(Dummy)#%d (0) {
}
}
object(stdClass)#%d (2) {
["baz"]=>
string(3) "BAZ"
["array"]=>
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}
}
object(stdClass)#%d (1) {
["obj"]=>
object(stdClass)#%d (0) {
}
}
object(stdClass)#%d (4) {
["0"]=>
string(3) "abc"
["1"]=>
string(3) "def"
["2"]=>
object(Dummy)#%d (0) {
}
["named"]=>
string(5) "value"
}
114 changes: 114 additions & 0 deletions Zend/tests/clone/clone_with_002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
--TEST--
Clone with respects visiblity
--FILE--
<?php

class P {
public $a = 'default';
protected $b = 'default';
private $c = 'default';
public private(set) string $d = 'default';

public function m1() {
return clone($this, [ 'a' => 'updated A', 'b' => 'updated B', 'c' => 'updated C', 'd' => 'updated D' ]);
}
}

class C extends P {
public function m2() {
return clone($this, [ 'a' => 'updated A', 'b' => 'updated B', 'c' => 'dynamic C' ]);
}

public function m3() {
return clone($this, [ 'd' => 'inaccessible' ]);
}
}

class Unrelated {
public function m3(P $p) {
return clone($p, [ 'b' => 'inaccessible' ]);
}
}

$p = new P();

var_dump(clone($p, [ 'a' => 'updated A' ]));
var_dump($p->m1());

$c = new C();
var_dump($c->m1());
var_dump($c->m2());
try {
var_dump($c->m3());
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
}

try {
var_dump(clone($p, [ 'b' => 'inaccessible' ]));
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
}

try {
var_dump(clone($p, [ 'd' => 'inaccessible' ]));
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
}

try {
var_dump((new Unrelated())->m3($p));
} catch (Error $e) {
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
}

?>
--EXPECTF--
object(P)#%d (4) {
["a"]=>
string(9) "updated A"
["b":protected]=>
string(7) "default"
["c":"P":private]=>
string(7) "default"
["d"]=>
string(7) "default"
}
object(P)#%d (4) {
["a"]=>
string(9) "updated A"
["b":protected]=>
string(9) "updated B"
["c":"P":private]=>
string(9) "updated C"
["d"]=>
string(9) "updated D"
}
object(C)#%d (4) {
["a"]=>
string(9) "updated A"
["b":protected]=>
string(9) "updated B"
["c":"P":private]=>
string(9) "updated C"
["d"]=>
string(9) "updated D"
}

Deprecated: Creation of dynamic property C::$c is deprecated in %s on line %d
object(C)#%d (5) {
["a"]=>
string(9) "updated A"
["b":protected]=>
string(9) "updated B"
["c":"P":private]=>
string(7) "default"
["d"]=>
string(7) "default"
["c"]=>
string(9) "dynamic C"
}
Error: Cannot modify private(set) property P::$d from scope C
Error: Cannot access protected property P::$b
Error: Cannot modify private(set) property P::$d from global scope
Error: Cannot access protected property P::$b
23 changes: 23 additions & 0 deletions Zend/tests/clone/clone_with_003.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
--TEST--
Clone with supports property hooks
--FILE--
<?php

class Clazz {
public string $hooked = 'default' {
set (string $value) {
$this->hooked = strtoupper($value);
}
}
}

$c = new Clazz();

var_dump(clone($c, [ 'hooked' => 'updated' ]));

?>
--EXPECTF--
object(Clazz)#%d (1) {
["hooked"]=>
string(7) "UPDATED"
}
82 changes: 82 additions & 0 deletions Zend/tests/clone/clone_with_004.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
--TEST--
Clone with evaluation order
--FILE--
<?php

class Clazz {
public string $hooked = 'default' {
set (string $value) {
echo __FUNCTION__, PHP_EOL;

$this->hooked = strtoupper($value);
}
}

public string $maxLength {
set (string $value) {
echo __FUNCTION__, PHP_EOL;

if (strlen($value) > 5) {
throw new \Exception('Length exceeded');
}

$this->maxLength = $value;
}
}

public string $minLength {
set (string $value) {
echo __FUNCTION__, PHP_EOL;

if (strlen($value) < 5) {
throw new \Exception('Length unsufficient');
}

$this->minLength = $value;
}
}
}

$c = new Clazz();

var_dump(clone($c, [ 'hooked' => 'updated' ]));
echo PHP_EOL;
var_dump(clone($c, [ 'hooked' => 'updated', 'maxLength' => 'abc', 'minLength' => 'abcdef' ]));
echo PHP_EOL;
var_dump(clone($c, [ 'minLength' => 'abcdef', 'hooked' => 'updated', 'maxLength' => 'abc' ]));

?>
--EXPECTF--
$hooked::set
object(Clazz)#%d (1) {
["hooked"]=>
string(7) "UPDATED"
["maxLength"]=>
uninitialized(string)
["minLength"]=>
uninitialized(string)
}

$hooked::set
$maxLength::set
$minLength::set
object(Clazz)#%d (3) {
["hooked"]=>
string(7) "UPDATED"
["maxLength"]=>
string(3) "abc"
["minLength"]=>
string(6) "abcdef"
}

$minLength::set
$hooked::set
$maxLength::set
object(Clazz)#%d (3) {
["hooked"]=>
string(7) "UPDATED"
["maxLength"]=>
string(3) "abc"
["minLength"]=>
string(6) "abcdef"
}
64 changes: 64 additions & 0 deletions Zend/tests/clone/clone_with_005.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
--TEST--
Clone with error handling
--FILE--
<?php

class Clazz {
public string $hooked = 'default' {
set (string $value) {
echo __FUNCTION__, PHP_EOL;

$this->hooked = strtoupper($value);
}
}

public string $maxLength {
set (string $value) {
echo __FUNCTION__, PHP_EOL;

if (strlen($value) > 5) {
throw new \Exception('Length exceeded');
}

$this->maxLength = $value;
}
}

public string $minLength {
set (string $value) {
echo __FUNCTION__, PHP_EOL;

if (strlen($value) < 5) {
throw new \Exception('Length insufficient');
}

$this->minLength = $value;
}
}
}

$c = new Clazz();

try {
var_dump(clone($c, [ 'hooked' => 'updated', 'maxLength' => 'abcdef', 'minLength' => 'abc' ]));
} catch (Throwable $e) {
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
}

echo PHP_EOL;

try {
var_dump(clone($c, [ 'hooked' => 'updated', 'minLength' => 'abc', 'maxLength' => 'abcdef' ]));
} catch (Throwable $e) {
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
}

?>
--EXPECT--
$hooked::set
$maxLength::set
Exception: Length exceeded

$hooked::set
$minLength::set
Exception: Length insufficient
16 changes: 16 additions & 0 deletions Zend/tests/clone/clone_with_006.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
Clone with error cases
--FILE--
<?php

$x = new stdClass();

try {
var_dump(clone($x, 1));
} catch (Throwable $e) {
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
}

?>
--EXPECT--
TypeError: clone(): Argument #2 ($withProperties) must be of type array, int given
Loading