पाठ 7 / 25

Classes, Constructor Promotion, Readonly and Enums

Write classes with promoted properties, readonly state and enums.

Modern classes with little boilerplate

Classes define typed properties, methods and constants, with visibility public, protected or private. Constructor property promotion (PHP 8.0) declares and assigns properties directly in the constructor signature: public function __construct(private string $sku, private Money $price) {}. Readonly properties (8.1) can be written only once, during initialisation, and readonly classes (8.2) make every property readonly, ideal for value objects such as Money or EmailAddress. To "change" a readonly object, return a new instance (a wither). Enums (8.1) define a closed set of values: pure enums (enum Status { case Placed; case Paid; }) or backed enums with string or int values (case Paid = 'paid';), with from() and tryFrom() to convert from stored values, cases() to list them, and methods and interfaces of their own. Enums replace class constants and magic strings for statuses, roles and types. Static members belong to the class (self:: or static:: for late static binding), and named constructors such as Money::inr('499.00') read better than overloaded constructors.

A value object

Immutable objects with validated state and methods that return new instances.

A sealed capsule with a lock icon and an arrow producing a second, slightly different capsule rather than changing the first.
Figure 3.1 — A readonly value object returning a new instance on change.

A readonly value object and a backed enum

Promotion, readonly, named constructors and enum methods.

<?php
declare(strict_types=1);

enum OrderStatus: string
{
    case Placed = 'placed';
    case Paid = 'paid';
    case Shipped = 'shipped';
    case Cancelled = 'cancelled';

    public function isFinal(): bool
    {
        return $this === self::Shipped || $this === self::Cancelled;
    }
}

final readonly class Money
{
    private function __construct(public int $paise, public string $currency) {}

    public static function inr(string $rupees): self
    {
        if (!preg_match('/^\d+\.\d{2}$/', $rupees)) {
            throw new InvalidArgumentException("Invalid amount {$rupees}");
        }
        return new self((int) str_replace('.', '', $rupees), 'INR');
    }

    public function add(Money $other): self
    {
        if ($other->currency !== $this->currency) {
            throw new LogicException('Currency mismatch');
        }
        return new self($this->paise + $other->paise, $this->currency);   // new instance
    }
}

$total = Money::inr('499.00')->add(Money::inr('120.50'));   // 61950 paise
$status = OrderStatus::from('paid');                           // OrderStatus::Paid
$maybe = OrderStatus::tryFrom('refunded');                     // null

Store money as integers

Floating-point arithmetic cannot represent many decimal amounts exactly. Store paise (or cents) as integers, or use a decimal library such as brick/money, and format only at the edges.

त्वरित जाँच: What does a readonly property guarantee in PHP 8.1+?

  • It can be initialised once and not modified afterwards
  • It can never be read
  • It is static
  • It is automatically serialised
Answer

It can be initialised once and not modified afterwards — Readonly properties are write-once, making immutable objects straightforward.