Re: RFC: Records

From: Date: Mon, 14 Jul 2025 18:22:34 +0000
Subject: Re: RFC: Records
References: 1 2  Groups: php.internals 
Request: Send a blank email to internals+get-128042@lists.php.net to get a copy of this message
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

« previous php.internals (#128042) next »