Skip to content

Commit 8075bde

Browse files
committed
Add initial support for clone with
1 parent 44cb7aa commit 8075bde

31 files changed

+2804
-605
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
--TEST--
2+
Test that the property initializer list is required when "with" is given
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
8+
public function withProperties()
9+
{
10+
return clone $this with;
11+
}
12+
}
13+
14+
?>
15+
--EXPECTF--
16+
Parse error: syntax error, unexpected token ";", expecting "{" in %s on line %d
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
--TEST--
2+
Test that the property initializer list cannot contain integer keys
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
8+
public function withProperties()
9+
{
10+
return clone $this with {1: "value"};
11+
}
12+
}
13+
14+
?>
15+
--EXPECTF--
16+
Parse error: syntax error, unexpected integer "1" in %s on line %d
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
--TEST--
2+
Test that the property initializer list can only contain literals
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
8+
public function withProperties()
9+
{
10+
$property = "string";
11+
12+
return clone $this with {$property: "value"};
13+
}
14+
}
15+
16+
?>
17+
--EXPECTF--
18+
Parse error: syntax error, unexpected variable "$property" in %s on line %d
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
Test that the clone property initializer respects visibility
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
8+
private $bar;
9+
}
10+
11+
$foo = new Foo();
12+
13+
try {
14+
$foo = clone $foo with {bar: 1};
15+
} catch (Error $exception) {
16+
echo $exception->getMessage() . "\n";
17+
}
18+
19+
?>
20+
--EXPECT--
21+
Cannot access private property Foo::$bar
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--TEST--
2+
Test that the "clone with" initializer list cannot contain the same property twice
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
8+
public function __construct(
9+
public readonly int $bar
10+
) {}
11+
12+
public function with(int $bar, stdClass $baz)
13+
{
14+
return clone $this with {
15+
bar: 1,
16+
bar: 2,
17+
};
18+
}
19+
}
20+
21+
?>
22+
--EXPECTF--
23+
Fatal error: Initializer list cannot contain the same property twice in %s on line %d
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
--TEST--
2+
Test that declared properties can be initialized during cloning
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
8+
public int $property1;
9+
public string $property2;
10+
11+
public function withProperties()
12+
{
13+
return clone $this with {
14+
property1: 1,
15+
property2: "foo",
16+
};
17+
}
18+
}
19+
20+
$foo = new Foo();
21+
var_dump($foo);
22+
$bar = $foo->withProperties();
23+
var_dump($bar);
24+
25+
?>
26+
--EXPECTF--
27+
object(Foo)#1 (%d) {
28+
["property1"]=>
29+
uninitialized(int)
30+
["property2"]=>
31+
uninitialized(string)
32+
}
33+
object(Foo)#2 (%d) {
34+
["property1"]=>
35+
int(1)
36+
["property2"]=>
37+
string(3) "foo"
38+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--TEST--
2+
Test that dynamic properties can be initialized during cloning
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
8+
public function withProperties()
9+
{
10+
return clone $this with {
11+
property1: 1,
12+
property2: "foo",
13+
};
14+
}
15+
}
16+
17+
$foo = new Foo();
18+
var_dump($foo);
19+
20+
try {
21+
$bar = $foo->withProperties();
22+
} catch (Exception $e) {
23+
echo $e->getMessage() . "\n";
24+
}
25+
26+
var_dump($bar);
27+
28+
?>
29+
--EXPECTF--
30+
object(Foo)#1 (0) {
31+
}
32+
33+
Deprecated: Creation of dynamic property Foo::$property1 is deprecated in %s on line %d
34+
35+
Deprecated: Creation of dynamic property Foo::$property2 is deprecated in %s on line %d
36+
object(Foo)#2 (2) {
37+
["property1"]=>
38+
int(1)
39+
["property2"]=>
40+
string(3) "foo"
41+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--TEST--
2+
Test that the property initializer list can be empty
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
8+
public function withProperties()
9+
{
10+
return clone $this with {};
11+
}
12+
}
13+
14+
$foo = new Foo();
15+
var_dump($foo);
16+
$bar = $foo->withProperties();
17+
var_dump($bar);
18+
19+
?>
20+
--EXPECT--
21+
object(Foo)#1 (0) {
22+
}
23+
object(Foo)#2 (0) {
24+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
Test that "clone with" is still chainable
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
8+
public $bar = 1;
9+
10+
public function withProperties()
11+
{
12+
return (clone $this with {})->bar;
13+
}
14+
}
15+
16+
$foo = new Foo();
17+
var_dump($foo);
18+
$bar = $foo->withProperties();
19+
var_dump($bar);
20+
21+
?>
22+
--EXPECT--
23+
object(Foo)#1 (1) {
24+
["bar"]=>
25+
int(1)
26+
}
27+
int(1)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--TEST--
2+
Test that the "clone with" works with dynamic properties
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
8+
}
9+
10+
$foo = new Foo();
11+
$foo = clone $foo with {bar: 1, baz: ""};
12+
var_dump($foo);
13+
14+
?>
15+
--EXPECTF--
16+
Deprecated: Creation of dynamic property Foo::$bar is deprecated in %s on line %d
17+
18+
Deprecated: Creation of dynamic property Foo::$baz is deprecated in %s on line %d
19+
object(Foo)#2 (%d) {
20+
["bar"]=>
21+
int(1)
22+
["baz"]=>
23+
string(0) ""
24+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--TEST--
2+
Test that "clone with" works with expressions as property values
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
8+
public $bar;
9+
public $baz;
10+
}
11+
12+
$foo = new Foo();
13+
$foo = clone $foo with {bar: new stdClass(), baz: strpos("abc", "b")};
14+
var_dump($foo);
15+
16+
?>
17+
--EXPECT--
18+
object(Foo)#2 (2) {
19+
["bar"]=>
20+
object(stdClass)#3 (0) {
21+
}
22+
["baz"]=>
23+
int(1)
24+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
--TEST--
2+
Test that "clone with" works with expressions as clone operand
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
8+
public static function create()
9+
{
10+
return new Foo();
11+
}
12+
}
13+
14+
$foo = clone Foo::create() with {};
15+
var_dump($foo);
16+
17+
?>
18+
--EXPECT--
19+
object(Foo)#2 (0) {
20+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
--TEST--
2+
Test that "clone with" works with readonly properties
3+
--FILE--
4+
<?php
5+
6+
class Foo
7+
{
8+
public function __construct(
9+
public readonly int $bar,
10+
public readonly stdClass $baz,
11+
) {}
12+
13+
public function with(int $bar, stdClass $baz)
14+
{
15+
return clone $this with {
16+
bar: 1,
17+
baz: new stdClass(),
18+
};
19+
}
20+
}
21+
22+
$stdClass = new stdClass();
23+
24+
$foo = new Foo(0, $stdClass);
25+
var_dump($foo);
26+
27+
var_dump($foo->with(1, $stdClass));
28+
29+
?>
30+
--EXPECTF--
31+
object(Foo)#%d (%d) {
32+
["bar"]=>
33+
int(0)
34+
["baz"]=>
35+
object(stdClass)#%d (%d) {
36+
}
37+
}
38+
object(Foo)#%d (%d) {
39+
["bar"]=>
40+
int(1)
41+
["baz"]=>
42+
object(stdClass)#%d (%d) {
43+
}
44+
}

Zend/tests/readonly_props/readonly_clone_error1.phpt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ class Foo {
1010

1111
public function __clone()
1212
{
13-
$this->bar++;
13+
$this->bar = 2;
1414
var_dump($this);
15-
$this->bar++;
15+
$this->bar = 3;
1616
}
1717
}
1818

@@ -24,10 +24,13 @@ try {
2424
echo $exception->getMessage() . "\n";
2525
}
2626

27+
echo "done";
28+
2729
?>
2830
--EXPECT--
2931
object(Foo)#2 (1) {
3032
["bar"]=>
3133
int(2)
3234
}
3335
Cannot modify readonly property Foo::$bar
36+
done

Zend/zend_ast.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1842,6 +1842,11 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
18421842
case ZEND_AST_MATCH_ARM_LIST:
18431843
zend_ast_export_list(str, (zend_ast_list*)ast, 0, 0, indent);
18441844
break;
1845+
case ZEND_AST_PROPERTY_INITIALIZER_LIST:
1846+
smart_str_appends(str, " {");
1847+
zend_ast_export_list(str, (zend_ast_list*)ast, 0, 0, indent);
1848+
smart_str_appends(str, " }");
1849+
break;
18451850
case ZEND_AST_CLOSURE_USES:
18461851
smart_str_appends(str, " use(");
18471852
zend_ast_export_var_list(str, (zend_ast_list*)ast, indent);

0 commit comments

Comments
 (0)