File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change 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}
Original file line number Diff line number Diff line change 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}
Original file line number Diff line number Diff line change 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)
Original file line number Diff line number Diff line change 3434 echo $ e . "\n\n" ;
3535}
3636try {
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
6161Stack 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
6565Stack trace:
6666#0 {main}
6767
Original file line number Diff line number Diff line change 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+ }
Original file line number Diff line number Diff 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
Original file line number Diff line number Diff 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
618622static void zend_property_guard_dtor (zval * el ) /* {{{ */ {
You can’t perform that action at this time.
0 commit comments