October 21st 2025
The #[NamespaceVisibility] attribute (from the PHP language extensions library) emulates PHP's missing namespace/package visibility modifier that many other languages have.
Package or Namespace visibility allows related classes to collaborate internally without exposing their details to the outside world. This is crucial for maintaining modularity and encapsulation while still enabling tight cooperation between classes that are part of the same subsystem.
For example, you might have a PriceCalculator class that the rest of the codebase should use.
Internally PriceCalculator might make use of DiscountCalculator and BundlePricingCalculator.
It would make sense for all three classes to live in the same namespace as their behaviour is closely related.
Adding the #[NamespaceVisibility] modifier to DiscountCalculator and BundlePricingCalculator means that methods in these classes can only be called from other classes in the same namespace.
See the following example.
namespace App\PriceCalculation {
final readonly class PriceCalculator {
public function __construct()
private DiscountCalculator $discountCalculator,
private BundlePricingCalculator $bundlePricingCalculator,
) {}
public function calculatePrice(Basket $basket): Price {
$price = new Price($basket->asFullPrice);
// ✅DiscountCalculator has NamespaceVisibility and is in the same namespace.
$price = $this->discountCalculator->applyDiscount($price, $basket);
return $price;
}
}
#[NamespaceVisibility]
final readonly class DiscountCalculator {
public function applyDiscount(Price $price, Basket $basket): Price {
// ... implementation ...
}
}
}
namespace App\AnotherNamespace {
final readonly class AClass {
public function __construct()
private PriceCalculator $priceCalculator,
private DiscountCalculator $discountCalculator,
) {}
}
public function calculate(Basket $basket): Price {
// ✅ PriceCalculator can be called from anywhere in the codebase
$price = $this->priceCalculator->calculatePrice($basket);
// ❌ DiscountVisibility can only be called from the same namespace
return $this->discountCalculator->applyDiscount($price, $basket);
}
}
The #[NamespaceVisibility] attribute can be added to class or a method.
If added to a class, then all methods withing the class are treated as having Namespace Visibility.
If added to a method, then only that method is treated as having Namespace Visibility.
Assume we have a Person class in the App\Entity.
The Person::setName has the #[NamespaceVisibility] attribute.
E.g.
namespace App\Entity;
final class Person {
#[NamespaceVisibility]
public function updateName(string $name): void
{
// ... implementation ...
}
}
By default, the Person::setName can be called from any class in App\Entity and the class in the sub-namespaces of App\Entity.
E.g. this would be OK:
namespace App\Entity\Helpers;
final class PersonUpdater {
public function updatePersonName(Person $person, string $name): void
{
// ✅ Calling Person::update from a sub-namespace of App\Entity
$person->updateName($name);
}
}
If you only want to allow method calls from the same namespace and not sub-namespaces use the excludeSubNamespaces parameter.
E.g. In the example below you'll only be able to call Person::updateName from the App\Entity namespace and NOT any sub-namespaces of App\Entity.
namespace App\Entity;
final class Person {
#[NamespaceVisibility(excludeSubNamespaces: true)]
public function updateName(string $name): void
{
// ... implementation ...
}
}
This would no longer be allowed:
namespace App\Entity\Helpers;
final class PersonUpdater {
public function updatePersonName(Person $person, string $name): void
{
// ❌ Calling Person::update from a sub-namespace of App\Entity is not allowed.
$person->updateName($name);
}
}
By default, using #[NamespaceVisibility] restricts calls from the same namespace or sub-namespaces.
It is possible to define a different namespace using the namespace parameter.
E.g. In this example the Person::__construct method can only be called from the App\Builders namespace.
namespace App\Entity;
final class Person {
#[NamespaceVisibility(namespace: 'App\Builders')]
public function __construct(string $name): void
{
// ... implementation ...
}
}
Given the above example, this would be OK:
namespace App\Builders;
final class PersonBuilder {
public string $name = 'Jane';
public function build(): Person
{
// ✅ Calling Person::__construct from namespace of App\Builders
return new Person($this->name);
}
}
But this would not...
namespace App\Services;
final class PersonService {
public function createPerson(string $name): Person
{
// ❌ Calling Person::__construct from namespace App\Services is not allowed.
return new Person($name);
}
}
It is possible to use both namespace and excludeSubNamespaces together.
The #[NamespaceVisibility] attribute helps developers to, where appropriate, decompose their code into multiple collaborating classes whilst limiting the public API.