PHP 8.1: Readonly properties
20, December 2021

From PHP 8.1, we've got readonly properties that can not be modified after it has been initialized. What a great addition that helps us to create immutable objects much more easily.


Prior to this, if we wanted to create an immutable object, we had to make class properties private/protected then expose those property with public getter method.

<?php

class Person {
    private string $name;

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

$person = new Person("Alice");

// we can read $name
echo $person->getName();

// but we can't modify $name
$person->name = 'Bob';
// Uncaught Error: Cannot access private property Person::$name

This is very common approach when dealing with value objects or DTO, since they are mostly immutable.


But from PHP 8.1, we can declare properties as readonly.

Readonly properties can only be initialized once, and only from the scope where they have been declared, which means insides the class definition.

<?php

class Person {
    public readonly string $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }
}

$person = new Person("Alice");

// we can read $name
echo $person->name;

// but we can't modify $name
$person->name = 'Bob';
// Uncaught Error: Cannot modify readonly property Person::$name


Rules & Restrictions for readonly properties:

  • Readonly properties can never be modified once they have been initialized, we can't even modify it in __construct() once assigned.
class Person {
    public readonly string $name;

    public function __construct(string $name)
    {
        $this->name = $name;

        $this->name = "Something else"; // this won't work
    }
}
  • Only typed properties can be readonly. If a property doesn't have any type definition, it implicitly contains default value of null, which counts as initialized. To avoid such confusion, only typed properties are allowed to be readonly. If we are not sure of the type of a property, we can define it as mixed, then it can be readonly too.
class Person {
    public readonly string $name;

    public readonly $job; // invalid

    public readonly mixed $job; // works´╗┐
}
  • Readonly properties can't have default value. Since they can't be modified, having default values would be same as constants, so default values aren't allowed.
class Person {
    public readonly string $name = "Bob"; // invalid
}
  • We can use property promotion and readonly together in a class.
class Person {
    public function __construct (
        public readonly string $name = "Bob"
    ) {} 
}

$person1 = new Person("Alice");
$person2 = new Person();

echo $person1->name;
// Alice
´╗┐
echo $person2->name;
// Bob
  • Due to technical limitations, readonly static properties are not supported.
  • During inheritance, it is not allowed to override regular properties with readonly ones or vice versa.
class Person {
    public string $name;
}

class Employee extends Person {
    // not allowed
    public readonly string $name;
}
  • If a class is using two traits, and both of those traits contain same parameter, either both parameters have to be readonly or not. They can't mismatch.
trait Student {
    public string $name;
}

trait Singer {
    // not allowed, readonly mismatchpublic readonly string $name;
}

class Performer {
    use Student;
    use Singer;
}
  • Readonly properties can't be unset() after they are initialized. But we can unset() before initializing them.


RFC: https://wiki.php.net/rfc/readonly_properties_v2


Write comment about this article: