0

Properties — typed, untyped, default values, readonly

Beginner5 min read·php-07-002
interview

Concept

PHP 7.4 introduced typed properties, and PHP 8.1 added readonly. Together they give PHP's property system full type safety and immutability control at the language level, replacing large amounts of getter/setter boilerplate.

Typed properties (public int $count) enforce that the property can only hold a value of the declared type. Assigning a wrong type throws a TypeError. Before first assignment, a typed property is in an "uninitialized" state (distinct from null unless the type includes null). Reading an uninitialized property throws \Error. Default values must be compatible with the type.

Nullable types (public ?string $name = null): The ? prefix is shorthand for string|null. Default null for nullable typed properties is common.

Readonly properties (PHP 8.1): Can only be written once (during initialization or in __construct). After that, any attempt to write throws \Error. Readonly enforces value-object immutability at the language level. Readonly properties cannot have default values (they must be initialized in the constructor). In PHP 8.2, readonly class marks all properties readonly automatically.

Property types vs method return types: Type the property for internal class invariants; type the method for public API contracts. Both are important in different roles.

Code Example

php
<?php
declare(strict_types=1);

class User
{
    // Typed with default value
    public string $name = '';
    public int    $age  = 0;
    public bool   $active = true;

    // Nullable — can be null or string
    public ?string $bio = null;

    // No default — must be assigned before reading
    public string $email;  // uninitialized until constructor assigns it

    // Readonly — write once (PHP 8.1)
    public readonly int $id;

    public function __construct(int $id, string $email)
    {
        $this->id    = $id;
        $this->email = $email;
    }
}

$user = new User(1, 'alice@example.com');
echo $user->id;    // 1
echo $user->email; // 'alice@example.com'

// $user->id = 2; // Fatal Error: Cannot modify readonly property

// Uninitialized property error
class LazyInit
{
    public string $data; // no default, not readonly
}
$obj = new LazyInit();
// echo $obj->data; // Fatal Error: Typed property must not be accessed before initialization

// Type enforcement
$user->age = 'twenty'; // TypeError: must be of type int, string given

// PHP 8.2 readonly class — all properties are readonly
readonly class Money
{
    public function __construct(
        public readonly float $amount,
        public readonly string $currency,
    ) {}
}
$price = new Money(9.99, 'EUR');
// $price->amount = 10.0; // Fatal Error

// Union types for flexible but typed properties
class Response
{
    public int|string $statusCode = 200;
}
$r = new Response();
$r->statusCode = '404'; // valid — int|string
$r->statusCode = 200;   // valid
// $r->statusCode = 20.5; // TypeError — float not in int|string