Skip to content
Theory · Classes and objects · lesson 10 of 11

readonly and __toString

Some data is set once and for good. A passport number is not changed while the passport is valid: if another number is needed, a new passport is issued.

A house has the same kind of thing—a number plate carved in stone when the house was built. It cannot be rewritten; you can only build another house with another plate.

Closing a property with the word private and giving it a getter is one way. But then the class's own methods can still change it by accident, and every property needs its own getter. Since PHP 8.1 there is a shorter and stricter way—readonly.

readonly: written once

PHP
<?php

class Passport
{
    public function __construct(
        public readonly string $number,
        public readonly string $owner,
    ) {}
}

$p = new Passport('4510 123456', 'Anna');
echo $p->owner, ' ', $p->number, "\n"; // Anna 4510 123456

Let's go through it step by step:

  1. public readonly string $number is a promoted property with the word readonly.
  2. new Passport('4510 123456', 'Anna') writes values into the properties: this is the first and only write.
  3. Such properties can be read from anywhere, and echo prints Anna 4510 123456.

A readonly property is a property that gets its value once, usually in the constructor, and after that can only be read. Let's try to change the owner:

PHP
$p->owner = 'Olga'; // ✗
TEXT
Fatal error: Uncaught Error: Cannot modify readonly property Passport::$owner

This is an error, not a warning: the lines above have run, and the program stops at this one. The ban holds for the class's own methods too: there is no second write, from outside or from inside.

The rules for readonly:

  • such a property must have a type, or the file will not run;
  • a default value in the property declaration is not allowed, but a promoted parameter may have one: public readonly int $level = 1 in the constructor works;
  • since PHP 8.2 you can declare a readonly class, and then every property of the class becomes readonly.

A new value means a new object

Nobody rewrites the plate on a house; they build a new house. It is the same with readonly: a passport with another owner is a new object, new Passport('4510 654321', 'Olga'), and the old one stays as it was.

A copy made with clone won't help here: it gets the properties already written, and a write to it throws the same error. For such cases PHP 8.5 added the function clone() with a list of new values. Inside the class, for example in a method withOwner(), you write clone($this, ['owner' => $owner]).

Here the stone picture is off: readonly only forbids writing something else into the property. If the property holds an object, that object's own properties can still change.

Interviewers ask why readonly is needed. The answer: a variable holds an object handle, and any code that got the handle could change the object. An object with readonly properties can be handed anywhere: nobody will overwrite its data.

__toString: an object in echo

echo can print numbers and strings, but not objects. To make an object printable, the class declares the method __toString:

PHP
<?php

class Price
{
    public function __construct(public readonly int $cents) {}

    public function __toString(): string
    {
        return number_format($this->cents / 100, 2) . ' EUR';
    }
}

$price = new Price(1250);
echo $price, "\n"; // 12.50 EUR
echo 'Total: ' . $price, "\n"; // Total: 12.50 EUR

Let's take it apart:

  1. __toString(): string returns the string the object will be shown as.
  2. echo $price calls __toString by itself and prints 12.50 EUR.
  3. Joining with . does the same: the object turns into a string.

A magic method is a method with two underscores at the start of its name that PHP calls by itself. You have already met __construct and __clone, and __toString is the third of them.

Common mistake

An object without __toString is printed with echo:

PHP
echo $p, "\n"; // ✗
TEXT
Fatal error: Uncaught Error: Object of class Passport could not be converted to string

PHP does not know which string to show the passport as, and it stops the program. The fix is to declare __toString in the class or to print the properties you need: echo $p->owner, "\n";.

Key points

  • A readonly property gets its value once, and any later write is an Error.
  • A readonly property must have a type and has no default value in its declaration, and a readonly class makes every property like that.
  • To "change" a readonly property, you create a new object.
  • __toString sets the string an object prints as in echo; without it, echo of an object is an error.