readonly + accessors

← readonly + accessors

Ashterix

docs: English JsonRpcBundle docs, REST and MCP Bundle sections, package cards

en/articles/from-tg/series/php-can-do/readonly.md new file +103 βˆ’0

1 + # readonly + accessors
2 +
3 + *Stable DTOs and Value Objects without boilerplate*
4 +
5 + - **Added in:** readonly β€” PHP 8.1, accessors β€” PHP 8.4
6 + - **What it gives you:** the ability to create objects whose data can't be changed by accident, plus a new syntax for controlling access to properties via get / set without separate methods.
7 +
8 + ## What it's about
9 +
10 + `readonly` makes a property immutable after initialization β€” the value can be set only once (usually in the constructor).
11 +
12 + `accessors` let you add control over reading and writing properties without classic getters and setters β€” right in the class body.
13 +
14 + Together they make a perfect combination for DTOs, VOs and configuration classes: the data is immutable and access is controlled.
15 +
16 + ## The classic approach
17 +
18 + ```php
19 + class Product {
20 + private string $name;
21 + private float $price;
22 +
23 + public function __construct(string $name, float $price)
24 + {
25 + $this->name = $name;
26 + $this->price = $price;
27 + }
28 +
29 + public function getPrice(): float {
30 + return $this->price;
31 + }
32 +
33 + public function setPrice(float $price): void {
34 + if ($price < 0) {
35 + throw new InvalidArgumentException('Price must be positive');
36 + }
37 + $this->price = $price;
38 + }
39 + }
40 + ```
41 +
42 + It works, but most of this code exists only to provide basic encapsulation.
43 +
44 + ## Now the same β€” the modern way
45 +
46 + ```php
47 + class Product {
48 + public readonly string $name;
49 + private float $price {
50 + get => $this->price;
51 + set => $this->price = max(0, $value);
52 + }
53 +
54 + public function __construct(string $name, float $price)
55 + {
56 + $this->name = $name;
57 + $this->price = $price;
58 + }
59 + }
60 + ```
61 +
62 + `readonly` guarantees the name is immutable, and the `accessors` automatically check the price on write.
63 +
64 + ## One more example β€” a Value Object
65 +
66 + ```php
67 + class Coordinates {
68 + public readonly float $lat {
69 + get => round($this->lat, 6);
70 + }
71 +
72 + public readonly float $lng {
73 + get => round($this->lng, 6);
74 + }
75 +
76 + public function __construct(float $lat, float $lng)
77 + {
78 + $this->lat = $lat;
79 + $this->lng = $lng;
80 + }
81 + }
82 + ```
83 +
84 + No changes after creation, only controlled access on read.
85 +
86 + ## Where it really helps
87 +
88 + - `DTO`: API requests and responses;
89 + - `Value Object`: Money, Price, GeoPoint, Range;
90 + - `Immutable configurations`: service parameters, constant states.
91 +
92 + ## Conclusion
93 +
94 + > readonly and accessors are a step towards real record classes in PHP.
95 + > They let you write clean, predictable structures without a ton of boilerplate.
96 +
97 + Today every DTO of mine is readonly.
98 +
99 + ← [Previous: Enums](/articles/from-tg/series/php-can-do/enums) Β· [Whole series](/articles/from-tg/series/php-can-do) Β· [Next: Attributes β†’](/articles/from-tg/series/php-can-do/attributes)
100 +
101 + ---
102 +
103 + *First published in Ukrainian in the Telegram channel [β€œCreated to die”](https://t.me/php_while_true/95) on November 30, 2025.*