Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:126932 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 A8C511A00BC for ; Tue, 25 Mar 2025 11:59:45 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1742903836; bh=+Elr6ty7bEtDoIZl2nu5gXFWq4ZQZqSDgvbs2GF9O7o=; h=Date:From:Cc:In-Reply-To:References:Subject:From; b=W3/HkKQQLULn0ovNDCYCU8a2oIH+vo2Ay4uBvyQpAHjUUlaCdzTISkrSajmAHuTG0 nqwSaKhbn6fgjQ04Ov/3knfAaWsQstCaKBKy9biNsc1gEcj094JohAK7c+Tw69haCF W2Gs/Ocf9TP8XuJzUPBH4o9NY/iq+ZOkjrL2jJDd5eWBdA2zMUVD79q2nmfWB8e6LU ycX/e1OdrHrJit0x96wpMB6//EXtjUGPE4tUFSarVfKYVx1/GKLTpWF0ZvizUXr5kM dmNqmckwk8/q6mDu+UTYOxy+Ekcs6J3BHxN7HgcYB+U6/lQ8atImm2Ij6AKjl93lSn jeXPqCeRMo3FQ== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 98CAF180053 for ; Tue, 25 Mar 2025 11:57:14 +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 fout-b7-smtp.messagingengine.com (fout-b7-smtp.messagingengine.com [202.12.124.150]) (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 ; Tue, 25 Mar 2025 11:57:14 +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 D1467114018B for ; Tue, 25 Mar 2025 07:59:42 -0400 (EDT) Received: from phl-imap-09 ([10.202.2.99]) by phl-compute-11.internal (MEProxy); Tue, 25 Mar 2025 07:59:42 -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=fm3; t=1742903982; x=1742990382; bh=z6yoGIuZ8fdV3XhYWJJeIhhLTTdlJBXlKh6Uv3C5jSg=; b=vJ5BeteaHrg9 SojnVbaEW0pSy0mTczBmp/x+cAQamCChJ9C/1YpamK/Rmzfnt92bdD7aHNZlgvfR acrNYBeFBKZ4gfe2bAj8UWFQ3rE0J9A4/sPO7DmXOOtES7PhqlZE9N97QZ+WtHnh xWSIdw1ALm8jO8KLAuYhnBdyb54QHmUSk0wc/fiKQLuiJwi2tIfBl2pSWvLdVeYa phSZ3c5n8M5Wqzcy43UFNjDPy39i66ZPpOO65Jxa4+CzrA3h+ju0GZzq28IR2YPq vZONFg9IuhLVZoherT+SbQWfz6L2zdSjWwRdeLdO37Df2GllBSBw+nqA0pLBemq/ F+fDbChK3w== 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= 1742903982; x=1742990382; bh=z6yoGIuZ8fdV3XhYWJJeIhhLTTdlJBXlKh6 Uv3C5jSg=; b=Ft9MY/VQAHf4zzeOHKGn0YZJnOnJYena2tayyGplsOsVT1vAfa8 v7nJp2YeV/jjxgixhXoxqybL9orsuoidPTQ3f7hO5ZyYeMKfEHhffZDfshLrjPBw fw4003DSoiAYEt7RMMa5kbnezw6Lxqgb5pbTCeNxyIwPDjaiS7O1LEn1dlvyaepD HqvZbsBe0ztk8y8UcP+HX9iHu6qQUzcGYx9dnlXTOYnVQP7RZ5fH5U7CoExtK3T5 v34UcS7CmXg3ca/Op498UKlFA7EfixxvkTHbwkL7S2xnz1ejZLP1hfsDcxHIQT/A f0PBKp2RQyYfzN4+tvTB9vP930xuSHdVoYQ== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefvddrtddtgdduiedvheelucetufdoteggodetrf dotffvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdggtfgfnhhsuhgsshgtrhhisggv pdfurfetoffkrfgpnffqhgenuceurghilhhouhhtmecufedttdenucesvcftvggtihhpih gvnhhtshculddquddttddmnegoufhushhpvggtthffohhmrghinhculdegledmnehmihhs shhinhhgucfvqfcufhhivghlugculdeftddmnecujfgurhepofggfffhvefkjghfufgtse grtderreertdejnecuhfhrohhmpedftfhosgcunfgrnhguvghrshdfuceorhhosgessgho thhtlhgvugdrtghouggvsheqnecuggftrfgrthhtvghrnhepkeffkeejkedtieffffdvhe etueeuleeflefgvdduleffudelkeefveefveejleeinecuffhomhgrihhnpehgihhthhhu sgdrtghomhdpshhpvggtqdgsrhgrihhnshhtohhrmhdrmhgupdefvheglhdrohhrghenuc evlhhushhtvghrufhiiigvpedtnecurfgrrhgrmhepmhgrihhlfhhrohhmpehrohgssegs ohhtthhlvggurdgtohguvghspdhnsggprhgtphhtthhopedupdhmohguvgepshhmthhpoh huthdprhgtphhtthhopehinhhtvghrnhgrlhhssehlihhsthhsrdhphhhprdhnvght X-ME-Proxy: Feedback-ID: ifab94697:Fastmail Received: by mailuser.phl.internal (Postfix, from userid 501) id 42D22780068; Tue, 25 Mar 2025 07:59:42 -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: Tue, 25 Mar 2025 12:59:20 +0100 Cc: "PHP internals" Message-ID: <52d84a5b-09d3-4e42-9620-a62fb239c21e@app.fastmail.com> 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=3381e71ef9ff4224b7ce6382e2135879 From: rob@bottled.codes ("Rob Landers") --3381e71ef9ff4224b7ce6382e2135879 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable On Mon, Mar 24, 2025 at 5:12=E2=80=AFPM Larry Garfield wrote: > On Mon, Mar 24, 2025, at 3:47 AM, Rob Landers wrote: >=20 > > On Sun, Mar 23, 2025, at 16:17, Larry Garfield wrote: >=20 > >> I've been following this thread with interest, and at the moment I'= m honestly undecided. I certainly see the use cases for this functional= ity (whatever it gets named), but as a practical matter it sounds like i= t introduces a lot of extra clunk and complexity. And it seems like the= use cases could be addressed as well with either fileprivate or module-= private. (The former being considerably less work.) > >> > >> So, how would nested classes compare to fileprivate, in terms of ab= ility to solve the problem space? As I understand it, the goal is: > >> > >> 1. Classes that can be instantiated only by the class that uses the= m. > >> 2. But can be returned from that class to a caller and reused as ap= propriate. > >> > >> The autoloading question (loading a whole file for just an implemen= tation 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 PH= P itself says you cannot put 20 3 line classes or enums together in one = file. It's just PSR-4 that says not go. Even composer would allow it if= configured properly) So how would the less-complicated alternative com= pare? > >> > >> --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= ?), > > but then you wouldn't be able to have "private" scope. >=20 > When I say module scope, I'm referring to something along the lines th= at Arnaud and I were exploring a while back. tldr, "cluster of files wi= th a common namespace root, which can get loaded together." It was most= ly about performance, but did offer module-private as well, with some ni= ce potential. At the moment it's stalled out on "there's nasty hard edg= e cases and we're not sure if it's worth it" concerns. >=20 > Concept brain dump here: https://github.com/Crell/php-rfcs/blob/master= /modules/spec-brainstorm.md > Code exploration from Arnaud here: https://github.com/arnaud-lb/php-sr= c/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 response.= .. I suspect I know exactly what edges you were running into now... I'm now= nearing completion of using `\` and I did end up just making namespaces= into logical structures (this is mostly used for bookkeeping, but with = ~3 lines of code -- minus grammar changes -- you can support module priv= acy). The loading aspect (loading together) would probably run into the = same issue I ran into: class binding order. This is illustrated here: ht= tps://3v4l.org/g9vC4 With PSR-4 autoloading, we basically never run into this problem because= as soon as we compile C, we'll late-bind it to B, which triggers the au= toloader, which eventually binds A, then B, then C, in the correct order= . With nested classes (or a module), order matters. I know how to fix it= , at least for nested/inner classes so they can be linked at compile tim= e. I suspect the solution would also apply to modules, but I don't think= it could apply generally across the language. >=20 > > 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, > > regardless of what file or "module" (namespace?) they are in; the lo= gic > > 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) {} > > } >=20 > In this case, I am not seeing what the nesting gets you. Making Desti= nation a normal class doesn't hurt anything here, does it? I wasn't intending to write a definitive example, just to illustrate it.= A better example might be a lazy cache, using hooks to notify the outer= class it has possibly been updated:=20 (note this is using the new syntax, but this syntax is NOT currently ref= lected 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 CacheIt= em($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() an= d saveChanges(); that's it. For CacheItem's, you only have the props for= the key or value, and updating the value marks the item as "dirty". 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 listener/obs= erver pattern is probably overkill here, unless you already have one lyi= ng around (i.e., using symfony/laravel or a related framework), but stil= l might require making some of the "internal" api public. Finally, itera= ting over all the items to find dirty ones is naive and inefficient. Hopefully this is a better illustration of what nesting provides? -- Rob --3381e71ef9ff4224b7ce6382e2135879 Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable
On Mon, Mar = 24, 2025 at 5:12=E2=80=AFPM Larry Garfield <larry@garfieldtech.com> wrote:
On Mon, Mar= 24, 2025, at 3:47 AM, Rob Landers wrote:

> On Sun, Mar 23, 2025, at 16:17, Larry Garfield wrote:

>> I've been following this thread with inte= rest, and at the moment I'm honestly undecided.  I certainly see th= e use cases for this functionality (whatever it gets named), but as a pr= actical matter it sounds like it introduces a lot of extra clunk and com= plexity.  And it seems like the use cases could be addressed as wel= l with either fileprivate or module-private.  (The former being con= siderably less work.)
>>
>> = So, how would nested classes compare to fileprivate, in terms of ability= to solve the problem space?  As I understand it, the goal is:
>>
>> 1. Classes that can be ins= tantiated only by the class that uses them.
>> 2. But can be returned from that class = to a caller and reused as appropriate.
>>
>> The autoloading question (loading a whole file for just= an implementation detail value object) is not one that carries much wei= ght for me, as that's a user-space question, not an engine question.&nbs= p; (Nothing in PHP itself says you cannot put 20 3 line classes or enums= together in one file.  It's just PSR-4 that says not go. Even comp= oser would allow it if configured properly)  So how would the less-= complicated alternative compare?
>>
>> --Larry Garfield
>
> He= y Larry,
>
> I think file-private = would/could be useful, but that only limits you to
> a "private" scope, which severely ha= mpers what you can do with it. If
> we went with "module-private" (rhetorical question: w= hat is a module?),
&= gt; but then you wouldn't be able to have "private" scope.

When I say module scope, I'm referring to something = along the lines that Arnaud and I were exploring a while back.  tld= r, "cluster of files with a common namespace root, which can get loaded = together."  It was mostly about performance, but did offer module-p= rivate as well, with some nice potential.  At the moment it's stall= ed out on "there's nasty hard edge cases and we're not sure if it's wort= h it" concerns.

=
Code exploration from Arnaud here: https://github.com/arnaud-lb/php-src/pull/= 10

Still well short of RFC state, o= f course, but provided for context.

My email has been broken for a few days, so sorry for the late res= ponse...

I suspect I know exactly what edge= s you were running into now... I'm now nearing completion of using `\` a= nd I did end up just making namespaces into logical structures (this is = mostly used for bookkeeping, but with ~3 lines of code -- minus grammar = changes -- you can support module privacy). The loading aspect (loading = together) would probably run into the same issue I ran into: class bindi= ng order. This is illustrated here: https://3v4l.org/g9vC4

With PSR-4 a= utoloading, we basically never run into this problem because as soon as = we compile C, we'll late-bind it to B, which triggers the autoloader, wh= ich eventually binds A, then B, then C, in the correct order. With neste= d classes (or a module), order matters. I know how to fix it, at least f= or nested/inner classes so they can be linked at compile time. I suspect= the solution would also apply to modules, but I don't think it could ap= ply generally across the language.


&= gt; With nested/inner classes, for example, you can put a protected clas= s
> on a class or= interface and access it only from those that use it,
> regardless of what file or "modul= e" (namespace?) they are in; the logic
> can be encapsulated to where it is used, not whe= re it is defined.
&g= t;
> interface Sh= ipment {
>  =  protected class Items {}
=
>   public readonly class Destination {}
>   function d= eliver(self:>Destination $destination);
> }
<= /div>
>
> = class InternationalShipment implements Shipment {
>   private function handle(S= hipment:>Items $items) {}
>
>&n= bsp;  public function deliver(Shipment:>Destination $destination= ) {}
> }

In this case, I am not seeing what the nesting = gets you.  Making Destination a normal class doesn't hurt anything = here, does it?

I wasn't intend= ing to write a definitive example, just to illustrate it. A better examp= le might be a lazy cache, using hooks to notify the outer class it has p= ossibly been updated:

(note this is using = the new syntax, but this syntax is NOT currently reflected in the RFC te= xt, yet)

namespace Caching;
<= br style=3D"max-width:100%;height:auto;">
class Cache {
    private= array $items =3D [];
    private array $dirty =3D [];

=
    public function getItem(string $key): Cach= eItem {
  =       return $this->items[$key] ?? ($this-&g= t;items[$key] =3D new CacheItem($this, $key, null));
    }

    public function saveChanges(): void {<= br style=3D"max-width:100%;height:auto;">
   &n= bsp;    foreach ($this->dirty as $key =3D> $value) = {
   =          echo "Saving $key to pe= rsistent storage\n";
        }
        $th= is->dirty =3D [];
    }

   = ; private function markDirty(string $key): void {
       = $this->dirty[$key] =3D $this->items[$key];
    }

    public class CacheItem {
      =   public string|null $value {
         &nb= sp;  get {
&nbs= p;           &nbs= p;   return $this->_value;
        &nbs= p;   }
&nb= sp;           set {
   &nbs= p;           $this->= ;_value =3D $value;
=             =    $this->cache->markDirty($this->key);
     &nb= sp;      }
        }

        public = function __construct(
            pri= vate readonly Cache $cache,
          &nbs= p; public private(set) string $key,
         &n= bsp;  private string|null $_value,
        ) {}
    }
}

$cache =3D new Cache();
$item1 =3D $cache->getItem('foo');
$item1->value =3D 'bar';

$cache->saveChanges();

This outputs:
<= br>
Saving foo to persistent storage

<= div>This provides for a simple API for outside the Cache class: getItem(= ) and saveChanges(); that's it. For CacheItem's, you only have the props= for the key or value, and updating the value marks the item as "dirty".=

Currently, if you want this same API, the = only way to do this is by:

1. using reflect= ion,
2. using a closure and binding it to the other class'= s scope,
3. providing a listener + observer (more formal v= ersion of [2]), or
3. iterating over items while saving to= find dirty items.

Those choices are not id= eal (IMHO).

Reflection feels "hacky" as doe= s using a bound closure. The listener/observer pattern is probably overk= ill here, unless you already have one lying around (i.e., using symfony/= laravel or a related framework), but still might require making some of = the "internal" api public. Finally, iterating over all the items to find= dirty ones is naive and inefficient.

Hopef= ully this is a better illustration of what nesting provides?
<= br>
-- Rob
--3381e71ef9ff4224b7ce6382e2135879--