PHP Asymmetric Property Visibility

PHP asymmetric property visibility lets you control who can read a property separately from who can change it. Added in PHP 8.4 for object properties, the feature can expose a value publicly while keeping writes inside the class or its child classes.

This design removes many simple getter methods without turning important state into a freely writable public property.

How Asymmetric Visibility Works

A normal property uses one visibility rule for both reading and writing. An asymmetric property adds a second modifier ending in (set). The first modifier controls read access, and the second controls write access.

Syntax:

<?php
class Product
{
    // Anyone can read the name, but only this class can change it
    public private(set) string $name;
}
?>

You can omit public when the read visibility is public. Therefore, private(set) string $name has the same access rules as public private(set) string $name.

Declaration Read access Write access
public private(set) Everywhere Declaring class only
public protected(set) Everywhere Declaring class and child classes
protected private(set) Class family Declaring class only

Requirement: Separate set visibility requires PHP 8.4 or later and a typed object property. PHP 8.5 also permits it on static properties.

Create a Publicly Readable Property

The most common pattern is a public value that external code may inspect but cannot overwrite.

Example:

<?php
class Wallet
{
    public private(set) int $balance;

    public function __construct(int $openingBalance)
    {
        // The declaring class may write the property
        $this->balance = $openingBalance;
    }

    public function deposit(int $amount): void
    {
        // Keep the validation beside the state change
        if ($amount <= 0) {
            throw new InvalidArgumentException("Amount must be positive");
        }

        $this->balance += $amount;
    }
}

$wallet = new Wallet(1000);
$wallet->deposit(250);

// Reading from outside the class is allowed
echo $wallet->balance;
?>

Output:

1250

An external assignment such as $wallet->balance = 0; throws an error because the setter is private. Callers must use the class method, so the validation cannot be bypassed.

Allow Child Classes to Write

Use protected(set) when a child class needs to update the property. External code can still read a public property but cannot change it directly.

Example:

<?php
class Employee
{
    public protected(set) string $department;

    public function __construct(string $department)
    {
        // The parent class sets the initial value
        $this->department = $department;
    }
}

class TeamLead extends Employee
{
    public function transferTo(string $department): void
    {
        // A child class may write a protected(set) property
        $this->department = $department;
    }
}

$lead = new TeamLead("Support");
$lead->transferTo("Engineering");

echo $lead->department;
?>

Output:

Engineering

Use Constructor Property Promotion

You can apply asymmetric visibility to a promoted constructor property. This keeps a small value object concise while protecting later writes.

Example:

<?php
class Order
{
    public function __construct(
        // The ID is readable after construction but cannot be replaced outside
        public private(set) string $id,
        public protected(set) string $status = "pending"
    ) {
    }

    public function confirm(): void
    {
        // The class controls valid status changes
        $this->status = "confirmed";
    }
}

$order = new Order("ORD-1042");
$order->confirm();

echo $order->id . ": " . $order->status;
?>

Output:

ORD-1042: confirmed

Follow the Visibility Rules

  • The set visibility must be the same as or more restrictive than the read visibility.
  • public protected(set) is valid, but protected public(set) is invalid.
  • Write private(set) without spaces inside the parentheses.
  • A private(set) property is implicitly final and cannot be redeclared in a child class.
  • Taking a reference to a property follows its set visibility because a reference could modify the value.
  • Changing an element inside an array property also counts as a write and follows set visibility.

Invalid access:

<?php
$order = new Order("ORD-1042");

// This write occurs outside the permitted scope
$order->status = "cancelled";
?>

Output:

Fatal error: Cannot modify protected(set) property Order::$status

Asymmetric Visibility vs Readonly

A readonly property can normally be initialized once and then cannot change. An asymmetric property can change many times, but only from an allowed scope. Choose readonly for immutable state. Choose asymmetric visibility when the object must update its own state while callers receive read access.

Best Practices

  • Use descriptive methods for changes that require validation or trigger other work.
  • Prefer private(set) unless child classes have a clear reason to write the property.
  • Do not expose mutable arrays merely to avoid a method; array element changes still need write permission.
  • Document the PHP version requirement when publishing a reusable package.
  • Test both valid internal updates and rejected external assignments.

Conclusion

PHP asymmetric property visibility separates read access from write access. With private(set) and protected(set), you can publish useful state while keeping updates under class control. The result is a smaller API, fewer boilerplate getters, and clearer object boundaries.



Found This Page Useful? Share It!
Get the Latest Tutorials and Updates
Join us on Telegram