Sometimes a class has members that exactly one other class needs, and nobody else should touch. A factory that needs a protected constructor is the common case. Today you either make those members public and mark them "internal" in the docs, or you reach for reflection or a fake subclass. This RFC from Daniel Scherzer adds friend classes, an idea borrowed from C++: a class names its friends, and those friends can access its protected members.
class User { // The UserFactory class is allowed to use the protected constructor friend UserFactory; protected function __construct( public readonly int $userId, public readonly string $username, ) {} } class UserFactory { public function newFromId(int $userId): ?User { return new User($userId, "Alice"); } }
UserFactory can call new User(...), while code anywhere else gets an Error. The idea came from the #[Friend] attribute in the dave-liddament/php-language-extensions package, which enforces the same rule through static analysis.
The rules
- Friends get protected access only, not private. An early draft allowed private access too, but that was dropped after problems with subclasses came up on the mailing list.
- Friends can modify protected properties, but only when asymmetric visibility allows writes from protected scope.
- Friendship goes one way. Being
User's friend doesn't makeUseryour friend. - It doesn't chain. A friend of your friend isn't your friend.
- It isn't inherited. A subclass of
UserFactoryisn't a friend, although methods it inherits fromUserFactoryunchanged still work, and so doparent::calls. - The friend class doesn't need to exist yet, and declaring it doesn't trigger the autoloader.
- Classes and enums can declare friends, but traits and interfaces can't.
Friendship isn't a new visibility level. Instead, protected now means "classes in the same hierarchy, plus friends." A new ReflectionClass::getFriendNames() method returns the list of declared friends.
What it means for existing code
There are two small backward compatibility breaks. A subclass of ReflectionClass that defines its own getFriendNames() method may clash if the signature doesn't match. And when the tokenizer extension is loaded, the new T_FRIEND token constant blocks any constant of your own with that name.
Static analysis tools will also need to stop warning about protected access from a friend.
Where it stands
The RFC is under discussion, with an implementation up for review. It targets PHP 8.6 and needs a two-thirds vote. The RFC lists private access, namespace-level friends and inherited friendship as possible follow-ups.