====== 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 [[https://packagist.org/packages/dave-liddament/php-language-extensions|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. ===== 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: ==== Examples ==== Friends have access to private methods (e.g. constructor): 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): 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 UserFactory can access private details of User, but User cannot access the private details of UserFactory (unless the factory also adds a friend declaration) * Friendship is **not transitive**. If UserFactory has a friend ServiceFactory, that does not mean that ServiceFactory can access the private details of User * Friendship is **not inherited**. If UserFactory has a subclass NamedUserFactory, that subclass cannot access the private details of User 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_FRIEND prevents 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::isReadable and ReflectionClass::isWritable are updated to account for friendship * Tokenizer: new token T_FRIEND for 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 [[https://github.com/php/policies/blob/main/feature-proposals.rst#voting-phase|the php/policies repository]] for the current voting guidelines. ---- Primary Vote requiring a 2/3 majority to accept the RFC: * Yes * No * Abstain ===== Patches and Tests ===== https://github.com/php/php-src/pull/21937 ===== 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 * https://packagist.org/packages/dave-liddament/php-language-extensions * 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