Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:128042 X-Original-To: internals@lists.php.net Delivered-To: internals@lists.php.net Received: from php-smtp4.php.net (php-smtp4.php.net [45.112.84.5]) by lists.php.net (Postfix) with ESMTPS id 446DA1A00BC for ; Mon, 14 Jul 2025 18:22:59 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1752517270; bh=hwRb/gs4d5Xnk/L6dR7ottHctzg0Vcl98B4lTjW6K1I=; h=Date:From:To:Cc:In-Reply-To:References:Subject:From; b=kWa3GicKET+zyUSeuc/efqHvQoPexQQVle5wVQublXgRaRK8ENRPyn0/zqylDTEft zY6c1OI8CnRaXkgzOpqYqtru8z6TnWIlpb0s3cO3Lt2XiUfo6xxeaddKjm7i1TBwa2 4Mx22Cjel1zXBjUUIKWHQ0xGb5ZSnIsqcjX2jiptTVry4sYsA8eZ41vHJuxQrVHnhz knDPre/IfOu0+fKmtxGfxE+Wk3FxyJBGtBixQvFtH5l3SY+6cHDvlBfK2gly8otXGe Y1WV838D3/+ZfjihszqR9ROqYuYicctfMrJxeuEx4kvpmqnVB6xEVuBaNfxOIS2jwi fM9/wSb1k4a6w== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 5E781180039 for ; Mon, 14 Jul 2025 18:21:09 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-2.8 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_MISSING,HTML_MESSAGE, RCVD_IN_DNSWL_LOW,SPF_HELO_PASS,SPF_PASS autolearn=no autolearn_force=no version=4.0.1 X-Spam-Virus: Error (Cannot connect to unix socket '/var/run/clamav/clamd.ctl': connect: Connection refused) X-Envelope-From: Received: from fout-b8-smtp.messagingengine.com (fout-b8-smtp.messagingengine.com [202.12.124.151]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Mon, 14 Jul 2025 18:21:08 +0000 (UTC) Received: from phl-compute-05.internal (phl-compute-05.phl.internal [10.202.2.45]) by mailfout.stl.internal (Postfix) with ESMTP id 1F7D31D002AF; Mon, 14 Jul 2025 14:22:56 -0400 (EDT) Received: from phl-imap-05 ([10.202.2.95]) by phl-compute-05.internal (MEProxy); Mon, 14 Jul 2025 14:22:56 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bottled.codes; h=cc:cc:content-type:content-type:date:date:from:from :in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to; s=fm3; t=1752517375; x= 1752603775; bh=qvpjIyj56h2TlIzRrnpjhslniwhy9DUQe7dlCzAaAk0=; b=Q WfPUrbzFhwJTk1pxlP0l80k7OXfWtpQmS9n8BmgOQffSBhZnB/mrdrWD4FIWB6fL EMBjyOF8Pzlj8lF4mV+54/svnkLmb7YXP9aBAqSzaS9Tn6apkjzA9GknJiCdrvam zUqGoIbw0eCu1DHiUBC7egWF+/l+kWT7TUaLltG0u5pu6BazopXOwS2GkMClCAxD +rIS15SSKN9ghydc4fhWsLyqfEH/pyNIvztTyTb9DX1mlAWZoy76AHZlkFn3QcQP tz3d+mrPfxSAgMM+vOyCDUwj+eE2d0SwxwNwFFhhdLujiLp3Hob4m3F2Csj/qhKV FnZgCkS8GaZxmeiy79Xkw== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-type:content-type:date:date :feedback-id:feedback-id:from:from:in-reply-to:in-reply-to :message-id:mime-version:references:reply-to:subject:subject:to :to:x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s=fm2; t= 1752517375; x=1752603775; bh=qvpjIyj56h2TlIzRrnpjhslniwhy9DUQe7d lCzAaAk0=; b=LPVRId2ccs+U/m12iisnXJzMhQ2YLaDECYtuEh+KpavIlBFyumw 7zwrJ7CsqlehD9HkIYvvOwQpLNjmQ6356ziASWjO+0DXC/Cp+h1TGZSvXrDT41UV I93XIo86pO60V+kKx9BK6MhHju3IQy2AQWd99GZeG+KpB6ESCncQeH/PKj/h0FGS WgywaEC761/u/ygdEHxQqrzLVQRIZFs2fSJu88XID/I8wtzkdP3wwk0uJlldZwet 895gdsqlDIiji9ThQRdqQcEJIhQEUjAV2O/LZPsUBypyuZWJOXkoUDe1bsY5aJFN eKuzjrt2M1VwmqIuL/zKhrKWoVkP3wIXS1w== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeeffedrtdefgdehvdeiiecutefuodetggdotefrod ftvfcurfhrohhfihhlvgemucfhrghsthforghilhdpuffrtefokffrpgfnqfghnecuuegr ihhlohhuthemuceftddtnecunecujfgurhepofggfffhvfevkfgjfhfutgesrgdtreerre dtjeenucfhrhhomhepfdftohgsucfnrghnuggvrhhsfdcuoehrohgssegsohhtthhlvggu rdgtohguvghsqeenucggtffrrghtthgvrhhnpeeiueethedvvdefjefhgfeiheelheehtd fhfeekjefflefgvedvkeduteejjedttdenucevlhhushhtvghrufhiiigvpedtnecurfgr rhgrmhepmhgrihhlfhhrohhmpehrohgssegsohhtthhlvggurdgtohguvghspdhnsggprh gtphhtthhopedvpdhmohguvgepshhmthhpohhuthdprhgtphhtthhopeigvghpohiiiigu sehgmhgrihhlrdgtohhmpdhrtghpthhtohepihhnthgvrhhnrghlsheslhhishhtshdrph hhphdrnhgvth X-ME-Proxy: Feedback-ID: ifab94697:Fastmail Received: by mailuser.phl.internal (Postfix, from userid 501) id 937A01820074; Mon, 14 Jul 2025 14:22:55 -0400 (EDT) X-Mailer: MessagingEngine.com Webmail Interface Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net x-ms-reactions: disallow MIME-Version: 1.0 X-ThreadId: Ta3f42e4d0ede798b Date: Mon, 14 Jul 2025 20:22:34 +0200 To: "Dmitry Derepko" Cc: internals@lists.php.net Message-ID: In-Reply-To: <0D6532F3-6E95-48B9-B394-E9CC1EC00B56@gmail.com> References: <0D6532F3-6E95-48B9-B394-E9CC1EC00B56@gmail.com> Subject: Re: [PHP-DEV] RFC: Records Content-Type: multipart/alternative; boundary=e4060a689181452ca751c81c072c00d8 From: rob@bottled.codes ("Rob Landers") --e4060a689181452ca751c81c072c00d8 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Hey Dmitry, Please remember to bottom post! On Mon, Jul 14, 2025, at 11:16, Dmitry Derepko wrote: > Hi, Rob! >=20 > I'm just wondering, why the implementation differs from a regular clas= s?=20 >=20 > Record makes a "class" immutable and unique, using field value compari= son instead of object refs. It has a custom `with` method which is simil= ar to the new `clone` operator accepted recently. I'd suggest not introd= ucing a new keyword "record" and new syntax to create records, but use r= egular `class` and `new Class`. Class should have a modifier "data" like= readonly, so it should be: >=20 > data class Record { > public function __construct(public $a){} // $a is mutable, because= why not? > } >=20 > Data class will compare with others using field value comparison. More= over, if you want to make it readonly, use the existing keyword to achie= ve this: >=20 > readonly data class Record { > public function __construct(public $a){} // $a is immutable, becau= se of "readonly" class modifier > } Data classes are basically structs (as we collectively discovered last D= ecember when I tried implementing exactly that). Records are implemented= completely differently than structs, even though they seem very similar= . This is largely why they=E2=80=99re completely different keywords. Fur= ther, records were meant to be nested inside other classes, and I did tr= y to actually pull that off with regular classes =E2=80=94 and it was de= clined. This negates the short-syntax and nested aspect of records. >=20 > 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 mut= able by default You=E2=80=99re referring to structs, not records. > 3. No new constructions to create the new type of classes, no adjustme= nts for autoloading and other things internally Records aren=E2=80=99t created (aka, "new'd"); that=E2=80=99s an intenti= onal design choice. Whether or not it is a good one is still up for deba= te. > 4. Eliminate with method, replace `with` method with the new `clone` The new clone is not compatible with records or any other type defined v= ia a constructor (including regular classes). This was a deliberate choi= ce of that implementation. Here's some examples run on that branch: statusCode >=3D 600) { throw new LogicException(); } } } $test =3D new Response(404, "Not Found"); var_dump($test); $test =3D clone($test, ['statusCode' =3D> 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 prope= rty 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"]=3D> int(404) ["reasonPhrase"]=3D> string(9) "Not Found" } object(Response)#2 (2) { ["statusCode"]=3D> int(900) ["reasonPhrase"]=3D> 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 entirel= y 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=E2=80=99t necessary and could be proposed se= parately. I=E2=80=99ve thought recently about this feature I will probably remove this, to be honest. It was for nesting records in= side classes or other records, but this feature was declined, so there i= sn't really a need for it. >=20 > WDYT? >=20 >=20 > (sorry for the duplicate in private mailbox) >=20 > ---------- >=20 > 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 w= ell-defined arrays with behaviour attached. They're not classes, but off= er similar semantics. =E2=80=94 Rob --e4060a689181452ca751c81c072c00d8 Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable
Hey Dmitry,<= /div>

Please remember to bottom post!
<= br>
On Mon, Jul 14, 2025, at 11:16, Dmitry Derepko wrote:
Hi, Rob!

I'm just wondering, why the impl= ementation differs from a regular class? 

= Record makes a "class" immutable and unique, using field value compariso= n instead of object refs. It has a custom `with` method which is similar= to the new `clone` operator accepted recently. I'd suggest not introduc= ing a new keyword "record" and new syntax to create records, but use reg= ular `class` and `new Class`. Class should have a modifier "data" like r= eadonly, so it should be:

data class Record {
    public function __construct(public $a){} // $a is= mutable, because why not?
}

Data cla= ss will compare with others using field value comparison. Moreover, if y= ou want to make it readonly, use the existing keyword to achieve this:

readonly data class Record {
  &n= bsp; public function __construct(public $a){} // $a is immutable, becaus= e of "readonly" class modifier
}

Data classes are basically structs (as we collectively discover= ed last December when I tried implementing exactly that). Records are im= plemented completely differently than structs, even though they seem ver= y similar. This is largely why they=E2=80=99re completely different keyw= ords. Further, records were meant to be nested inside other classes, and= I did try to actually pull that off with regular classes =E2=80=94 and = it was declined. This negates the short-syntax and nested aspect of reco= rds.


1. So record Record {} beco= mes 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 c= lasses, which are similar to records and are mutable by default

You=E2=80=99re referring to structs, not r= ecords.

3. No new constructions to create the n= ew type of classes, no adjustments for autoloading and other things inte= rnally

Records aren=E2=80=99t crea= ted (aka, "new'd"); that=E2=80=99s 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 wit= h records or any other type defined via a constructor (including regular= classes). This was a deliberate choice of that implementation. Here's s= ome examples run on that branch:

<?php
=

final readonly class Response {
 &nbs= p;  public function __construct(
    =     public int $statusCode,
   &= nbsp;    public string $reasonPhrase,
 &nb= sp;      // ...
    ) = {
        if($this->stat= usCode >=3D 600) {
      &nbs= p;     throw new LogicException();
 &= nbsp;      }
    }
=
}

$test =3D new Response(404, "Not F= ound");
var_dump($test);
$test =3D clone($test, ['st= atusCode' =3D> 900]);
var_dump($test);

=
--- output ---

PHP Fatal error:  Unca= ught Error: Cannot modify protected(set) readonly property Response::$st= atusCode 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 /ho= me/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 i= n /home/withinboredom/code/php-src/test.php on line 28

Then removing the readonly distinction:

object(Response)#1 (2) {
  ["statusCode"]=3D>
  int(404)
  ["reasonPhrase"]=3D>
&n= bsp; string(9) "Not Found"
}
object(Response)#2 (2) = {
  ["statusCode"]=3D>
  int(900)
=
  ["reasonPhrase"]=3D>
  string(9) "Not Foun= d"
}

---

As = you can see, it doesn't work for readonly classes and allows invalid mut= able objects to be created, requiring devs to rethink how validation wil= l be structured as this will make any constructor validations entirely b= ypassable in PHP 8.5+. Previously, you needed to use reflection to do th= is, but now it is going to be incredibly easy and a footgun. I think thi= s will be another weird quirk of PHP from now on and it is by design.

5. Inline constructor isn=E2=80=99t necessary and= could be proposed separately. I=E2=80=99ve thought recently about this = feature

I will probably remove thi= s, to be honest. It was for nesting records inside classes or other reco= rds, but this feature was declined, so there isn't really a need for it.=


WDYT?


(sorry for the duplicate in private mailbox)

----------

Best regards,
<= div>Dmitrii Derepko.
@xepozz

Data classes are much more similar to structs tha= n 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.

=E2=80=94 Rob
--e4060a689181452ca751c81c072c00d8--