mirror of
https://github.com/php/php-src.git
synced 2025-08-15 13:38:49 +02:00
RFC: Clone with v2 (#18747)
RFC: https://wiki.php.net/rfc/clone_with_v2 Co-authored-by: Volker Dusch <volker@tideways-gmbh.com>
This commit is contained in:
parent
8629256dc7
commit
7f4076bae0
27 changed files with 701 additions and 10 deletions
3
NEWS
3
NEWS
|
@ -2,6 +2,9 @@ PHP NEWS
|
|||
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||
?? ??? ????, PHP 8.5.0alpha3
|
||||
|
||||
- Core:
|
||||
. Add clone-with support to the clone() function. (timwolla, edorian)
|
||||
|
||||
- Curl:
|
||||
. Add support for CURLINFO_CONN_ID in curl_getinfo() (thecaliskan)
|
||||
. Add support for CURLINFO_QUEUE_TIME_T in curl_getinfo() (thecaliskan)
|
||||
|
|
|
@ -408,7 +408,8 @@ PHP 8.5 UPGRADE NOTES
|
|||
. get_exception_handler() allows retrieving the current user-defined exception
|
||||
handler function.
|
||||
RFC: https://wiki.php.net/rfc/get-error-exception-handler
|
||||
. The clone language construct is now a function.
|
||||
. The clone language construct is now a function and supports reassigning
|
||||
(readonly) properties during cloning via the new $withProperties parameter.
|
||||
RFC: https://wiki.php.net/rfc/clone_with_v2
|
||||
|
||||
- Curl:
|
||||
|
|
71
Zend/tests/clone/clone_with_001.phpt
Normal file
71
Zend/tests/clone/clone_with_001.phpt
Normal file
|
@ -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
Zend/tests/clone/clone_with_002.phpt
Normal file
114
Zend/tests/clone/clone_with_002.phpt
Normal file
|
@ -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
Zend/tests/clone/clone_with_003.phpt
Normal file
23
Zend/tests/clone/clone_with_003.phpt
Normal file
|
@ -0,0 +1,23 @@
|
|||
--TEST--
|
||||
Clone with supports property hooks
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Clazz {
|
||||
public string $hooked = 'default' {
|
||||
set {
|
||||
$this->hooked = strtoupper($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$c = new Clazz();
|
||||
|
||||
var_dump(clone($c, [ 'hooked' => 'updated' ]));
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
object(Clazz)#%d (1) {
|
||||
["hooked"]=>
|
||||
string(7) "UPDATED"
|
||||
}
|
82
Zend/tests/clone/clone_with_004.phpt
Normal file
82
Zend/tests/clone/clone_with_004.phpt
Normal file
|
@ -0,0 +1,82 @@
|
|||
--TEST--
|
||||
Clone with evaluation order
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Clazz {
|
||||
public string $hooked = 'default' {
|
||||
set {
|
||||
echo __FUNCTION__, PHP_EOL;
|
||||
|
||||
$this->hooked = strtoupper($value);
|
||||
}
|
||||
}
|
||||
|
||||
public string $maxLength {
|
||||
set {
|
||||
echo __FUNCTION__, PHP_EOL;
|
||||
|
||||
if (strlen($value) > 5) {
|
||||
throw new \Exception('Length exceeded');
|
||||
}
|
||||
|
||||
$this->maxLength = $value;
|
||||
}
|
||||
}
|
||||
|
||||
public string $minLength {
|
||||
set {
|
||||
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
Zend/tests/clone/clone_with_005.phpt
Normal file
64
Zend/tests/clone/clone_with_005.phpt
Normal file
|
@ -0,0 +1,64 @@
|
|||
--TEST--
|
||||
Clone with error handling
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Clazz {
|
||||
public string $hooked = 'default' {
|
||||
set {
|
||||
echo __FUNCTION__, PHP_EOL;
|
||||
|
||||
$this->hooked = strtoupper($value);
|
||||
}
|
||||
}
|
||||
|
||||
public string $maxLength {
|
||||
set {
|
||||
echo __FUNCTION__, PHP_EOL;
|
||||
|
||||
if (strlen($value) > 5) {
|
||||
throw new \Exception('Length exceeded');
|
||||
}
|
||||
|
||||
$this->maxLength = $value;
|
||||
}
|
||||
}
|
||||
|
||||
public string $minLength {
|
||||
set {
|
||||
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
Zend/tests/clone/clone_with_006.phpt
Normal file
16
Zend/tests/clone/clone_with_006.phpt
Normal file
|
@ -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
|
29
Zend/tests/clone/clone_with_007.phpt
Normal file
29
Zend/tests/clone/clone_with_007.phpt
Normal file
|
@ -0,0 +1,29 @@
|
|||
--TEST--
|
||||
Clone with supports __clone
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Clazz {
|
||||
public function __construct(
|
||||
public string $foo,
|
||||
public string $bar,
|
||||
) { }
|
||||
|
||||
public function __clone() {
|
||||
$this->foo = 'foo updated in __clone';
|
||||
$this->bar = 'bar updated in __clone';
|
||||
}
|
||||
}
|
||||
|
||||
$c = new Clazz('foo', 'bar');
|
||||
|
||||
var_dump(clone($c, [ 'foo' => 'foo updated in clone-with' ]));
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
object(Clazz)#%d (2) {
|
||||
["foo"]=>
|
||||
string(25) "foo updated in clone-with"
|
||||
["bar"]=>
|
||||
string(22) "bar updated in __clone"
|
||||
}
|
40
Zend/tests/clone/clone_with_008.phpt
Normal file
40
Zend/tests/clone/clone_with_008.phpt
Normal file
|
@ -0,0 +1,40 @@
|
|||
--TEST--
|
||||
Clone with readonly
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
readonly class Clazz {
|
||||
public function __construct(
|
||||
public public(set) string $a,
|
||||
public public(set) string $b,
|
||||
) { }
|
||||
|
||||
public function __clone() {
|
||||
$this->b = '__clone';
|
||||
}
|
||||
}
|
||||
|
||||
$c = new Clazz('default', 'default');
|
||||
|
||||
var_dump(clone($c, [ 'a' => "with" ]));
|
||||
|
||||
try {
|
||||
var_dump(clone($c, [ 'b' => "with" ]));
|
||||
} catch (Throwable $e) {
|
||||
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
object(Clazz)#%d (2) {
|
||||
["a"]=>
|
||||
string(4) "with"
|
||||
["b"]=>
|
||||
string(7) "__clone"
|
||||
}
|
||||
object(Clazz)#%d (2) {
|
||||
["a"]=>
|
||||
string(7) "default"
|
||||
["b"]=>
|
||||
string(4) "with"
|
||||
}
|
72
Zend/tests/clone/clone_with_009.phpt
Normal file
72
Zend/tests/clone/clone_with_009.phpt
Normal file
|
@ -0,0 +1,72 @@
|
|||
--TEST--
|
||||
Clone with lazy objects
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class C {
|
||||
public $a = 1;
|
||||
|
||||
public function __construct() {
|
||||
}
|
||||
}
|
||||
|
||||
function test(string $name, object $obj) {
|
||||
printf("# %s:\n", $name);
|
||||
|
||||
$reflector = new ReflectionClass($obj::class);
|
||||
$clone = clone($obj, [ 'a' => 2 ]);
|
||||
|
||||
var_dump($reflector->isUninitializedLazyObject($obj));
|
||||
var_dump($obj);
|
||||
var_dump($reflector->isUninitializedLazyObject($clone));
|
||||
var_dump($clone);
|
||||
}
|
||||
|
||||
$reflector = new ReflectionClass(C::class);
|
||||
|
||||
$obj = $reflector->newLazyGhost(function ($obj) {
|
||||
var_dump("initializer");
|
||||
$obj->__construct();
|
||||
});
|
||||
|
||||
test('Ghost', $obj);
|
||||
|
||||
$obj = $reflector->newLazyProxy(function ($obj) {
|
||||
var_dump("initializer");
|
||||
return new C();
|
||||
});
|
||||
|
||||
test('Proxy', $obj);
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
# Ghost:
|
||||
string(11) "initializer"
|
||||
bool(false)
|
||||
object(C)#%d (1) {
|
||||
["a"]=>
|
||||
int(1)
|
||||
}
|
||||
bool(false)
|
||||
object(C)#%d (1) {
|
||||
["a"]=>
|
||||
int(2)
|
||||
}
|
||||
# Proxy:
|
||||
string(11) "initializer"
|
||||
bool(false)
|
||||
lazy proxy object(C)#%d (1) {
|
||||
["instance"]=>
|
||||
object(C)#%d (1) {
|
||||
["a"]=>
|
||||
int(1)
|
||||
}
|
||||
}
|
||||
bool(false)
|
||||
lazy proxy object(C)#%d (1) {
|
||||
["instance"]=>
|
||||
object(C)#%d (1) {
|
||||
["a"]=>
|
||||
int(2)
|
||||
}
|
||||
}
|
21
Zend/tests/clone/clone_with_010.phpt
Normal file
21
Zend/tests/clone/clone_with_010.phpt
Normal file
|
@ -0,0 +1,21 @@
|
|||
--TEST--
|
||||
Clone with native classes
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
try {
|
||||
var_dump(clone(new \Random\Engine\Secure(), [ 'with' => "something" ]));
|
||||
} catch (Throwable $e) {
|
||||
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
|
||||
try {
|
||||
var_dump(clone(new \Random\Engine\Xoshiro256StarStar(), [ 'with' => "something" ]));
|
||||
} catch (Throwable $e) {
|
||||
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
Error: Trying to clone an uncloneable object of class Random\Engine\Secure
|
||||
Error: Cannot create dynamic property Random\Engine\Xoshiro256StarStar::$with
|
18
Zend/tests/clone/clone_with_011.phpt
Normal file
18
Zend/tests/clone/clone_with_011.phpt
Normal file
|
@ -0,0 +1,18 @@
|
|||
--TEST--
|
||||
Clone with name mangling
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Foo {
|
||||
private string $bar = 'default';
|
||||
}
|
||||
|
||||
try {
|
||||
var_dump(clone(new Foo(), ["\0Foo\0bar" => 'updated']));
|
||||
} catch (Throwable $e) {
|
||||
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
Error: Cannot access property starting with "\0"
|
35
Zend/tests/clone/clone_with_012.phpt
Normal file
35
Zend/tests/clone/clone_with_012.phpt
Normal file
|
@ -0,0 +1,35 @@
|
|||
--TEST--
|
||||
Clone with property hook updating readonly property
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
class Clazz {
|
||||
public string $foo {
|
||||
set {
|
||||
$this->foo = $value;
|
||||
$this->bar = 'bar updated in hook';
|
||||
}
|
||||
}
|
||||
|
||||
public public(set) readonly string $bar;
|
||||
}
|
||||
|
||||
$f = new Clazz();
|
||||
|
||||
var_dump(clone($f, ['foo' => 'foo updated in clone-with']));
|
||||
|
||||
try {
|
||||
var_dump(clone($f, ['foo' => 'foo updated in clone-with', 'bar' => 'bar updated in clone-with']));
|
||||
} catch (Throwable $e) {
|
||||
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
object(Clazz)#%d (2) {
|
||||
["foo"]=>
|
||||
string(25) "foo updated in clone-with"
|
||||
["bar"]=>
|
||||
string(19) "bar updated in hook"
|
||||
}
|
||||
Error: Cannot modify readonly property Clazz::$bar
|
31
Zend/tests/clone/clone_with_013.phpt
Normal file
31
Zend/tests/clone/clone_with_013.phpt
Normal file
|
@ -0,0 +1,31 @@
|
|||
--TEST--
|
||||
Clone with references
|
||||
--FILE--
|
||||
<?php
|
||||
|
||||
$x = new stdClass();
|
||||
|
||||
$ref = 'reference';
|
||||
$with = ['x' => &$ref];
|
||||
|
||||
try {
|
||||
var_dump(clone($x, $with));
|
||||
} catch (Throwable $e) {
|
||||
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
|
||||
unset($ref);
|
||||
|
||||
try {
|
||||
var_dump(clone($x, $with));
|
||||
} catch (Throwable $e) {
|
||||
echo $e::class, ": ", $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECTF--
|
||||
Error: Cannot assign by reference when cloning with updated properties
|
||||
object(stdClass)#%d (1) {
|
||||
["x"]=>
|
||||
string(9) "reference"
|
||||
}
|
|
@ -72,9 +72,12 @@ zend_result zend_startup_builtin_functions(void) /* {{{ */
|
|||
ZEND_FUNCTION(clone)
|
||||
{
|
||||
zend_object *zobj;
|
||||
HashTable *with = (HashTable*)&zend_empty_array;
|
||||
|
||||
ZEND_PARSE_PARAMETERS_START(1, 1)
|
||||
ZEND_PARSE_PARAMETERS_START(1, 2)
|
||||
Z_PARAM_OBJ(zobj)
|
||||
Z_PARAM_OPTIONAL
|
||||
Z_PARAM_ARRAY_HT(with)
|
||||
ZEND_PARSE_PARAMETERS_END();
|
||||
|
||||
/* clone() also exists as the ZEND_CLONE OPcode and both implementations must be kept in sync. */
|
||||
|
@ -95,7 +98,16 @@ ZEND_FUNCTION(clone)
|
|||
}
|
||||
|
||||
zend_object *cloned;
|
||||
cloned = zobj->handlers->clone_obj(zobj);
|
||||
if (zend_hash_num_elements(with) > 0) {
|
||||
if (UNEXPECTED(!zobj->handlers->clone_obj_with)) {
|
||||
zend_throw_error(NULL, "Cloning objects of class %s with updated properties is not supported", ZSTR_VAL(ce->name));
|
||||
RETURN_THROWS();
|
||||
}
|
||||
|
||||
cloned = zobj->handlers->clone_obj_with(zobj, scope, with);
|
||||
} else {
|
||||
cloned = zobj->handlers->clone_obj(zobj);
|
||||
}
|
||||
|
||||
ZEND_ASSERT(cloned || EG(exception));
|
||||
if (EXPECTED(cloned)) {
|
||||
|
|
|
@ -8,7 +8,7 @@ class stdClass
|
|||
}
|
||||
|
||||
/** @refcount 1 */
|
||||
function _clone(object $object): object {}
|
||||
function _clone(object $object, array $withProperties = []): object {}
|
||||
|
||||
function exit(string|int $status = 0): never {}
|
||||
|
||||
|
|
3
Zend/zend_builtin_functions_arginfo.h
generated
3
Zend/zend_builtin_functions_arginfo.h
generated
|
@ -1,8 +1,9 @@
|
|||
/* This is a generated file, edit the .stub.php file instead.
|
||||
* Stub hash: 12327caa3fe940ccef68ed99f9278982dc0173a5 */
|
||||
* Stub hash: 0be87bb6b55e100c022e70aa6f3b17001725784f */
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_clone, 0, 1, IS_OBJECT, 0)
|
||||
ZEND_ARG_TYPE_INFO(0, object, IS_OBJECT, 0)
|
||||
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, withProperties, IS_ARRAY, 0, "[]")
|
||||
ZEND_END_ARG_INFO()
|
||||
|
||||
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_exit, 0, 0, IS_NEVER, 0)
|
||||
|
|
|
@ -31,6 +31,7 @@ static const zend_object_handlers iterator_object_handlers = {
|
|||
iter_wrapper_free,
|
||||
iter_wrapper_dtor,
|
||||
NULL, /* clone_obj */
|
||||
NULL, /* clone_obj_with */
|
||||
NULL, /* prop read */
|
||||
NULL, /* prop write */
|
||||
NULL, /* read dim */
|
||||
|
|
|
@ -2541,6 +2541,7 @@ ZEND_API const zend_object_handlers std_object_handlers = {
|
|||
zend_object_std_dtor, /* free_obj */
|
||||
zend_objects_destroy_object, /* dtor_obj */
|
||||
zend_objects_clone_obj, /* clone_obj */
|
||||
zend_objects_clone_obj_with, /* clone_obj_with */
|
||||
|
||||
zend_std_read_property, /* read_property */
|
||||
zend_std_write_property, /* write_property */
|
||||
|
|
|
@ -180,6 +180,7 @@ typedef void (*zend_object_free_obj_t)(zend_object *object);
|
|||
typedef void (*zend_object_dtor_obj_t)(zend_object *object);
|
||||
|
||||
typedef zend_object* (*zend_object_clone_obj_t)(zend_object *object);
|
||||
typedef zend_object* (*zend_object_clone_obj_with_t)(zend_object *object, const zend_class_entry *scope, const HashTable *properties);
|
||||
|
||||
/* Get class name for display in var_dump and other debugging functions.
|
||||
* Must be defined and must return a non-NULL value. */
|
||||
|
@ -209,6 +210,7 @@ struct _zend_object_handlers {
|
|||
zend_object_free_obj_t free_obj; /* required */
|
||||
zend_object_dtor_obj_t dtor_obj; /* required */
|
||||
zend_object_clone_obj_t clone_obj; /* optional */
|
||||
zend_object_clone_obj_with_t clone_obj_with; /* optional */
|
||||
zend_object_read_property_t read_property; /* required */
|
||||
zend_object_write_property_t write_property; /* required */
|
||||
zend_object_read_dimension_t read_dimension; /* required */
|
||||
|
|
|
@ -276,6 +276,52 @@ ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object,
|
|||
}
|
||||
}
|
||||
|
||||
ZEND_API zend_object *zend_objects_clone_obj_with(zend_object *old_object, const zend_class_entry *scope, const HashTable *properties)
|
||||
{
|
||||
zend_object *new_object = old_object->handlers->clone_obj(old_object);
|
||||
|
||||
if (EXPECTED(!EG(exception))) {
|
||||
/* Unlock readonly properties once more. */
|
||||
if (ZEND_CLASS_HAS_READONLY_PROPS(new_object->ce)) {
|
||||
for (uint32_t i = 0; i < new_object->ce->default_properties_count; i++) {
|
||||
zval* prop = OBJ_PROP_NUM(new_object, i);
|
||||
Z_PROP_FLAG_P(prop) |= IS_PROP_REINITABLE;
|
||||
}
|
||||
}
|
||||
|
||||
const zend_class_entry *old_scope = EG(fake_scope);
|
||||
|
||||
EG(fake_scope) = scope;
|
||||
|
||||
ZEND_HASH_FOREACH_KEY_VAL(properties, zend_ulong num_key, zend_string *key, zval *val) {
|
||||
if (UNEXPECTED(Z_ISREF_P(val))) {
|
||||
if (Z_REFCOUNT_P(val) == 1) {
|
||||
val = Z_REFVAL_P(val);
|
||||
} else {
|
||||
zend_throw_error(NULL, "Cannot assign by reference when cloning with updated properties");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (UNEXPECTED(key == NULL)) {
|
||||
key = zend_long_to_str(num_key);
|
||||
new_object->handlers->write_property(new_object, key, val, NULL);
|
||||
zend_string_release_ex(key, false);
|
||||
} else {
|
||||
new_object->handlers->write_property(new_object, key, val, NULL);
|
||||
}
|
||||
|
||||
if (UNEXPECTED(EG(exception))) {
|
||||
break;
|
||||
}
|
||||
} ZEND_HASH_FOREACH_END();
|
||||
|
||||
EG(fake_scope) = old_scope;
|
||||
}
|
||||
|
||||
return new_object;
|
||||
}
|
||||
|
||||
ZEND_API zend_object *zend_objects_clone_obj(zend_object *old_object)
|
||||
{
|
||||
zend_object *new_object;
|
||||
|
|
|
@ -30,6 +30,7 @@ ZEND_API void ZEND_FASTCALL zend_objects_clone_members(zend_object *new_object,
|
|||
ZEND_API void zend_object_std_dtor(zend_object *object);
|
||||
ZEND_API void zend_objects_destroy_object(zend_object *object);
|
||||
ZEND_API zend_object *zend_objects_clone_obj(zend_object *object);
|
||||
ZEND_API zend_object *zend_objects_clone_obj_with(zend_object *object, const zend_class_entry *scope, const HashTable *properties);
|
||||
|
||||
void zend_object_dtor_dynamic_properties(zend_object *object);
|
||||
void zend_object_dtor_property(zend_object *object, zval *p);
|
||||
|
|
|
@ -6006,7 +6006,8 @@ ZEND_VM_COLD_CONST_HANDLER(110, ZEND_CLONE, CONST|TMPVAR|UNUSED|THIS|CV, ANY)
|
|||
SAVE_OPLINE();
|
||||
obj = GET_OP1_OBJ_ZVAL_PTR_UNDEF(BP_VAR_R);
|
||||
|
||||
/* ZEND_CLONE also exists as the clone() function and both implementations must be kept in sync. */
|
||||
/* ZEND_CLONE also exists as the clone() function and both implementations must be kept in sync.
|
||||
* The OPcode intentionally does not support a clone-with property list to keep it simple. */
|
||||
|
||||
do {
|
||||
if (OP1_TYPE == IS_CONST ||
|
||||
|
|
12
Zend/zend_vm_execute.h
generated
12
Zend/zend_vm_execute.h
generated
|
@ -5180,7 +5180,8 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CLONE_SPEC_CONST_
|
|||
SAVE_OPLINE();
|
||||
obj = RT_CONSTANT(opline, opline->op1);
|
||||
|
||||
/* ZEND_CLONE also exists as the clone() function and both implementations must be kept in sync. */
|
||||
/* ZEND_CLONE also exists as the clone() function and both implementations must be kept in sync.
|
||||
* The OPcode intentionally does not support a clone-with property list to keep it simple. */
|
||||
|
||||
do {
|
||||
if (IS_CONST == IS_CONST ||
|
||||
|
@ -15427,7 +15428,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CLONE_SPEC_TMPVAR_HANDLER(ZEND
|
|||
SAVE_OPLINE();
|
||||
obj = _get_zval_ptr_var(opline->op1.var EXECUTE_DATA_CC);
|
||||
|
||||
/* ZEND_CLONE also exists as the clone() function and both implementations must be kept in sync. */
|
||||
/* ZEND_CLONE also exists as the clone() function and both implementations must be kept in sync.
|
||||
* The OPcode intentionally does not support a clone-with property list to keep it simple. */
|
||||
|
||||
do {
|
||||
if ((IS_TMP_VAR|IS_VAR) == IS_CONST ||
|
||||
|
@ -33522,7 +33524,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CLONE_SPEC_UNUSED_HANDLER(ZEND
|
|||
SAVE_OPLINE();
|
||||
obj = &EX(This);
|
||||
|
||||
/* ZEND_CLONE also exists as the clone() function and both implementations must be kept in sync. */
|
||||
/* ZEND_CLONE also exists as the clone() function and both implementations must be kept in sync.
|
||||
* The OPcode intentionally does not support a clone-with property list to keep it simple. */
|
||||
|
||||
do {
|
||||
if (IS_UNUSED == IS_CONST ||
|
||||
|
@ -41041,7 +41044,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CLONE_SPEC_CV_HANDLER(ZEND_OPC
|
|||
SAVE_OPLINE();
|
||||
obj = EX_VAR(opline->op1.var);
|
||||
|
||||
/* ZEND_CLONE also exists as the clone() function and both implementations must be kept in sync. */
|
||||
/* ZEND_CLONE also exists as the clone() function and both implementations must be kept in sync.
|
||||
* The OPcode intentionally does not support a clone-with property list to keep it simple. */
|
||||
|
||||
do {
|
||||
if (IS_CV == IS_CONST ||
|
||||
|
|
|
@ -514,6 +514,7 @@ zend_object_handlers php_com_object_handlers = {
|
|||
php_com_object_free_storage,
|
||||
zend_objects_destroy_object,
|
||||
php_com_object_clone,
|
||||
NULL, /* clone_with */
|
||||
com_property_read,
|
||||
com_property_write,
|
||||
com_read_dimension,
|
||||
|
|
|
@ -402,6 +402,7 @@ zend_object_handlers php_com_saproxy_handlers = {
|
|||
saproxy_free_storage,
|
||||
zend_objects_destroy_object,
|
||||
saproxy_clone,
|
||||
NULL, /* clone_with */
|
||||
saproxy_property_read,
|
||||
saproxy_property_write,
|
||||
saproxy_read_dimension,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue