Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:126939 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 F30611A00BC for ; Tue, 25 Mar 2025 20:29:46 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1742934437; bh=+douYQC/+L/lePP/S0VF2VugjiyIam+X/+5QEJWE9Ec=; h=Date:From:To:In-Reply-To:References:Subject:From; b=d6uK/9U0iqKYbKGDtCVmPWpNUrWENO8m3pUmJw48v15ZvLEHWTWrq/E1QrWkc1z01 G7TL4Iy4lOfg+dYREyq/JesFGCOrHUs8W1B1yfUlRYcGEun0Jl5R108C4wUo850E8y CBaoM3AXTYRsz3+fZoHrUaodDAEmMNUO5v85+D7b+lpN/GStnrbitQ+Np5cVD2OZXo WbT/jcoJI7GmgwrOglB2kHC4H7lpZ8mX0jhyTsq1Tst5xI1bySsaHsBsTzLih45ydB LHZKXdi77+uKj+ft6FN7b9GzOMXcaV3a5lyJUpoEK4ZwJYfqBqHfNwqV/2tvUwBr4k T7IF5D38ZSemg== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 4A138180086 for ; Tue, 25 Mar 2025 20:27:16 +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=-4.6 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_MISSING,HTML_MESSAGE, RCVD_IN_DNSWL_LOW,RCVD_IN_MSPIKE_H2,SPF_HELO_PASS,SPF_PASS autolearn=no autolearn_force=no version=4.0.0 X-Spam-Virus: No X-Envelope-From: Received: from fout-b5-smtp.messagingengine.com (fout-b5-smtp.messagingengine.com [202.12.124.148]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Tue, 25 Mar 2025 20:27:16 +0000 (UTC) Received: from phl-compute-11.internal (phl-compute-11.phl.internal [10.202.2.51]) by mailfout.stl.internal (Postfix) with ESMTP id CEC8D114016F for ; Tue, 25 Mar 2025 16:29:44 -0400 (EDT) Received: from phl-imap-09 ([10.202.2.99]) by phl-compute-11.internal (MEProxy); Tue, 25 Mar 2025 16:29:44 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bottled.codes; h=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=1742934584; x=1743020984; bh=sTHMPrV/SV uCmsP3H+8Kl9/3raHasxcQMo2nw+Sxcww=; b=WRFrwmyaZFI2Qj4b2Fe+dIu74s 130K/bFjYB0R2NVq2Ijsq+VrFOCMoNBdy3TLcU7Hk6LzHHeFw6VMOs/d17JtSihv qqce51zZNgyCvbyeWAn3FwBbkMcpm8D+4s82RCVVc6PWVwLJ8IuhftydQ5XeadDa oMSRJ4TgwTbmIgJBGij45+zIZI5CNU5ONwG9NMS2S6D7hg/fU4vsfd1NrUoG8O3I /vgXBOgjhxrig+fHckAqCdSVCi9I6jAm3Ln0TqNC7KdoYrVwxQ5gtemN9yNd3Jn/ LC/eseRUuhBjo7BjJaBKbUBvmd+RGRAHiE5OcTk4FzZ5SNA9iq0m/oibspiA== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=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= 1742934584; x=1743020984; bh=sTHMPrV/SVuCmsP3H+8Kl9/3raHasxcQMo2 nw+Sxcww=; b=ZO8LJUs57GmxHSovyc2Xct9QBqXSuqKae+55/cX1mSaXGThKcMn 3JDZU9JYXdw+eqE2bKLfdF6nqBnOVy6a/4xG85sBS80CY1nS7ki7XQCA4td1Rxi1 olz4EU6pi7Ia4Gwd2vlxdEpEKG5YCQ82hcbp3n5whG+IO9NBlw7Nl+X0/PBYCoQx ufoPXKn5XEWMfbSmI6SIvamvXb0vNEqctqxf5wB2o0XMPG7md1yKrCyhqIPp00/z lo4IoYGRA5yYWa4+FXqvvXUJ4n3wZRZgKv9h/zQGyXY1IwoigknESaPNKCJIBBlt F6m+1y/zdiFJg3kgXmKZJ5Bvc/BKNKWW59g== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefvddrtddtgdduieefieduucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdggtfgfnhhsuhgsshgtrhhisggv pdfurfetoffkrfgpnffqhgenuceurghilhhouhhtmecufedttdenucenucfjughrpefogg ffhffvkfgjfhfutgesrgdtreerredtjeenucfhrhhomhepfdftohgsucfnrghnuggvrhhs fdcuoehrohgssegsohhtthhlvggurdgtohguvghsqeenucggtffrrghtthgvrhhnpefhtd euledtueetueejjeeigfejkeetvdejffduvefggeeuffefgfejlefgtedthfenucffohhm rghinhepghhithhhuhgsrdgtohhmpdhsphgvtgdqsghrrghinhhsthhorhhmrdhmugenuc evlhhushhtvghrufhiiigvpedtnecurfgrrhgrmhepmhgrihhlfhhrohhmpehrohgssegs ohhtthhlvggurdgtohguvghspdhnsggprhgtphhtthhopedupdhmohguvgepshhmthhpoh huthdprhgtphhtthhopehinhhtvghrnhgrlhhssehlihhsthhsrdhphhhprdhnvght X-ME-Proxy: Feedback-ID: ifab94697:Fastmail Received: by mailuser.phl.internal (Postfix, from userid 501) id 4D4B8780068; Tue, 25 Mar 2025 16:29:44 -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 Date: Tue, 25 Mar 2025 21:29:16 +0100 To: internals@lists.php.net Message-ID: In-Reply-To: <09a82882-f1ee-4bdb-8a27-e46144a711f1@app.fastmail.com> 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> <52d84a5b-09d3-4e42-9620-a62fb239c21e@app.fastmail.com> <09a82882-f1ee-4bdb-8a27-e46144a711f1@app.fastmail.com> Subject: Re: [PHP-DEV] RFC: short and inner classes Content-Type: multipart/alternative; boundary=9bfa3f44b5b748be8aad0490bbd1d2c3 From: rob@bottled.codes ("Rob Landers") --9bfa3f44b5b748be8aad0490bbd1d2c3 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable On Tue, Mar 25, 2025, at 19:51, Larry Garfield wrote: > On Tue, Mar 25, 2025, at 6:59 AM, Rob Landers wrote: >=20 > >> When I say module scope, I'm referring to something along the lines= that Arnaud and I were exploring a while back. tldr, "cluster of files= with a common namespace root, which can get loaded together." It was m= ostly about performance, but did offer module-private as well, with some= nice potential. At the moment it's stalled out on "there's nasty hard = edge cases and we're not sure if it's worth it" concerns. > >>=20 > >> Concept brain dump here: https://github.com/Crell/php-rfcs/blob/mas= ter/modules/spec-brainstorm.md > >> Code exploration from Arnaud here: https://github.com/arnaud-lb/php= -src/pull/10 > >>=20 > >> Still well short of RFC state, of course, but provided for context. > > > > My email has been broken for a few days, so sorry for the late respo= nse... >=20 > No worries, I'm about to leave town myself. :-) >=20 > >> In this case, I am not seeing what the nesting gets you. Making De= stination a normal class doesn't hurt anything here, does it? > > > > I wasn't intending to write a definitive example, just to illustrate=20 > > it. A better example might be a lazy cache, using hooks to notify th= e=20 > > outer class it has possibly been updated:=20 > > > > (note this is using the new syntax, but this syntax is NOT currently=20 > > reflected in the RFC text, yet) > > > > namespace Caching; > > > > class Cache { > > private array $items =3D []; > > private array $dirty =3D []; > > > > public function getItem(string $key): CacheItem { > > return $this->items[$key] ?? ($this->items[$key] =3D new=20 > > CacheItem($this, $key, null)); > > } > > > > public function saveChanges(): void { > > foreach ($this->dirty as $key =3D> $value) { > > echo "Saving $key to persistent storage\n"; > > } > > $this->dirty =3D []; > > } > > > > private function markDirty(string $key): void { > > $this->dirty[$key] =3D $this->items[$key]; > > } > > > > public class CacheItem { > > public string|null $value { > > get { > > return $this->_value; > > } > > set { > > $this->_value =3D $value; > > $this->cache->markDirty($this->key); > > } > > } > > > > public function __construct( > > private readonly Cache $cache, > > public private(set) string $key, > > private string|null $_value, > > ) {} > > } > > } > > > > $cache =3D new Cache(); > > $item1 =3D $cache->getItem('foo'); > > $item1->value =3D 'bar'; > > > > $cache->saveChanges(); > > > > This outputs: > > > > Saving foo to persistent storage > > > > This provides for a simple API for outside the Cache class: getItem(= )=20 > > and saveChanges(); that's it. For CacheItem's, you only have the pro= ps=20 > > for the key or value, and updating the value marks the item as "dirt= y". > > > > Currently, if you want this same API, the only way to do this is by: > > > > 1. using reflection, > > 2. using a closure and binding it to the other class's scope, > > 3. providing a listener + observer (more formal version of [2]), or > > 3. iterating over items while saving to find dirty items. > > > > Those choices are not ideal (IMHO). > > > > Reflection feels "hacky" as does using a bound closure. The=20 > > listener/observer pattern is probably overkill here, unless you alre= ady=20 > > have one lying around (i.e., using symfony/laravel or a related=20 > > framework), but still might require making some of the "internal" ap= i=20 > > public. Finally, iterating over all the items to find dirty ones is=20 > > naive and inefficient. > > > > Hopefully this is a better illustration of what nesting provides? > > > > -- Rob > =20 > I have a similar if less involved use case in my ordering library, tho= ugh it doesn't need the look-back functionality. >=20 > So the use case that nested classes would enable that isn't currently = covered by "just use separate files and @internal, deal" is around the l= esser class having private access to the greater class. Which... I beli= eve fileprivate and modules would also address, albeit in a different wa= y, yes? (Presuming fileprivate is available on properties, not just the= class itself.) >=20 > --Larry Garfield Well ... to use the best engineering response: "it depends..." File-private, in my mind, is different; such as when the classes are dis= tinctly unrelated with respect to each other, but nobody else should be = able to instantiate or use the private class. It is like it doesn't exis= t outside that file. For example, something like a log formatter or a de= fault strategy implementation. Personally, I'd feel that file-private sh= ould be kept as simple as possible and limit it to "top-level" things, b= ut that doesn't necessarily have to be the case. If we did allow it on m= ethods/properties, when mixing it with regular visibility, what happens?= `fileprivate public private(set)` ... means what exactly? I assume we p= robably wouldn't allow that particular scenario, and maybe `fileprivate`= on a property means `public` in the file, but `private` outside the fil= e. But then how would that intersect with inheritance? My point is that = I don't think there is an intuitive answer to these behaviors, but at le= ast nested/inner classes, while not 100% intuitive either, at least are = available in other languages, so its behavior will be familiar. If we were to target php 9, then we could simply redefine what private e= ven means, similar to Rowan's findings up thread (emphasis mine), and no= t even have a special keyword: On Mon, Mar 24, 2025, at 20:22, Rowan Tommins [IMSoP] wrote: > If you'll excuse a brief philosophical detour, I noticed something int= eresting in the Swift documentation: the description of nested classes d= oesn't describe any special scope access. Instead, the description of "a= ccess levels" defines "private" in a way that naturally includes them: >=20 >> Private access restricts the use of an entity to the enclosing declar= ation, and to extensions of that declaration that *are in the same file*. >=20 > It's a subtly different framing - nested types aren't breaking into th= e private entity, the "private" keyword is explicitly letting nested typ= es in. Module-private, on the other hand, would be a huge boon to php; for a nu= mber of reasons. After reading your brain-dump, it would be really inter= esting if we could package a binary representation of the module alongsi= de the module itself -- maybe not in an initial version, but later -- so= that it could basically be "pre-loaded" right into OPcache... But I dig= ress. Your module idea looks far simpler to implement than this one :D=20 I'd be happy to give it a go, if you're up for it and no one else is wor= king on it. It may be a few months, though; if this one passes, I want t= o go for short-classes next, plus I assume there is an expectation that = I would fix bugs. =E2=80=94 Rob --9bfa3f44b5b748be8aad0490bbd1d2c3 Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable
On Tue, Mar 25,= 2025, at 19:51, Larry Garfield wrote:
On Tue, Mar 25, 2025, at 6:59 AM, Rob Lander= s wrote:

>> When I say module scope, = I'm referring to something along the lines that Arnaud and I were explor= ing a while back.  tldr, "cluster of files with a common namespace = root, which can get loaded together."  It was mostly about performa= nce, but did offer module-private as well, with some nice potential.&nbs= p; At the moment it's stalled out on "there's nasty hard edge cases and = we're not sure if it's worth it" concerns.
>> <= br>
>> Code exploration from Arnaud here: https://github.com/arnaud= -lb/php-src/pull/10
>> 
>&= gt; Still well short of RFC state, of course, but provided for context.<= br>
>
> My email has been broken for a few= days, so sorry for the late response...

No= worries, I'm about to leave town myself. :-)

>> In this case, I am not seeing what the nesting gets you.&nbs= p; Making Destination a normal class doesn't hurt anything here, does it= ?
>
> I wasn't intending to write a de= finitive example, just to illustrate 
> it. A bett= er example might be a lazy cache, using hooks to notify the 
> outer class it has possibly been updated: 
>
> (note this is using the new syntax, but this = syntax is NOT currently 
> reflected in the RFC te= xt, yet)
>
> namespace Caching;
>
> class Cache {
> &n= bsp;   private array $items =3D [];
> &n= bsp;   private array $dirty =3D [];
>
>     public function getItem(string $key)= : CacheItem {
>      &nbs= p;  return $this->items[$key] ?? ($this->items[$key] =3D new&= nbsp;
> CacheItem($this, $key, null));
&g= t;     }
>
> =     public function saveChanges(): void {
&= gt;         foreach ($this->d= irty as $key =3D> $value) {
>    = ;         echo "Saving $key to p= ersistent storage\n";
>     &n= bsp;   }
>      = ;   $this->dirty =3D [];
>  &nbs= p;  }
>
>    = private function markDirty(string $key): void {
> = ;        $this->dirty[$key] =3D $t= his->items[$key];
>     }
>
>     public class Cac= heItem {
>       &nb= sp; public string|null $value {
>   &nbs= p;         get {
&= gt;           &nb= sp;     return $this->_value;
>&= nbsp;            = }
>        &nbs= p;    set {
>    &nb= sp;           $this-&g= t;_value =3D $value;
>     &nb= sp;          $this->cach= e->markDirty($this->key);
>   &nbs= p;         }
>&= nbsp;        }
>
=
>         public f= unction __construct(
>     &nb= sp;       private readonly Cache $cache,
>         &= nbsp;   public private(set) string $key,
>&nb= sp;            pr= ivate string|null $_value,
>    &nb= sp;    ) {}
>     }<= br>
> }
>
> $cache =3D ne= w Cache();
> $item1 =3D $cache->getItem('foo');
<= /div>
> $item1->value =3D 'bar';
>
<= div>> $cache->saveChanges();
>
>= This outputs:
>
> Saving foo to persi= stent storage
>
> This provides for a = simple API for outside the Cache class: getItem() 
&g= t; and saveChanges(); that's it. For CacheItem's, you only have the prop= s 
> for the key or value, and updating the value = marks the item as "dirty".
>
> Current= ly, if you want this same API, the only way to do this is by:
<= div>>
> 1. using reflection,
> 2. u= sing a closure and binding it to the other class's scope,
= > 3. providing a listener + observer (more formal version of [2]), or=
> 3. iterating over items while saving to find dirty i= tems.
>
> Those choices are not ideal = (IMHO).
>
> Reflection feels "hacky" a= s does using a bound closure. The 
> listener/obse= rver pattern is probably overkill here, unless you already 
> have one lying around (i.e., using symfony/laravel or a rela= ted 
> framework), but still might require making = some of the "internal" api 
> public. Finally, ite= rating over all the items to find dirty ones is 
>= naive and inefficient.
>
> Hopefully = this is a better illustration of what nesting provides?
&g= t;
> -- Rob
 
I have a= similar if less involved use case in my ordering library, though it doe= sn't need the look-back functionality.

So t= he use case that nested classes would enable that isn't currently covere= d by "just use separate files and @internal, deal" is around the lesser = class having private access to the greater class.  Which... I belie= ve fileprivate and modules would also address, albeit in a different way= , yes?  (Presuming fileprivate is available on properties, not just= the class itself.)

--Larry Garfield

Well ... to use the best engineering= response: "it depends..."

File-private, in= my mind, is different; such as when the classes are distinctly unrelate= d with respect to each other, but nobody else should be able to instanti= ate or use the private class. It is like it doesn't exist outside that f= ile. For example, something like a log formatter or a default strategy i= mplementation. Personally, I'd feel that file-private should be kept as = simple as possible and limit it to "top-level" things, but that doesn't = necessarily have to be the case. If we did allow it on methods/propertie= s, when mixing it with regular visibility, what happens? `fileprivate pu= blic private(set)` ... means what exactly? I assume we probably wouldn't= allow that particular scenario, and maybe `fileprivate` on a property m= eans `public` in the file, but `private` outside the file. But then how = would that intersect with inheritance? My point is that I don't think th= ere is an intuitive answer to these behaviors, but at least nested/inner= classes, while not 100% intuitive either, at least are available in oth= er languages, so its behavior will be familiar.

=
If we were to target php 9, then we could simply redefine what priv= ate even means, similar to Rowan's findings up thread (emphasis mine), a= nd not even have a special keyword:

On Mon,= Mar 24, 2025, at 20:22, Rowan Tommins [IMSoP] wrote:
If you'll excuse a brief phil= osophical detour, I noticed something interesting in the Swift documenta= tion: the description of nested classes doesn't describe any special sco= pe access. Instead, the description of "access levels" defines "private"= in a way that naturally includes them:

Private access restricts the= use of an entity to the enclosing declaration, and to extensions of tha= t declaration that are in the same file.

It's a subtly different framing - nested types aren't = breaking into the private entity, the "private" keyword is explicitly le= tting nested types in.

Module-= private, on the other hand, would be a huge boon to php; for a number of= reasons. After reading your brain-dump, it would be really interesting = if we could package a binary representation of the module alongside the = module itself -- maybe not in an initial version, but later -- so that i= t could basically be "pre-loaded" right into OPcache... But I digress. Y= our module idea looks far simpler to implement than this one :D

I'd be happy to give it a go, if you're up for it = and no one else is working on it. It may be a few months, though; if thi= s one passes, I want to go for short-classes next, plus I assume there i= s an expectation that I would fix bugs.

=E2=80=94 Rob
--9bfa3f44b5b748be8aad0490bbd1d2c3--