Hey Dmitry, Please remember to bottom post!
On Mon, Jul 14, 2025, at 11:16, Dmitry Derepko wrote: > Hi, Rob! > > I'm just wondering, why the implementation differs from a regular class? > > Record makes a "class" immutable and unique, using field value comparison > instead of object refs. It has a custom `with` method which is similar to the > new `clone` operator accepted recently. I'd suggest not introducing a new > keyword "record" and new syntax to create records, but use regular `class` > and `new Class`. Class should have a modifier "data" like readonly, so it > should be: > > data class Record { > public function __construct(public $a){} // $a is mutable, because why > not? > } > > Data class will compare with others using field value comparison. Moreover, > if you want to make it readonly, use the existing keyword to achieve this: > > readonly data class Record { > public function __construct(public $a){} // $a is immutable, because of > "readonly" class modifier > } Data classes are basically structs (as we collectively discovered last December when I tried implementing exactly that). Records are implemented completely differently than structs, even though they seem very similar. This is largely why they’re completely different keywords. Further, records were meant to be nested inside other classes, and I did try to actually pull that off with regular classes — and it was declined. This negates the short-syntax and nested aspect of records. > > 1. So record Record {} becomes readonly data class Record {} A record is not a class just like an enum is not a class. :) Though, it is class-like semantics. > 2. We introduce data classes, which are similar to records and are mutable by > default You’re referring to structs, not records. > 3. No new constructions to create the new type of classes, no adjustments for > autoloading and other things internally Records aren’t created (aka, "new'd"); that’s an intentional design choice. Whether or not it is a good one is still up for debate. > 4. Eliminate with method, replace `with` method with the new `clone` The new clone is not compatible with records or any other type defined via a constructor (including regular classes). This was a deliberate choice of that implementation. Here's some examples run on that branch: <?php final readonly class Response { public function __construct( public int $statusCode, public string $reasonPhrase, // ... ) { if($this->statusCode >= 600) { throw new LogicException(); } } } $test = new Response(404, "Not Found"); var_dump($test); $test = clone($test, ['statusCode' => 900]); var_dump($test); --- output --- PHP Fatal error: Uncaught Error: Cannot modify protected(set) readonly property Response::$statusCode from global scope in /home/withinboredom/code/php-src/test.php:28 Stack trace: #0 /home/withinboredom/code/php-src/test.php(28): clone() #1 {main} thrown in /home/withinboredom/code/php-src/test.php on line 28 Fatal error: Uncaught Error: Cannot modify protected(set) readonly property Response::$statusCode from global scope in /home/withinboredom/code/php-src/test.php:28 Stack trace: #0 /home/withinboredom/code/php-src/test.php(28): clone() #1 {main} thrown in /home/withinboredom/code/php-src/test.php on line 28 Then removing the readonly distinction: object(Response)#1 (2) { ["statusCode"]=> int(404) ["reasonPhrase"]=> string(9) "Not Found" } object(Response)#2 (2) { ["statusCode"]=> int(900) ["reasonPhrase"]=> string(9) "Not Found" } --- As you can see, it doesn't work for readonly classes and allows invalid mutable objects to be created, requiring devs to rethink how validation will be structured as this will make any constructor validations entirely bypassable in PHP 8.5+. Previously, you needed to use reflection to do this, but now it is going to be incredibly easy and a footgun. I think this will be another weird quirk of PHP from now on and it is by design. > 5. Inline constructor isn’t necessary and could be proposed separately. I’ve > thought recently about this feature I will probably remove this, to be honest. It was for nesting records inside classes or other records, but this feature was declined, so there isn't really a need for it. > > WDYT? > > > (sorry for the duplicate in private mailbox) > > ---------- > > Best regards, > Dmitrii Derepko. > @xepozz Data classes are much more similar to structs than to records -- and are even implemented completely differently. Records are effectively just well-defined arrays with behaviour attached. They're not classes, but offer similar semantics. — Rob