Introducing the Friend attribute (example 1)

September 27th 2025


Code showing the Friend attribute usage with creational patterns

Enforcing Creational Design Patterns with the Friend Attribute

Have you ever used creational design patterns to create objects in PHP? (E.g. a Factory or a Builder.) If so, you'll probably want to ensure that the object is not created by using the new keyword. Instead, you'll want to enforce the object is only created by the relevant creational code.

But how can this be enforced?

PHP’s standard visibility modifiers (public, protected, private) aren’t fine-grained enough to enforce this.

The good news is that, with the PHP language extensions library, there’s now a way to be stricter about who can call a constructor or method. Enter the #[Friend] attribute.

An example

Let’s start with a simple Person class:

class Person
{
    public function __construct(
        private string $name,
    ) {}

    public function getName(): string
    {
        return $this->name;
    }
}

And a PersonFactory:

class PersonFactory
{
    public function createPerson(string $name): Person
    {
        // Imagine validation or other setup here
        return new Person($name);
    }
}

The intent is that all Person objects are created through the PersonFactory. But nothing stops someone doing this:

$direct = new Person('Bob'); // Bypasses the factory

Let’s just be friends

Other languages, like C++, have a concept of friends. A class or method can declare that only certain other classes are allowed to call it.

That’s exactly what we need here. By making the Person constructor a “friend” of PersonFactory, we can enforce the correct creation path.


class Person { #[Friend(PersonFactory::class)] public function __construct( private string $name, ) {} public function getName(): string { return $this->name; } }

Now, if anyone tries to call new Person('Bob') outside of PersonFactory, static analysis (e.g. via PHPStan) will complain with this error:

Cannot call Person::__construct() from Demo.
It can only be called from its friend: PersonFactory.

Benefits

This approach has some clear upsides:

  • Architectural rules enforced — Factories and builders can’t be bypassed.
  • Better communication — The attribute makes intent explicit in the code.
  • Static analysis support — PHPStan can spot violations before they get merged.

Drawbacks

There are some caveats too:

  • Enforcement happens at analysis time, not runtime.
  • You’ll need static analysis tools like PHPStan in your workflow (though you should be running these anyway).
  • Overuse can make code more rigid — it’s best saved for places where the architectural rules matter.

Conclusion

The #[Friend] attribute gives us a way to enforce creational design patterns directly in our codebase.

Instead of relying purely on documentation or developer discipline, we can move the rule into code, where it belongs. Factories and builders exist for a reason and with #[Friend], we can make sure they’re always used.

Further reading