Use asymmetric property visibility in PHP 8.4

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

M Bytes Newsletter
Get the developer newsletter

    Fresh bytes every Thursday. No spam, ever. Unsubscribe anytime.

    Join 9,000+ developers and get three free video lessons every week.

    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.