PHP RFC: Friends
- Version: 0.1
- Date: 2026-05-04
- Author: Daniel Scherzer, daniel.e.scherzer@gmail.com
- Status: Draft
- Implementation: https://github.com/php/php-src/pull/21937
This RFC proposes adding support for friendship in PHP, allowing classes to specify other classes as friends. Those friends would then be able to access protected and private properties, constants, and methods of the declaring class.
Introduction
PHP has three visibility levels
- Public: accessible everywhere
- Protected: accessible in the declaring class and other classes in the same hierarchy
- Private: accessible only in the declaring class
Classes will occasionally have property or methods that need to be exposed to one or more other classes, but should not be public to everyone. These are generally internal implementation details that are exposed for use in other parts of the same library, like factory classes, but should not be used by external code.
Currently, the options to do so are limited:
- Make things public, and document them as internal
- Make things public, and enforce the internal-ness by examining backtraces
- Don't make things public, and use reflection to access them
The use of reflection for bypassing visibility restrictions in non-test code is generally frowned upon, and should not be recommended. Thus, the primary approach currently taken is to make things public, and to require, via documentation, tooling, and/or runtime backtrace checks, that the internal aspect of the class is not being accessed improperly. One tool, that inspired this RFC, is the #[Friend] attribute from the dave-liddament/php-language-extensions library, which adds an attribute for friendship that is enforced via static analysis.
This RFC proposes an approach based on C++'s concept of friends. A class (e.g. User) can declare another class (e.g. UserFactory) as a friend, allowing that friend to access internal details.
<?php class User { // The UserFactory class is allowed to use the private constructor friend UserFactory; // Private constructor - user information must come from a trusted source private function __construct( public readonly int $userId, public readonly string $username, ) {} } ?>
Proposal
Add support for PHP classes (including enums) to declare friends based on class name, following the same namespace handling rules as normal (e.g. use statements are applied). The named friend need not exist at the time of declaration, and is not autoloaded. This allows for potential friendship with external classes.
Friends (defined as classes where the fully qualified class name matches the declared friend name) are then allowed to access protected and private parts of the declaring class.
For protected parts that are inherited by subclasses, when not overridden the friend can still access them. In other words, friendship checks are based on the class where a property/method/constant is defined.
To allow checking what friends a class has, a new method is added to ReflectionClass returning an array of the friend names:
<?php class ReflectionClass implements Reflector // ...existing methods public function getFriendNames(): array {} } ?>
Examples
Friends have access to private methods (e.g. constructor):
<?php class User { friend UserFactory; // Private constructor - user information must come from a trusted source private function __construct( public readonly int $userId, public readonly string $username, ) {} } class UserFactory { public function newFromId(int $userId): ?User { // In reality this would query a database or something return match ($userId) { 1 => new User(1, "Alice"), 2 => new User(2, "Bob"), default => null, }; } } $factory = new UserFactory; $alice = $factory->newFromId(1); var_dump($alice); $bob = $factory->newFromId(2); var_dump($bob); // Creation outside of the factory fails try { $unknown = new User(3, "Camille"); } catch (Error $e) { echo $e; } ?>
Friends also have access to change private properties (including those with asymmetric visibility):
<?php class User { friend UserBuilder; public private(set) ?int $userId = null; public private(set) ?string $username = null; // Private constructor - use the UserBuilder private function __construct() {} } class UserBuilder { public function newWithId(int $userId): User { $u = new User(); $u->userId = $userId; return $u; } public function newWithName(string $username): User { $u = new User(); $u->username = $username; return $u; } } $builder = new UserBuilder(); $alice = $builder->newWithId(1); var_dump($alice); $bob = $builder->newWithName("Bob"); var_dump($bob); // Creation outside of the builder fails try { $unknown = new User(); } catch (Error $e) { echo $e; } ?>
Details
Assuming User has a friend UserFactory
- Friendship is not mutual. If
UserFactorycan access private details ofUser, butUsercannot access the private details ofUserFactory(unless the factory also adds a friend declaration) - Friendship is not transitive. If
UserFactoryhas a friendServiceFactory, that does not mean thatServiceFactorycan access the private details ofUser - Friendship is not inherited. If
UserFactoryhas a subclassNamedUserFactory, that subclass cannot access the private details ofUser
Additionally:
- Friendship is not affected by asymmetric visibility - friends can modify class properties regardless of visibility
- Friends cannot be declared on traits or interfaces
Backward Incompatible Changes
- The new
ReflectionClass::getFriendNames()method means that any subclass with a method of the same name but an incompatible signature will no longer be allowed - New global constant
T_FRIENDprevents userland constants of the same name when using the tokenizer extension
Proposed PHP Version(s)
Next minor version (PHP 8.6).
RFC Impact
To the Ecosystem
Static Analyzers will want to suppress warnings about accessing private/protected class members when the access is coming from a friend.
Linters and IDEs will need to update for the new syntax.
Userland PHP libraries that have internal details exposed to other parts of the library via public methods may want to migrate to using friends once they require PHP 8.6+.
To Existing Extensions
Will existing extensions be affected?
- Opcache: caching of class entries is updated to include the list of friends of the class
- Reflection:
- introduction of
ReflectionClass::getFriendNames ReflectionProperty::isReadableandReflectionClass::isWritableare updated to account for friendship
- Tokenizer: new token
T_FRIENDfor the new keywork
To SAPIs
None
Open Issues
Make sure there are no open issues when the vote starts!
Future Scope
- Adding support for inherited friendship (“UserFactory and all of its subclasses are all friends of User”)
- Adding support for namespaces as friends (“Any class in \My\Library\Namespace is a friend of User”)
- Adding support for friends on traits (“Any class that uses this trait will declare UserFactory as a friend”)
Voting Choices
Please consult the php/policies repository for the current voting guidelines.
Primary Vote requiring a 2/3 majority to accept the RFC:
Patches and Tests
Implementation
After the RFC is implemented, this section should contain:
- the version(s) it was merged into
- a link to the git commit(s)
- a link to the PHP manual entry for the feature
References
Links to external references, discussions, or RFCs.
- Blog post with feature overview: https://scherzer.dev/Blog/20260309-php-friends
- Pre-RFC discussion on syntax: https://externals.io/message/130710
Rejected Features
Keep this updated with features that were discussed on the mail lists.
Changelog
If there are major changes to the initial proposal, please include a short summary with a date or a link to the mailing list announcement here, as not everyone has access to the wikis' version history.
- v0.1: created