Skip to content

Commit b988fe1

Browse files
committed
feat: allow hooks for backed properties
1 parent 6eae466 commit b988fe1

File tree

7 files changed

+168
-33
lines changed

7 files changed

+168
-33
lines changed

Zend/tests/property_hooks/gh15419_1.phpt

Lines changed: 0 additions & 12 deletions
This file was deleted.

Zend/tests/property_hooks/gh15419_2.phpt

Lines changed: 0 additions & 14 deletions
This file was deleted.
Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,73 @@
11
--TEST--
2-
Hooked properties cannot be readonly
2+
Backed readonly property may have nice hooks
33
--FILE--
44
<?php
55

6-
class Test {
7-
public readonly int $prop { get; set; }
6+
// class readonly
7+
final readonly class Foo
8+
{
9+
public function __construct(
10+
public array $values {
11+
set(array $value) => array_map(strtoupper(...), $value);
12+
},
13+
) {}
814
}
915

16+
// property readonly
17+
final class Foo2
18+
{
19+
public function __construct(
20+
public readonly array $values {
21+
set(array $value) => array_map(strtoupper(...), $value);
22+
},
23+
) {}
24+
}
25+
26+
// redundant readonly
27+
final readonly class Foo3
28+
{
29+
public function __construct(
30+
public readonly array $values {
31+
set(array $value) => array_map(strtoupper(...), $value);
32+
get => $this->makeNicer($this->values);
33+
},
34+
) {}
35+
36+
public function makeNicer(array $entries): array
37+
{
38+
return array_map(
39+
fn($i, $entry) => $entry . strtoupper(['', 'r', 'st'][$i]), array_keys($entries),
40+
$entries
41+
);
42+
}
43+
}
44+
45+
\var_dump(new Foo(['yo,', 'you', 'can'])->values);
46+
\var_dump(new Foo2(['just', 'do', 'things'])->values);
47+
\var_dump(new Foo3(['nice', 'nice', 'nice'])->values);
1048
?>
11-
--EXPECTF--
12-
Fatal error: Hooked properties cannot be readonly in %s on line %d
49+
--EXPECT--
50+
array(3) {
51+
[0]=>
52+
string(3) "YO,"
53+
[1]=>
54+
string(3) "YOU"
55+
[2]=>
56+
string(3) "CAN"
57+
}
58+
array(3) {
59+
[0]=>
60+
string(4) "JUST"
61+
[1]=>
62+
string(2) "DO"
63+
[2]=>
64+
string(6) "THINGS"
65+
}
66+
array(3) {
67+
[0]=>
68+
string(4) "NICE"
69+
[1]=>
70+
string(5) "NICER"
71+
[2]=>
72+
string(6) "NICEST"
73+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
--TEST--
2+
Backed readonly property may have hooks
3+
--FILE--
4+
<?php
5+
6+
// readonly property
7+
class Test1 {
8+
public readonly int $prop {
9+
get => $this->prop;
10+
set => $value;
11+
}
12+
13+
public function __construct(int $v) {
14+
$this->prop = $v;
15+
}
16+
}
17+
18+
$t = new Test1(42);
19+
var_dump($t->prop);
20+
try {
21+
$t->prop = 43;
22+
} catch (Error $e) {
23+
echo $e->getMessage(), "\n";
24+
}
25+
26+
// readonly class
27+
readonly class Test2 {
28+
public int $prop {
29+
get => $this->prop;
30+
set => $value;
31+
}
32+
33+
public function __construct(int $v) {
34+
$this->prop = $v;
35+
}
36+
}
37+
38+
$t = new Test2(42);
39+
var_dump($t->prop);
40+
try {
41+
$t->prop = 43;
42+
} catch (Error $e) {
43+
echo $e->getMessage(), "\n";
44+
}
45+
46+
// readonly class, promoted
47+
readonly class Test3 {
48+
public function __construct(
49+
public int $prop {
50+
get => $this->prop;
51+
set => $value;
52+
}
53+
) {}
54+
}
55+
56+
$t = new Test3(42);
57+
var_dump($t->prop);
58+
try {
59+
$t->prop = 43;
60+
} catch (Error $e) {
61+
echo $e->getMessage(), "\n";
62+
}
63+
?>
64+
--EXPECT--
65+
int(42)
66+
Cannot modify protected(set) readonly property Test1::$prop from global scope
67+
int(42)
68+
Cannot modify protected(set) readonly property Test2::$prop from global scope
69+
int(42)
70+
Cannot modify protected(set) readonly property Test3::$prop from global scope
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
--TEST--
2+
Virtual readonly property cannot have hooks
3+
--FILE--
4+
<?php
5+
6+
class Test {
7+
public readonly int $prop {
8+
get => 42;
9+
}
10+
}
11+
?>
12+
--EXPECTF--
13+
Fatal error: Hooked virtual properties cannot be readonly 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+
Virtual promoted property in readonly class cannot have hooks
3+
--FILE--
4+
<?php
5+
6+
readonly class Test {
7+
public function __construct(
8+
public int $prop {
9+
get => 42;
10+
}
11+
) {}
12+
}
13+
14+
?>
15+
--EXPECTF--
16+
Fatal error: Hooked virtual properties cannot be readonly in %s on line %d

Zend/zend_compile.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8497,8 +8497,9 @@ static void zend_compile_property_hooks(
84978497
{
84988498
zend_class_entry *ce = CG(active_class_entry);
84998499

8500-
if (prop_info->flags & ZEND_ACC_READONLY) {
8501-
zend_error_noreturn(E_COMPILE_ERROR, "Hooked properties cannot be readonly");
8500+
/* Allow hooks on backed readonly properties only. */
8501+
if ((prop_info->flags & (ZEND_ACC_READONLY|ZEND_ACC_VIRTUAL)) == (ZEND_ACC_READONLY|ZEND_ACC_VIRTUAL)) {
8502+
zend_error_noreturn(E_COMPILE_ERROR, "Hooked virtual properties cannot be readonly");
85028503
}
85038504

85048505
if (hooks->children == 0) {

0 commit comments

Comments
 (0)