On Mon, Mar 24, 2025 at 5:12 PM Larry Garfield <la...@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 interest, and at the moment I'm > >> honestly undecided. I certainly 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 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 ability > >> to solve the problem space? As I understand it, the goal is: > >> > >> 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 > >> appropriate. > >> > >> The autoloading 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 user-space question, not an engine question. (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 composer would allow it if configured > >> properly) So how would the less-complicated alternative compare? > >> > >> --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. > > 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 mostly 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. > > 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-src/pull/10 > > 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 privacy). The loading aspect (loading together) would probably run into the same issue I ran into: class binding order. This is illustrated here: https://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 autoloader, 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 time. I suspect the solution would also apply to modules, but I don't think it could apply generally across the language. > > > 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 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) {} > > } > > 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 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: (note this is using the new syntax, but this syntax is NOT currently reflected in the RFC text, yet) namespace Caching; class Cache { private array $items = []; private array $dirty = []; public function getItem(string $key): CacheItem { return $this->items[$key] ?? ($this->items[$key] = new CacheItem($this, $key, null)); } public function saveChanges(): void { foreach ($this->dirty as $key => $value) { echo "Saving $key to persistent storage\n"; } $this->dirty = []; } private function markDirty(string $key): void { $this->dirty[$key] = $this->items[$key]; } public class CacheItem { public string|null $value { get { return $this->_value; } set { $this->_value = $value; $this->cache->markDirty($this->key); } } public function __construct( private readonly Cache $cache, public private(set) string $key, private string|null $_value, ) {} } } $cache = new Cache(); $item1 = $cache->getItem('foo'); $item1->value = 'bar'; $cache->saveChanges(); This outputs: Saving foo to persistent storage 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 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/observer pattern is probably overkill 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. Hopefully this is a better illustration of what nesting provides? -- Rob