Readonly Properties

Yo Yogesh.M Updated 17 Oct 2026
6 min read ·Lesson 65 of 109
In this post

Readonly Properties What Is a Readonly Property? In simple terms: A readonly property can be given a value once and can never be changed afterwards. It is like writing an order number on an invoice in

What Is a Readonly Property?

In simple terms: A readonly property can be given a value once and can never be changed afterwards. It is like writing an order number on an invoice in permanent ink: anyone can read it, but nobody can alter it.

Technically: Since PHP 8.1, a property declared with the readonly modifier can be initialized only once, and only from inside the class (normally in the constructor). Any later attempt to modify, increment, append to, or unset it throws an Error. Readonly properties must have a type and cannot have a default value.

Why Readonly Properties Exist

Many values should never change after an object is created: an order's ID, a product's SKU, the timestamp of a payment, the amount of a transaction. Before PHP 8.1, protecting such values required private properties plus getter methods. With readonly, a property can be public for reading while remaining unchangeable:

// Before PHP 8.1
private string $sku;
public function getSku(): string { return $this->sku; }

// PHP 8.1+
public readonly string $sku;

Declaring and Using Readonly Properties

Example

<?php
final class Product
{
    public readonly string $sku;
    public readonly string $name;
    public readonly int $pricePaise;

    public function __construct(string $sku, string $name, int $pricePaise)
    {
        if ($pricePaise <= 0) {
            throw new InvalidArgumentException('Price must be positive.');
        }

        $this->sku = strtoupper($sku);
        $this->name = $name;
        $this->pricePaise = $pricePaise;
    }
}

$pen = new Product('pen-01', 'Gel Pen', 2000);

echo "{$pen->sku}: {$pen->name}, " . $pen->pricePaise / 100 . " rupees\n";

try {
    $pen->pricePaise = 1;
} catch (Error $e) {
    echo $e->getMessage() . "\n";
}

Output

PEN-01: Gel Pen, 20 rupees
Cannot modify readonly property Product::$pricePaise

How It Works

  1. The constructor initializes each readonly property exactly once — after validating and normalizing the values.
  2. Reading the properties works from anywhere, because they are public.
  3. Any attempt to assign a new value throws an Error, even from inside the class.

With Constructor Promotion

Readonly properties are most often declared through constructor property promotion:

final class Product
{
    public function __construct(
        public readonly string $sku,
        public readonly string $name,
        public readonly int $pricePaise,
    ) {}
}

The Rules of Readonly

RuleError If Violated
Must have a declared typeReadonly property Product::$x must have type
Cannot have a default valueReadonly property Product::$x cannot have default value
Can be initialized only onceCannot modify readonly property Product::$x
Must be initialized from inside the class scopeCannot initialize readonly property Product::$x from global scope
Cannot be unset after initializationCannot unset readonly property Product::$x
Cannot be staticCompile error

Initialization Only From Inside the Class

<?php
final class Invoice
{
    public readonly int $number;
}

$invoice = new Invoice();

try {
    $invoice->number = 1001;
} catch (Error $e) {
    echo $e->getMessage() . "\n";
}

Output

Cannot initialize readonly property Invoice::$number from global scope

Even the first assignment is rejected when it comes from outside the class. This guarantees that the class itself controls the value. (Since PHP 8.4, readonly properties behave like protected(set), so child classes may also initialize them.)

Arrays Are Fully Protected

<?php
final class Course
{
    public function __construct(public readonly array $topics) {}

    public function addTopic(string $topic): void
    {
        $this->topics[] = $topic;
    }
}

$course = new Course(['Variables', 'Arrays']);

try {
    $course->addTopic('Functions');
} catch (Error $e) {
    echo $e->getMessage() . "\n";
}

Output

Cannot modify readonly property Course::$topics

Appending to, or changing an element of, a readonly array counts as modifying the property.


Interior Mutability: Readonly Is Shallow

A readonly property that holds an object cannot be replaced with another object — but the object itself may still be changed through its own methods:

<?php
final class Event
{
    public function __construct(public readonly DateTime $startsAt) {}
}

$event = new Event(new DateTime('2026-10-20 10:00'));
$event->startsAt->modify('+1 day');

echo $event->startsAt->format('Y-m-d H:i') . "\n";

Output

2026-10-21 10:00

readonly protects the reference, not the referenced object. To make the event's start time truly unchangeable, use an immutable type:

public readonly DateTimeImmutable $startsAt;

DateTimeImmutable::modify() returns a new object instead of changing the existing one, so the event's value can no longer change. The same applies to your own classes: readonly objects are only fully immutable if the objects they contain are immutable too.


Changing Values: The "Wither" Pattern

Immutable objects are not changed; instead, a method returns a new object with the modified value. Such methods are commonly named with...():

<?php
final class Product
{
    public function __construct(
        public readonly string $sku,
        public readonly string $name,
        public readonly int $pricePaise,
    ) {}

    public function withPrice(int $pricePaise): self
    {
        if ($pricePaise <= 0) {
            throw new InvalidArgumentException('Price must be positive.');
        }

        return new self($this->sku, $this->name, $pricePaise);
    }

    public function formattedPrice(): string
    {
        return '₹' . number_format($this->pricePaise / 100, 2);
    }
}

$original = new Product('PEN-01', 'Gel Pen', 2000);
$updated = $original->withPrice(2500);

echo "Original: " . $original->formattedPrice() . "\n";
echo "Updated:  " . $updated->formattedPrice() . "\n";
var_dump($original === $updated);

Output

Original: ₹20.00
Updated:  ₹25.00
bool(false)

Why This Is Useful

  • Code holding a reference to $original can rely on it never changing — for example, an order that captured the product's price at purchase time.
  • Every "change" produces a separate object, so history and comparisons are easy.
  • DateTimeImmutable, PSR-7 HTTP messages ($request->withHeader(...)), and many value objects use this pattern.

Cloning in Newer PHP Versions

For classes with many properties, calling the constructor in every wither becomes repetitive. Newer PHP versions offer alternatives:

VersionFeature
PHP 8.3Readonly properties may be re-initialized inside __clone(), enabling deep cloning
PHP 8.5"Clone with": clone($this, ['pricePaise' => $pricePaise]) creates a copy with changed properties
// PHP 8.5+
public function withPrice(int $pricePaise): static
{
    return clone($this, ['pricePaise' => $pricePaise]);
}

Note that "clone with" bypasses the constructor, so validation must be performed in the wither itself (or in a property hook).


When to Use Readonly Properties

Good FitPoor Fit
Identifiers (IDs, SKUs, order numbers)Values that legitimately change (stock levels, status)
Value objects (Money, EmailAddress, Coordinates)Objects that are built step by step
Data transfer objects and request/response objectsEntities whose state changes through their lifecycle
Configuration loaded once at startupCaches and counters

When all properties of a class are readonly, the whole class can be declared readonly (PHP 8.2+); see Readonly Classes.


Common Mistakes

Giving a Readonly Property a Default Value

Effect: Compile error.

Fix: Assign the value in the constructor (or use a class constant for fixed values).

Initializing From Outside the Class

Effect: Cannot initialize readonly property ... from global scope.

Fix: Initialize in the constructor or a named constructor.

Assuming Readonly Makes Contained Objects Immutable

Effect: Mutable objects (such as DateTime) inside readonly properties can still change.

Fix: Use immutable types for readonly properties.

Trying to Modify Readonly Arrays

Effect: Cannot modify readonly property.

Fix: Return a new object with a new array (wither pattern), or do not make the property readonly.


Best Practices

  • Use public readonly for data that must be readable but never changed.
  • Initialize all readonly properties in the constructor, after validation.
  • Store immutable types (such as DateTimeImmutable) in readonly properties.
  • Implement changes as with...() methods that return new objects.
  • Reserve mutable properties for state that genuinely changes over an object's lifetime.

Official reference: https://www.php.net/manual/en/language.oop5.properties.php#language.oop5.properties.readonly-properties

0 Comments

Reviewed before they appear

No comments yet.

PHP
PHP Home PHP Switch PHP Match Alternative Syntax PHP Loops While Loop Do While Loop For Loop Foreach Loop Nested Loops Break Statement PHP Home PHP Intro PHP Install PHP Built-in Web Server PHP Syntax PHP Tags PHP and HTML PHP Comments PHP Multiline Comments PHP Variables Variables Scope PHP Casting Logical Operators Null Coalescing Operator Spaceship Operator Operator Precedence Continue Statement Modify Strings PHP If...Else...Elseif PHP If Modify Strings PHP If Operators PHP Functions Concatenate Strings PHP If...Else Slicing Strings User-defined Functions Slicing Strings PHP Shorthand if Escape Characters Default Argument Values PHP Nested if Pass by Reference String Interpolation Named Arguments PHP File Upload Variadic Functions Return Values Search in Strings Multibyte Strings Type Declarations in Functions PHP Numbers PHP Math PHP Floats PHP Random Numbers PHP Constants PHP Properties PHP Methods PHP Constructor Constructor Property Promotion PHP Cookies PHP Destructor PHP Access Modifiers Readonly Properties PHP Sessions PHP Inheritance Method Overriding PHP Filters The final Keyword CSRF Protection PHP Filters Advanced Session Security Secure Cookies PHP Magic Constants File Upload Security PHP Callback Functions PHP Operators Filesystem Security PHP JSON Arithmetic Operators Error Reporting in Production Hiding PHP Assignment Operators PHP Configuration (php.ini) Comparison Operators PHP Command Line (CLI) PHP Exceptions Increment / Decrement Operators Migrating to PHP 8 PHP Form Validation PHP Form Required PHP Form URL/E-mail Error Handling Form Input Sanitizing Escaping Output (htmlspecialchars) Custom Error Handler Checkbox, Radio and Select Inputs Sticky Forms PHP Form Complete PHP Date and Time Date Formatting Error Logging HTTP Headers and Redirects Output Buffering Sending Email PHP cURL Consuming REST APIs PHP Generators
Ask about this post
AI Ask about this post

Ask questions about Readonly Properties and get answers drawn from it.

Signed-in readers only.