Skip to content

Commit 32aa49f

Browse files
RFC examples, fixes
1 parent 51eac29 commit 32aa49f

7 files changed

Lines changed: 280 additions & 5 deletions

File tree

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
--TEST--
2+
Friends: RFC example 1 (User and UserFactory)
3+
--FILE--
4+
<?php
5+
6+
class User {
7+
friend UserFactory;
8+
9+
// Private constructor - user information must come from a trusted source
10+
private function __construct(
11+
public readonly int $userId,
12+
public readonly string $username,
13+
) {}
14+
}
15+
16+
class UserFactory {
17+
18+
public function newFromId(int $userId): ?User {
19+
// In reality this would query a database or something
20+
return match ($userId) {
21+
1 => new User(1, "Alice"),
22+
2 => new User(2, "Bob"),
23+
default => null,
24+
};
25+
}
26+
}
27+
28+
$factory = new UserFactory;
29+
30+
$alice = $factory->newFromId(1);
31+
var_dump($alice);
32+
33+
$bob = $factory->newFromId(2);
34+
var_dump($bob);
35+
36+
// Creation outside of the factory fails
37+
try {
38+
$unknown = new User(3, "Camille");
39+
} catch (Error $e) {
40+
echo $e;
41+
}
42+
43+
?>
44+
--EXPECTF--
45+
object(User)#%d (2) {
46+
["userId"]=>
47+
int(1)
48+
["username"]=>
49+
string(5) "Alice"
50+
}
51+
object(User)#%d (2) {
52+
["userId"]=>
53+
int(2)
54+
["username"]=>
55+
string(3) "Bob"
56+
}
57+
Error: Call to private User::__construct() from global scope in %s:%d
58+
Stack trace:
59+
#0 {main}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
--TEST--
2+
Friends: RFC example 2 (User and UserBuilder)
3+
--FILE--
4+
<?php
5+
6+
class User {
7+
friend UserBuilder;
8+
9+
public private(set) ?int $userId = null;
10+
public private(set) ?string $username = null;
11+
12+
// Private constructor - use the UserBuilder
13+
private function __construct() {}
14+
}
15+
16+
class UserBuilder {
17+
18+
public function newWithId(int $userId): User {
19+
$u = new User();
20+
$u->userId = $userId;
21+
return $u;
22+
}
23+
24+
public function newWithName(string $username): User {
25+
$u = new User();
26+
$u->username = $username;
27+
return $u;
28+
}
29+
}
30+
31+
$builder = new UserBuilder();
32+
33+
$alice = $builder->newWithId(1);
34+
var_dump($alice);
35+
36+
$bob = $builder->newWithName("Bob");
37+
var_dump($bob);
38+
39+
// Creation outside of the builder fails
40+
try {
41+
$unknown = new User();
42+
} catch (Error $e) {
43+
echo $e;
44+
}
45+
46+
?>
47+
--EXPECTF--
48+
object(User)#%d (2) {
49+
["userId"]=>
50+
int(1)
51+
["username"]=>
52+
NULL
53+
}
54+
object(User)#%d (2) {
55+
["userId"]=>
56+
NULL
57+
["username"]=>
58+
string(3) "Bob"
59+
}
60+
Error: Call to private User::__construct() from global scope in %s:%d
61+
Stack trace:
62+
#0 {main}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
--TEST--
2+
Friends: `friend` is not reserved
3+
--FILE--
4+
<?php
5+
6+
namespace ClazzDemo {
7+
class Friend implements \InterfaceDemo\Friend {
8+
use \TraitDemo\Friend;
9+
10+
public mixed $Friend;
11+
12+
public function Friend(): Friend {
13+
var_dump($this->Friend);
14+
return $this;
15+
}
16+
}
17+
18+
class Subclass extends Friend {}
19+
}
20+
21+
namespace InterfaceDemo {
22+
interface Friend {}
23+
}
24+
25+
namespace TraitDemo {
26+
trait Friend {}
27+
}
28+
29+
namespace EnumDemo {
30+
enum Friend {
31+
case Friend;
32+
}
33+
}
34+
35+
namespace {
36+
use ClazzDemo\Friend;
37+
use ClazzDemo\Subclass;
38+
39+
$f = new Friend;
40+
$f->Friend = "demo";
41+
$f->Friend();
42+
43+
$f = new Subclass;
44+
$f->Friend = "demo";
45+
$f->Friend();
46+
47+
var_dump(\EnumDemo\Friend::Friend);
48+
}
49+
50+
?>
51+
--EXPECT--
52+
string(4) "demo"
53+
string(4) "demo"
54+
enum(EnumDemo\Friend::Friend)

Zend/tests/friends/property_access.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ try {
3434
echo $e . "\n\n";
3535
}
3636
try {
37-
Foo::$privateInstance = 2;
37+
Foo::$privateStatic = 2;
3838
} catch (Error $e) {
3939
echo $e . "\n\n";
4040
}
@@ -61,7 +61,7 @@ Error: Cannot access protected property Foo::$protectedStatic in %s:%d
6161
Stack trace:
6262
#0 {main}
6363

64-
Error: Cannot access private property Foo::$privateInstance in %s:%d
64+
Error: Cannot access private property Foo::$privateStatic in %s:%d
6565
Stack trace:
6666
#0 {main}
6767

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
--TEST--
2+
Friends: allows access to properties with asymmetric visibility
3+
--FILE--
4+
<?php
5+
6+
class Foo {
7+
friend Bar;
8+
9+
public protected(set) static mixed $protectedStatic;
10+
public private(set) static mixed $privateStatic;
11+
12+
public protected(set) mixed $protectedInstance;
13+
public private(set) mixed $privateInstance;
14+
}
15+
16+
class Bar {
17+
public static function testPropertyAccess() {
18+
Foo::$protectedStatic = 1;
19+
var_dump(Foo::$protectedStatic);
20+
Foo::$privateStatic = 2;
21+
var_dump(Foo::$privateStatic);
22+
$f = new Foo();
23+
$f->protectedInstance = 3;
24+
$f->privateInstance = 4;
25+
var_dump($f);
26+
}
27+
}
28+
29+
// Confirm that the presence of a friend does not negate normal visibility
30+
// enforcement for non friends
31+
try {
32+
Foo::$protectedStatic = 1;
33+
} catch (Error $e) {
34+
echo $e . "\n\n";
35+
}
36+
try {
37+
Foo::$privateStatic = 2;
38+
} catch (Error $e) {
39+
echo $e . "\n\n";
40+
}
41+
$f = new Foo();
42+
try {
43+
$f->protectedInstance = 3;
44+
} catch (Error $e) {
45+
echo $e . "\n\n";
46+
}
47+
try {
48+
$f->privateInstance = 4;
49+
} catch (Error $e) {
50+
echo $e . "\n\n";
51+
}
52+
53+
echo "\n\n-----\n\n";
54+
55+
// But friend works
56+
Bar::testPropertyAccess();
57+
58+
?>
59+
--EXPECTF--
60+
Error: Cannot modify protected(set) property Foo::$protectedStatic from global scope in %s:%d
61+
Stack trace:
62+
#0 {main}
63+
64+
Error: Cannot modify private(set) property Foo::$privateStatic from global scope in %s:%d
65+
Stack trace:
66+
#0 {main}
67+
68+
Error: Cannot modify protected(set) property Foo::$protectedInstance from global scope in %s:%d
69+
Stack trace:
70+
#0 {main}
71+
72+
Error: Cannot modify private(set) property Foo::$privateInstance from global scope in %s:%d
73+
Stack trace:
74+
#0 {main}
75+
76+
77+
78+
-----
79+
80+
int(1)
81+
int(2)
82+
object(Foo)#%d (2) {
83+
["protectedInstance"]=>
84+
int(3)
85+
["privateInstance"]=>
86+
int(4)
87+
}

Zend/zend_language_scanner.l

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1802,7 +1802,16 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_
18021802
RETURN_TOKEN_WITH_IDENT(T_READONLY);
18031803
}
18041804

1805-
<ST_IN_SCRIPTING>"friend" {
1805+
/*
1806+
* The friend keyword must be followed by whitespace and another identifier.
1807+
* This avoids the BC break of using friend in classes, namespaces, functions and constants.
1808+
*/
1809+
<ST_IN_SCRIPTING>"friend"{WHITESPACE_OR_COMMENTS}("extends"|"implements") {
1810+
yyless(6);
1811+
RETURN_TOKEN_WITH_STR(T_STRING, 0);
1812+
}
1813+
<ST_IN_SCRIPTING>"friend"{WHITESPACE_OR_COMMENTS}[a-zA-Z_\x80-\xff] {
1814+
yyless(6);
18061815
RETURN_TOKEN_WITH_IDENT(T_FRIEND);
18071816
}
18081817

Zend/zend_object_handlers.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -611,8 +611,12 @@ ZEND_API bool ZEND_FASTCALL zend_asymmetric_property_has_set_access(const zend_p
611611
if (prop_info->ce == scope) {
612612
return true;
613613
}
614-
return EXPECTED((prop_info->flags & ZEND_ACC_PROTECTED_SET)
615-
&& is_protected_compatible_scope(prop_info->prototype->ce, scope));
614+
if (EXPECTED((prop_info->flags & ZEND_ACC_PROTECTED_SET)
615+
&& is_protected_compatible_scope(prop_info->prototype->ce, scope))
616+
) {
617+
return true;
618+
}
619+
return zend_check_friend(prop_info->ce, scope);
616620
}
617621

618622
static void zend_property_guard_dtor(zval *el) /* {{{ */ {

0 commit comments

Comments
 (0)