← lessons

Use asymmetric property visibility in PHP 8.4

Video Lesson

PHP 8.4 introduces asymmetric visibility, a new way to control who can read and write to class properties.

Lesson Content

PHP 8.4 has a new feature called asymmetric visibility. This sounds so intricate! But it’s just a really simple way to give you a bit of added control in determining who can set class properties.

Traditional PHP properties are pretty straightforward. We’re all familiar with the syntax when declaring property visibility with public, protected and private:

<?php

class User
{
    public string $name;        // Anyone can read & write
    protected string $email;    // Only this class and children can read & write
    private string $lastLogin;  // Only this class can read & write
}

This setting controls which classes can read and write to these properties. But sometimes you’ll want different visibility levels for both reading and writing to these properties.

That's where asymmetric visibility comes into play. We define the visibility for reading first, and this will look just like the syntax we are always used to. But this will be a more permissive visibility.

For example, rather than making our property protected, we will set the read visibility to public. Then, we can add another level after it, which will be our write visibility, and we will couple it with (set). This will control exactly who can modify it:

<?php

class User
{
    public string $name;
    // - Anyone can read (public)
    // - Anyone can write (public)

    public protected(set) string $email;
    // - Anyone can read (public)
    // - Only this class and children can write (protected)

    public private(set) DateTime $lastLogin;
    // - Anyone can read (public)
    // - Only this class can write (private)

    public function __construct(
        string $name,
        string $email,
        ?DateTime $lastLogin = null,
    ) {
        $this->name = $name;
        $this->email = $email;
        $this->lastLogin = $lastLogin ?? new DateTime();
    }
}

Let's see what happens when we try to use these properties:

<?php

require_once 'User.php';

$user = new User('John', 'john@example.com');

// Reading works fine - it's public
echo $user->name;                              // Works!
echo $user->email;                             // Works!
echo $user->lastLogin->format('Y-m-d H:i:s');  // Works!

// Writing follows visibility(set) -- if it is defined
$user->name = 'John Doe';           // Works! (public)
$user->email = 'new@example.com';   // Error! (protected)
$user->lastLogin = new DateTime();  // Error! (private)

Child classes follow these same rules:

class AdminUser extends User
{
    public function updateUser(): void
    {
        $this->name = 'John Doe';           // Works! (public)
        $this->email = 'new@email.com';     // Works! (protected)
        $this->lastLogin = new DateTime();  // Error! (private)
    }
}

This feature is perfect for properties that should be readable by anyone, but only changeable by certain parts of your code. For example:

  • Account numbers (readable by all, but settable only by the class)

  • User IDs (visible to everyone, but modified internally)

  • Creation timestamps (public reading, with writing controlled internally)

The syntax is straightforward:

  1. Start with your normal property visibility (public usually)

  2. Add protected(set) or private(set) to control who can modify it

That's it! Asymmetric visibility gives you way more control over your properties, but without the complexity.