# Classes, Constructor Promotion, Readonly and Enums — PHP

Source: https://www.skillbyai.com/en/php/o-classes

> 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.](assets/figures/php/section-3-map.svg) — 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
<?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.

**Quiz:** What does a readonly property guarantee in PHP 8.1+?

- [x] 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.
