Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:126920 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 qa.php.net (Postfix) with ESMTPS id E22411A00BC for ; Mon, 24 Mar 2025 08:48:11 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1742805942; bh=8WdJZaBMTP0mG08l+N2xuQkWMZUdQrbVccLbUKEmSb0=; h=Date:From:Cc:In-Reply-To:References:Subject:From; b=lfU9zQKPJKRvLQk/7JD7C1qL2nnCmdQP6auy9FELd5++OTt8S6DiGe6FyO5jj+wEu OEYeK2PqYUaFuzXx2kARFnnpd0HvKZ0TadSmUj+4UVD24SBQ48PXcYZqMBR5cXWSdr HLTiHx8EmZFT/8jTDExHO6Q8BQryYhs4t1lkTjTF0vMmEotoJLhylUusvFTXVKD1ZX X2k13rSsHvubhDpDHi0oDsVvQLCWv/w6g/8aqx8kx+gmnyy6K3P0kOog10s+F2Zywl bJdSsNzivO5rBFCl02sCWS01y0NvPDY0TNeUW0IJIhqFVnvRkRq9NnlS8CkOnGE/CM 0DwHXZkqd+6DQ== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 7369218003C for ; Mon, 24 Mar 2025 08:45:40 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.0 (2022-12-13) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-1.8 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_MISSING,HTML_MESSAGE, MISSING_HEADERS,RCVD_IN_DNSWL_LOW,SPF_HELO_PASS,SPF_PASS autolearn=no autolearn_force=no version=4.0.0 X-Spam-Virus: No X-Envelope-From: Received: from fhigh-b7-smtp.messagingengine.com (fhigh-b7-smtp.messagingengine.com [202.12.124.158]) (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, 24 Mar 2025 08:45:40 +0000 (UTC) Received: from phl-compute-11.internal (phl-compute-11.phl.internal [10.202.2.51]) by mailfhigh.stl.internal (Postfix) with ESMTP id 3B163254017D for ; Mon, 24 Mar 2025 04:48:09 -0400 (EDT) Received: from phl-imap-09 ([10.202.2.99]) by phl-compute-11.internal (MEProxy); Mon, 24 Mar 2025 04:48:09 -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; s=fm2; t=1742806089; x=1742892489; bh=mlpsuRYJX6UDnPt07RO6hbZRBAw1jo6+KhMja7YmI04=; b=ogY4PikVrSOB JwYk7owVzjpNEmVFZbHJe4X4BbvOl+dGo90Iv+0Ktr5TaWUlPu4GkoU/0ktmEfrA Cb1N6SSTduELDMbxZSRLMSOiIoyxL88VLcUvhniXk6PLdiJA+3Lj5ZCoqg0vpktP C2TDg9DqY6xkakvyGk2aDgUVx/MAUV2+MaH7R8Uk5DBsyv2dMrqnMOhIRZBnRrN5 WG531yRyQxJN1DtQ75d+FrXJqb31NVcKpjPm/1KuNgR7OLEd12pFB0YZGXtyMX5M /wNCim4vnz5jsZ5bobmpvC7nr6ELUBHWAZArP6vyTP5tY1ZsfKP2dlu8+CZ3y3r5 B+3WD8jjiA== 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 :x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s=fm2; t= 1742806089; x=1742892489; bh=mlpsuRYJX6UDnPt07RO6hbZRBAw1jo6+KhM ja7YmI04=; b=TTuFxno6/YzGqUBLmGdSs8J+cMrLSSTzepSdMGKF6EUMyN4PUXN Dr5uBGM2ZJH/C2ECMqAZoYli1hhEuP/ChskGI0+KjiLqMqCww76m8Q/up/VK85zT 5p4AW6L2N6k1ffdUrCXQhvhAzjf/O7KPXRoRV9WlLp3uP8GArpwgQwjPb+jZlnnu NnOntGmaxXQH+KOgOGqulSdYvngyr2HFiPajmcXnWG6ErX3cDyWwI2ejACqiwbkt ropCaAz2Jm5eX0Ll4rZcWh6gXCb+pEYJA9n/7neiWHWtRrzc+88gPac+Y+U4QoMT E1QmwTUrmQr0Qwn035rTjJT2NUJyoA2eE3Q== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefvddrtddtgdduheelfeefucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdggtfgfnhhsuhgsshgtrhhisggv pdfurfetoffkrfgpnffqhgenuceurghilhhouhhtmecufedttdenucesvcftvggtihhpih gvnhhtshculddquddttddmnehmihhsshhinhhgucfvqfcufhhivghlugculdeftddmnecu jfgurhepofggfffhvefkjghfufgtsegrtderreertdejnecuhfhrohhmpedftfhosgcunf grnhguvghrshdfuceorhhosgessghothhtlhgvugdrtghouggvsheqnecuggftrfgrthht vghrnhepffduiefgtdfgvefgueetjeevledtfeettdehfffhhedvudeuhffhvddvtdffvd eknecuvehluhhsthgvrhfuihiivgeptdenucfrrghrrghmpehmrghilhhfrhhomheprhho sgessghothhtlhgvugdrtghouggvshdpnhgspghrtghpthhtohepuddpmhhouggvpehsmh htphhouhhtpdhrtghpthhtohepihhnthgvrhhnrghlsheslhhishhtshdrphhhphdrnhgv th X-ME-Proxy: Feedback-ID: ifab94697:Fastmail Received: by mailuser.phl.internal (Postfix, from userid 501) id B231E780068; Mon, 24 Mar 2025 04:48:08 -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: Tb82dc1343eb6237f Date: Mon, 24 Mar 2025 09:47:47 +0100 Cc: "PHP internals" Message-ID: In-Reply-To: References: <3e4ba7ea-a154-452d-abfc-05ef1322fade@app.fastmail.com> <782d76d9-711a-4cee-ae0e-fe0d69973f53@app.fastmail.com> <48dce917-d147-456b-9f03-c7e23411adff@app.fastmail.com> <8a16b81c-7dab-4523-a352-76ba0cb4e771@app.fastmail.com> <9c4ac301-dfb2-49da-90e5-37a2824fc4e3@app.fastmail.com> <5b1e6d70-a1c9-455c-93d3-6b22cf1fef11@app.fastmail.com> Subject: Re: [PHP-DEV] RFC: short and inner classes Content-Type: multipart/alternative; boundary=bc5f9e022986409680ae0036be38bba0 From: rob@bottled.codes ("Rob Landers") --bc5f9e022986409680ae0036be38bba0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Hello, I'm now deep into the rabbit hole of trying out "\" as the separator. It= does give some nice ergonomics when using the nested/inner classes, and= I really like it much better than ":>", but the amount of technical deb= t I have run into is enormous. For now, my implementation will probably = be 'hackish' just to get it working (even though I still have to address= the technical debt). However, it looks like I may have to make namespac= es first-class logical spaces in the engine (instead of string prefixes)= to do it properly. I will probably just share my "hackish" solution for= now. It is simple enough to follow, but I promise you will cringe at th= e number of string operations required to make it work. That being said, the below responses use ":>" as the separator. On Fri, Mar 21, 2025, at 12:15, Rokas =C5=A0leinius wrote: > Hello all, amazing effort as always. >=20 > My 2=C2=A2: >=20 > RL usecase I immediately would adopt this for - is building IDE-suppor= ted complex DTOs. This means autocompletion, refactor rename, inspection= s for undefined keys, etc for the price of using no language =E2=80=9Dha= cks=E2=80=9D. >=20 > A real life example - json response from UPS shipping API - it has 200= ++ keys in up to 10+ levels! The structure keeps changing too, albeit in= glacial speeds, but having it in a structured definition allows one to = not loose ones head when providing continuous support. >=20 > However: there's a *bunch* of multilevel =E2=80=9Dleaf nodes=E2=80=9D = which are reused dozends of times, for example MonetaryAmount - it has t= he numeric value, but also a Currency as a property. >=20 > If extending a class does not inherit the public/protected nested clas= ses, we are forced to define the structure of MonetaryAmount in the =E2=80= =9Dregular=E2=80=9D fashion as separate classes. >=20 > Not to mention this non-inheritance is counter-inuitive IMHO. >=20 > Also, IMHO =E2=80=9Dnested classes=E2=80=9D is a better name. >=20 > Good luck! Hey Rokus, Generally, nested classes do not get inherited in languages that support= them for several reasons: they are mostly scoped types, and they help o= rganize the code and reduce namespace pollution but aren't considered pa= rt of the class hierarchy.=20 If we were to be an outlier and support inheritance, the options for inh= eritance basically boil down to either enforce inheritance in child clas= ses and end up in some really weird situations; or deal with "LSP violat= ions." Let's take a look at an example where we want to add a nested child clas= s that illustrates "weird" if we turn on forced inheritance: abstract class Shipment { protected final class Items {} } Then when we want to add our own Items in our subclass: class InternationalShipment extends Shipment { protected class Items extends Shipment\items {} // ummmm } So, now we have an abstract class that cannot be implemented with any ne= sted class called "Items" even if they are unrelated, they are forcibly = related. If we don't enforce inheritance but simply allow inherited classes to "j= ust work" you can end up with this code: abstract class Shipment { protected class Items {} } class LocalShipment extends Shipment { protected function deliver(self:>Items $items); } and then you later come along and add it as a class: class LocalShipment extends Shipment { protected class Items {} protected function deliver(self:>Items $items) {} // now it refers to = a totally different type! } This can make refactoring a total nightmare, whereas the current solutio= n would have to look like this: class LocalShipment extends Shipment { protected function deliver(Shipment:>Items $items) {} } You can add a class with the name `Items` if you want, and there is no p= roblem at all; everyone reading it knows you mean "*this* particular cla= ss's Items" and they don't have to search the class to see if it is inhe= rited or not. On Sun, Mar 23, 2025, at 16:17, Larry Garfield wrote: > On Wed, Mar 12, 2025, at 5:10 AM, Rob Landers wrote: >=20 > > Hello internals, > > > > I've made some major updates to the text of the RFC to clarify=20 > > behaviors and revisited the implementation (which is still under=20 > > development, though I hope to have a draft by the end of this weeken= d).=20 > > Here's a broad overview of what has changed in inner classes: > > > > - Accessing inner classes is done via a new token: ":>" instead of "= ::". > > - Inner classes may now be infinitely nested. > > - Inner classes may be declared `abstract`. > > - Documented changes to ReflectionClass. > > - Usage of `static` to refer to inner classes is restricted to preve= nt=20 > > accidental violations of LSP. > > > > Otherwise, there are not any big changes, but a lot of time was spen= t=20 > > clarifying behavior and expanding on the reasoning for those decisio= ns=20 > > in the RFC itself. > > > > =E2=80=94 Rob >=20 > I've been following this thread with interest, and at the moment I'm h= onestly undecided. I certainly see the use cases for this functionality= (whatever it gets named), but as a practical matter it sounds like it i= ntroduces a lot of extra clunk and complexity. And it seems like the us= e cases could be addressed as well with either fileprivate or module-pri= vate. (The former being considerably less work.) >=20 > So, how would nested classes compare to fileprivate, in terms of abili= ty to solve the problem space? As I understand it, the goal is: >=20 > 1. Classes that can be instantiated only by the class that uses them. > 2. But can be returned from that class to a caller and reused as appro= priate. >=20 > The autoloading question (loading a whole file for just an implementat= ion detail value object) is not one that carries much weight for me, as = that's a user-space question, not an engine question. (Nothing in PHP i= tself says you cannot put 20 3 line classes or enums together in one fil= e. It's just PSR-4 that says not go. Even composer would allow it if co= nfigured properly) So how would the less-complicated alternative compar= e? >=20 > --Larry Garfield Hey Larry, I think file-private would/could be useful, but that only limits you to = a "private" scope, which severely hampers what you can do with it. If we= went with "module-private" (rhetorical question: what is a module?), bu= t then you wouldn't be able to have "private" scope. With nested/inner classes, for example, you can put a protected class on= a class or interface and access it only from those that use it, regardl= ess of what file or "module" (namespace?) they are in; the logic can be = encapsulated to where it is used, not where it is defined. interface Shipment { protected class Items {} public readonly class Destination {} function deliver(self:>Destination $destination); } class InternationalShipment implements Shipment { private function handle(Shipment:>Items $items) {} public function deliver(Shipment:>Destination $destination) {} } However, you can use private nested/inner classes to encapsulate the log= ic to where it is defined instead, like file-private would give you. The goal here isn't to only to reduce "namespace pollution" but also to = encapsulate related logic integrally connected to the outer class's beha= vior. Since you've referenced Kotlin a number of times, I assume you are= familiar with the concept of nested classes there? This is extremely si= milar. It's also worth pointing out that the nested/inner classes are more like= "friend classes", just like in other languages, which is something you = don't get from file-private or module-private. Oh, and I don't think fil= e-private or module-private is less complicated :) just a different kind= of complicated... For the latter, I'm guessing we'd bikeshed for months= just deciding what a "module" even is. In any case, I'm going to be tac= kling that (module-private) next-ish; regardless of whether this RFC pas= ses. The plumbing and implementation details can be modified for that pu= rpose, and the changes are fairly trivial. Both of these, together, woul= d be immensely powerful tools. Though, even just one of them would still= be awesome. =E2=80=94 Rob --bc5f9e022986409680ae0036be38bba0 Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable
Hello,

I'm now= deep into the rabbit hole of trying out "\" as the separator. It does g= ive some nice ergonomics when using the nested/inner classes, and I real= ly like it much better than ":>", but the amount of technical debt I = have run into is enormous. For now, my implementation will probably be '= hackish' just to get it working (even though I still have to address the= technical debt). However, it looks like I may have to make namespaces f= irst-class logical spaces in the engine (instead of string prefixes) to = do it properly. I will probably just share my "hackish" solution for now= . It is simple enough to follow, but I promise you will cringe at the nu= mber of string operations required to make it work.

That being said, the below responses use ":>" as the separat= or.

On Fri, Mar 21, 2025, at 12:15, Rokas =C5= =A0leinius wrote:
Hello all, amazing effort as always.

My 2=C2= =A2:

RL usecase I immediately would adopt this for - is building ID= E-supported complex DTOs. This means autocompletion, refactor rename, in= spections for undefined keys, etc for the price of using no language =E2= =80=9Dhacks=E2=80=9D.

A real life example - json response from UPS = shipping API - it has 200++ keys in up to 10+ levels! The structure keep= s changing too, albeit in glacial speeds, but having it in a structured = definition allows one to not loose ones head when providing continuous s= upport.

However: there's a *bunch* of multilevel =E2=80=9Dleaf node= s=E2=80=9D which are reused dozends of times, for example MonetaryA= mount - it has the numeric value, but also a Currency as a property.
=

If extending a class does not inherit the public/protected nested clas= ses, we are forced to define the structure of MonetaryAmount in the =E2=80= =9Dregular=E2=80=9D fashion as separate classes.

Not to mention thi= s non-inheritance is counter-inuitive IMHO.

Also, IMHO =E2=80=9Dnes= ted classes=E2=80=9D is a better name.

Good luck!

Hey Rokus,

Generally, nested classes do not get inherited in languages tha= t support them for several reasons: they are mostly scoped types, and th= ey help organize the code and reduce namespace pollution but aren't cons= idered part of the class hierarchy.

If we = were to be an outlier and support inheritance, the options for inheritan= ce basically boil down to either enforce inheritance in child classes an= d end up in some really weird situations; or deal with "LSP violations."=

Let's take a look at an example where we w= ant to add a nested child class that illustrates "weird" if we turn on f= orced inheritance:

abstract class Shipment = {
  protected final class Items {}
}

Then when we want to add our own Items in our= subclass:

class InternationalShipment exte= nds Shipment {
  protected class Items extends Shipme= nt\items {} // ummmm
}

So, no= w we have an abstract class that cannot be implemented with any nested c= lass called "Items" even if they are unrelated, they are forcibly relate= d.

If we don't enforce inheritance but simp= ly allow inherited classes to "just work" you can end up with this code:=

abstract class Shipment {
&n= bsp; protected class Items {}
}

class LocalShipment extends Shipment {
  protected= function deliver(self:>Items $items);
}
=
and then you later come along and add it as a class:
<= /div>

class LocalShipment extends Shipment {
  protected class Items {}
  protected fun= ction deliver(self:>Items $items) {} // now it refers to a totally di= fferent type!
}

This can make= refactoring a total nightmare, whereas the current solution would have = to look like this:

class LocalShipment exte= nds Shipment {
  protected function deliver(Shipment:= >Items $items) {}
}

You ca= n add a class with the name `Items` if you want, and there is no problem= at all; everyone reading it knows you mean "this particular clas= s's Items" and they don't have to search the class to see if it is inher= ited or not.

On Sun, Mar 23, 2025, at 16:17= , Larry Garfield wrote:
On Wed, Mar 12, 2025, at 5:10 AM, Rob Landers wrote:

> Hello internals,
>
> I've made some major updates to the text of the RFC to clarif= y 
> behaviors and revisited the implementation (w= hich is still under 
> development, though I hope = to have a draft by the end of this weekend). 
> He= re's a broad overview of what has changed in inner classes:
>
> - Accessing inner classes is done via a new to= ken: ":>" instead of "::".
> - Inner classes may now= be infinitely nested.
> - Inner classes may be declare= d `abstract`.
> - Documented changes to ReflectionClass= .
> - Usage of `static` to refer to inner classes is re= stricted to prevent 
> accidental violations of LS= P.
>
> Otherwise, there are not any bi= g changes, but a lot of time was spent 
> clarifyi= ng behavior and expanding on the reasoning for those decisions 
=
> in the RFC itself.
>
>= =E2=80=94 Rob

I've been following this thr= ead with interest, and at the moment I'm honestly undecided.  I cer= tainly see the use cases for this functionality (whatever it gets named)= , but as a practical matter it sounds like it introduces a lot of extra = clunk and complexity.  And it seems like the use cases could be add= ressed as well with either fileprivate or module-private.  (The for= mer being considerably less work.)

So, how = would nested classes compare to fileprivate, in terms of ability to solv= e the problem space?  As I understand it, the goal is:

1. Classes that can be instantiated only by the class t= hat uses them.
2. But can be returned from that class to a= caller and reused as appropriate.

The auto= loading question (loading a whole file for just an implementation detail= value object) is not one that carries much weight for me, as that's a u= ser-space question, not an engine question.  (Nothing in PHP itself= says you cannot put 20 3 line classes or enums together in one file.&nb= sp; It's just PSR-4 that says not go. Even composer would allow it if co= nfigured properly)  So how would the less-complicated alternative c= ompare?

--Larry Garfield

Hey Larry,

I think = file-private would/could be useful, but that only limits you to a "priva= te" scope, which severely hampers what you can do with it. If we went wi= th "module-private" (rhetorical question: what is a module?), but then y= ou wouldn't be able to have "private" scope.

With nested/inner classes, for example, you can put a protected class = on a class or interface and access it only from those that use it, regar= dless of what file or "module" (namespace?) they are in; the logic can b= e encapsulated to where it is used, not where it is defined.

interface Shipment {
  protected cl= ass Items {}
  public readonly class Destination {}
  function deliver(self:>Destination $destination);=
}

class InternationalShipmen= t implements Shipment {
  private function handle(Shi= pment:>Items $items) {}

  public fu= nction deliver(Shipment:>Destination $destination) {}
}=

However, you can use private nested/inner = classes to encapsulate the logic to where it is defined instead, like fi= le-private would give you.

The goal here isn't = to only to reduce "namespace pollution" but also to encapsulate related = logic integrally connected to the outer class's behavior. Since you've r= eferenced Kotlin a number of times, I assume you are familiar with the c= oncept of nested classes there? This is extremely similar.

It's also worth pointing out that the nested/inner class= es are more like "friend classes", just like in other languages, which i= s something you don't get from file-private or module-private. Oh, and I= don't think file-private or module-private is less complicated :) just = a different kind of complicated... For the latter, I'm guessing we'd bik= eshed for months just deciding what a "module" even is. In any case, I'm= going to be tackling that (module-private) next-ish; regardless of whet= her this RFC passes. The plumbing and implementation details can be modi= fied for that purpose, and the changes are fairly trivial. Both of these= , together, would be immensely powerful tools. Though, even just one of = them would still be awesome.

=E2=80=94 Rob
--bc5f9e022986409680ae0036be38bba0--