# RFC: short and inner classes

- Thread: https://externals.io/message/126589
- Original archive: https://news-web.php.net/php.internals/126589
- Started by Rob Landers on 2025-03-05T23:11:22+00:00
- Messages: 102
- RFC: Nested Classes (Declined) — https://externals.io/rfc/short-and-inner-classes.md
- Page 1 of 5: messages 1 to 25 (25 per page, add `?page=N` to the URL)
- Next page: https://externals.io/message/126589.md?page=2
- Reply by email to internals@lists.php.net
- More for agents: search with https://externals.io/search.md?q={query} · RFCs at https://externals.io/rfc.md · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

---

## #126589 — Rob Landers — 2025-03-05T23:11:22+00:00

URL: https://externals.io/message/126589#126589

Hello PHP Internals,

I'd like to introduce my RFC for discussion: https://wiki.php.net/rfc/short-and-inner-classes

This RFC defines a short class syntax as well as the ability to nest classes inside another class. This introduces an unprecedented amount of control, flexibility, and expressiveness over how objects are used and instantiated in PHP. There is a PR (https://github.com/php/php-src/pull/17895) that implements this functionality -- all test failures are related to different/new/incorrect error messages being generated. However, the core functionality exists to take for a test ride.

So, what do I mean by "unprecedented amount of control"? With this change, you can declare an inner class as private or protected, preventing its usage outside of the outer class:

class User {
  private class Id {}

  public function __construct(public self::Id $id) {}
}

In the above example, the class `User` is impossible to construct even though it has a public constructor (except through reflection) because User::Id is private; User::Id cannot be instantiated, used as a type hint, or even via `instanceof` outside of the User class itself. This example isn't practical but demonstrates something that is nearly impossible in previous versions of PHP, where all classes are essentially publicly accessible from anywhere within the codebase.

As a number of inner classes will probably be used as DTOs, the RFC introduces a "short syntax" for declaring classes, which enhances expressiveness, even allowing the usage of traits, all in a single line:

// declare a readonly Point, that implements Vector2 and uses the Evolvable trait
readonly class Point(public int $x, public int $y) implements Vector2 use Evolvable;

When combined with inner classes, it looks something like this:

class Pixel {
  public readonly class Point(public int $x, public int $y) implements Vector2 use Evolvable;
}

// Create a new pixel point with property $x and $y set to 0
$p = new Pixel::Point(0, 0);

There are far more details in the RFC itself, so please check it out. I'm quite excited to hear your thoughts!

— Rob

PS. I know I tend to rush into things, but I want to make it clear that I'm not rushing this -- I've learned from my mistakes (thank you to those who have given me advice). I'm going to do this right.

---

## #126591 — Juris Evertovskis — 2025-03-06T01:08:22+00:00

URL: https://externals.io/message/126589#126591 · In reply to: #126589

Hi,

 

Maybe I didn’t read the RFC carefully enough, but… Do any of these features require the other one?

 

I’m asking because I wouldn’t want to see them both denied just because the voters disagree with one of them.

 

Good luck with this proposal!

 

Juris

---

## #126593 — Rob Landers — 2025-03-06T06:23:21+00:00

URL: https://externals.io/message/126589#126593 · In reply to: #126591

> Hi,
>  
> Maybe I didn’t read the RFC carefully enough, but… Do any of these features require the other one?
>  
> I’m asking because I wouldn’t want to see them both denied just because the voters disagree with one of them.
>  
> Good luck with this proposal!
>  
> Juris

Hi Juris,

Yes and no. Without the short syntax, classes explode in size if you just want to have simple inner classes for DTO/organization. For example, I used this for quite a bit over the last couple of weeks. One big usage, from my experiments, was to replace array returns:

class StringList(public array $strings);

And it makes it “easy on the eyes” to just have a list of classes at the top of the class. By itself, a short syntax makes some sense, but isn’t compelling (IMHO), and inner classes cause an explosion in LoC without it.

So, technically, they aren’t required to be in the same RFC; but also, they complement each other very well.

If the RFC fails due to them being together, I’ll take the feedback and go back to the drawing board.

— Rob

---

## #126595 — tim@bastelstu.be — 2025-03-06T08:04:55+00:00

URL: https://externals.io/message/126589#126595 · In reply to: #126593

Hi

Am 2025-03-06 07:23, schrieb Rob Landers:
> So, technically, they aren’t required to be in the same RFC; but also, 
> they complement each other very well.

They really should be separate RFCs then. Your RFC text acknowledges 
that in the very first sentence: “two significant enhancements to the 
language”. Each individual proposal likely has sufficient bike-shedding 
potential on its own and discussion will likely get messy, because one 
needs to closely follow which of the two proposals an argument relates 
to.

---------

I've also given your RFC a first pass, without yet trying to understand 
all the implications. Here's some comments.

As for the “Short classes” proposal:

- I don't understand the use of `private` properties. Given that the 
classes cannot have methods, they would be inaccessible, no?

As for the “Inner classes” proposal:

- “abstract is not allowed as an inner class cannot be parent classes.” 
- Why?
- “type hint” - PHP does not have type hints, types are enforced. You 
mean “Type declaration”.
- “this allows you to redefine an inner class in a subclass, allowing 
rich hierarchies” - The RFC does not specify if and how this interacts 
with the LSP checks.

Best regards
Tim Düsterhus

---

## #126597 — michal.brzuchalski@gmail.com — 2025-03-06T09:03:24+00:00

URL: https://externals.io/message/126589#126597 · In reply to: #126589

Hi Rob,

czw., 6 mar 2025 o 00:16 Rob Landers <rob@bottled.codes> napisał(a):

> Hello PHP Internals,
>
> I'd like to introduce my RFC for discussion:
> https://wiki.php.net/rfc/short-and-inner-classes
>
> This RFC defines a short class syntax as well as the ability to nest
> classes inside another class. This introduces an unprecedented amount of
> control, flexibility, and expressiveness over how objects are used and
> instantiated in PHP. There is a PR (
> https://github.com/php/php-src/pull/17895) that implements this
> functionality -- all test failures are related to different/new/incorrect
> error messages being generated. However, the core functionality exists to
> take for a test ride.
>
> So, what do I mean by "unprecedented amount of control"? With this change,
> you can declare an inner class as private or protected, preventing its
> usage outside of the outer class:
>
> class User {
>   private class Id {}
>
>   public function __construct(public self::Id $id) {}
> }
>
> In the above example, the class `User` is impossible to construct even
> though it has a public constructor (except through reflection) because
> User::Id is private; User::Id cannot be instantiated, used as a type hint,
> or even via `instanceof` outside of the User class itself. This example
> isn't practical but demonstrates something that is nearly impossible in
> previous versions of PHP, where all classes are essentially publicly
> accessible from anywhere within the codebase.
>
> As a number of inner classes will probably be used as DTOs, the RFC
> introduces a "short syntax" for declaring classes, which enhances
> expressiveness, even allowing the usage of traits, all in a single line:
>
> // declare a readonly Point, that implements Vector2 and uses the
> Evolvable trait
> readonly class Point(public int $x, public int $y) implements Vector2 use
> Evolvable;
>
> When combined with inner classes, it looks something like this:
>
> class Pixel {
>   public readonly class Point(public int $x, public int $y) implements
> Vector2 use Evolvable;
> }
>
> // Create a new pixel point with property $x and $y set to 0
> $p = new Pixel::Point(0, 0);
>
> There are far more details in the RFC itself, so please check it out. I'm
> quite excited to hear your thoughts!
>
> — Rob
>
> PS. I know I tend to rush into things, but I want to make it clear that
> I'm not rushing this -- I've learned from my mistakes (thank you to those
> who have given me advice). I'm going to do this right.
>

Inner classes - YES,
Short classes - YES,
Short empty classes - YES - very nice solution for extending Exception
class, maybe there is no need for parentheses??,
Traits in single line - dunno, personally don't see the need so no much
preference here, IMO would be debatable.

Especially both together. Inner classes are something I was thinking about
many years ago [1].
And the short classes are something that is also in my field of interest. I
remember this was proposed some time ago on ML.

Currently, all of the classes replace array-shapes I mark with additional
docblock including `@internal` tag.
Many DTO's have no logic or not that much, meaning they don't need methods.

An example which you may think could be expressed differently but since I'm
used to expressing my opinion as fast as possible this is what came to my
mind now. Possibly not all of the inner classes should be inner, but there
is one that definitely should be, see:

final class TZParser
{
    /**
     * @param TZTypeInfo[] $types
     * @param TTInfo[] $transitions
     * @param string[] $abbreviations
     * @param array<int,array{timestamp:int,corr:int}> $leapSecondData
     */
  public class TZInfo(
    string $version,
    bool $isV2Plus,
    array $types,
    array $transitions,
    array $abbreviations,
    array $leapSecondData,
    string|null $posixString,
  );

  protected class TZTypeInfo(public int $gmtOffset, bool $isDst, int
$abbreviationIndex, bool $isStd = false, bool $isUt = false)

  protected class TTInfo(int $timestamp, int $typeIndex);

  private class TZInfoSection(array $types, array $transitions, array
$abbreviations, array $leaps, int $newOffset);

  public static function parse(string $filename): TZInfo
  { /** impl */ }
}

While it looks different with short class and doc block for TZInfo, for the
others that don't require docblock this is pretty nice, even if the fields
would be declared each in a separate line - it's a personal preference.
Additionally, I could easily remove the some prefixes

I see this way of declaring DTO's more convenient than having 3 separate
files for just a small DTO class where I need to put `/** @internal */` on
each of them. Inner classes won't be possible to instantiate outside of the
TZParser class which is intended here. All instances should be available
outside of the parser to read the information with one exception for
TZInfoSection.

In this example the TZInfoSection class is used internally (which is why
marked as private) by the parser itself, it's not exposed outside because
there is no real use case for that which makes this feature very
interesting to me.

If the RFC would be splitted into smaller or not I say YES for
inner+short(and empty).
I hope others will see proposed features as useful as I. Good luck.

[1] https://brzuchal.com/posts/inner-classes-in-php-concept/

---

## #126603 — Juris Evertovskis — 2025-03-06T12:49:49+00:00

URL: https://externals.io/message/126589#126603 · In reply to: #126595

> - I don't understand the use of `private` properties. Given that the
> classes cannot have methods, they would be inaccessible, no?

I think the RFC was a bit unclear on this. Short classes can have 
methods. The short syntax just doesn't provide the ability to define 
them whilst defining the class.

But otherwise they are indistinguishable from normal classes and they 
can have methods by inheriting them or by using traits.

BR,
Juris

---

## #126604 — Niels Dossche — 2025-03-06T19:08:03+00:00

URL: https://externals.io/message/126589#126604 · In reply to: #126589

Hi Rob

Without looking too deep (yet) into the details, I'm generally in favor of the idea.
What I'm less in favor of is the implementation choice to expose the inner class as a property/const and using a fetch mode to grab it.
That feels quite weird to me honestly. How did you arrive at this choice?

Kind regards
Niels

---

## #126606 — Larry Garfield — 2025-03-06T19:33:56+00:00

URL: https://externals.io/message/126589#126606 · In reply to: #126589

> Hello PHP Internals,
>
> I'd like to introduce my RFC for discussion: 
> https://wiki.php.net/rfc/short-and-inner-classes

I agree with others who have said this should be two RFCs.  They stand alone, but can complement each other well.  That's fine, talk about how that works, but they're separate RFCs.  (Like we split hooks and aviz.)

I'm broadly in favor of short classes and warm on inner classes, with assorted caveats as below.

## Short classes

The no-method-definitions restriction seems odd to me.  I don't really see what value that provides.  The main advantages are

1. Even shorter way to define promoted properties for the common case.
2. No need to define a body if you don't need one.

Neither of those conflicts with defining methods.

If the concern is the verbosity of method definitions, well, I tried:

https://wiki.php.net/rfc/short-functions

I still think that would be beneficial, but am frying other fish at the moment.

So really, all it need to do is allow inline definition of the properties by the class name as an alternative to a constructor.

class Point(int $x, int $y) {}

class Rect(Point $tl, Point $br) {
  public method area() {
     /* ... */ 
  }
}

(Also allowing to skip the empty {} is fine with me, but that the last of my concerns.)

The other question is trait usage.  The RFC proposes just shifting that up to the declaration line.  That also seems fine to do either way, regardless of whether there are methods.

My biggest concern with this is that it makes methods and short-classes mutually incompatible.  So if you have a class that uses short-syntax, and as it evolves you realize it needs one method, sucks to be you, now you have to rewrite basically the whole class to a long-form constructor.  That sucks even more than rewriting a short-lambda arrow function to a long-form closure, except without the justification of capture semantics.

Additionally: The RFC doesn't specify if or how properties with hooks are supported.  That should be defined so we can argue about it. :-)

Additionally: What happens here:

class Point(int $x, int $y);

class 3DPoint(int $z) extends Point;

I have to redeclare all of the parameters from the parent, just to add one?  That's ugly. 

## Inner classes

I'm on board with the use case.  What I'm not sure on is inner classes vs file-private visibility, something that Ilija was working on at one point and Michał Brzuchalski suggested in his post.  Both solve largely the same problem with different spelling.

Arguably, inner classes have fewer issues with current autoload conventions.  I must ponder this further.

> However, no classes may not inherit from inner classes, but inner classes may inherit from other classes, including the outer class. 

I think you have one too many negatives in that sentence.

--Larry Garfield

---

## #126607 — tim@bastelstu.be — 2025-03-06T21:00:42+00:00

URL: https://externals.io/message/126589#126607 · In reply to: #126604

Hi


> What I'm less in favor of is the implementation choice to expose the inner class as a property/const and using a fetch mode to grab it.
> That feels quite weird to me honestly. How did you arrive at this choice?

Somewhat relatedly, the RFC does not mention how the choice of `::` as 
the separator interacts with the following features (i.e. what will the 
result of each of the statements be):

     Closure::fromCallable('Outer::Inner::method');
     new ReflectionMethod('Outer::Inner::method');
     defined('Outer::Inner');
     constant('Outer::Inner');
     $inner = 'Inner';
     Outer::{$inner};

… and any other meta-programming functionality working on class 
constants or static methods.

Also, what will happen for:

     class P {
         class Inner { }
     }

     class C extends P {
          const Inner = 'x';
     }

(and vice versa)

Best regards
Tim Düsterhus

---

## #126608 — Rob Landers — 2025-03-06T21:38:51+00:00

URL: https://externals.io/message/126589#126608 · In reply to: #126595

> Hi
> 
> Am 2025-03-06 07:23, schrieb Rob Landers:
> > […]
> 
> They really should be separate RFCs then. Your RFC text acknowledges 
> that in the very first sentence: “two significant enhancements to the 
> language”. Each individual proposal likely has sufficient bike-shedding 
> potential on its own and discussion will likely get messy, because one 
> needs to closely follow which of the two proposals an argument relates 
> to.

I put a lot of thought into this issue off and on, all day. I've decided to remove short syntax from the RFC and focus on inner classes. If this passes, then I will propose it as a separate RFC. Introducing them concurrently makes little sense in light of the feedback I have gotten so far, and it is turning out that there is much more to discuss than I initially expected.

Thus, I will skip replying about short classes.

> 
> As for the “Inner classes” proposal:
> 
> - “abstract is not allowed as an inner class cannot be parent classes.” 
> - Why?

This is mostly a technical reason, as I was unable to determine a grammar rule that didn't result in ambiguity. Another reason is to ensure encapsulation and prevent usages outside their intended scope. We can always add it later.

> - “type hint” - PHP does not have type hints, types are enforced. You 
> mean “Type declaration”.

Thank you for pointing this out! I learned something new today! I've updated the RFC.

> - “this allows you to redefine an inner class in a subclass, allowing 
> rich hierarchies” - The RFC does not specify if and how this interacts 
> with the LSP checks.

It doesn't affect LSP. I've updated the RFC accordingly.


> Hi Rob
> 
> Without looking too deep (yet) into the details, I'm generally in favor of the idea.
> What I'm less in favor of is the implementation choice to expose the inner class as a property/const and using a fetch mode to grab it.
> That feels quite weird to me honestly. How did you arrive at this choice?
> 
> Kind regards
> Niels

It's a slightly interesting story about how I arrived at this particular implementation. If you noticed the branch name, this is the second implementation. The first implementation used a dedicated list on the class-entry for inner classes. Since I wanted to prevent static property/consts from being declared with the same name, I had just set it to a string of the full class name as a placeholder. That implementation also required some pretty dramatic OPcache changes, which I didn't like. At one point, I went to add the first test that did `new Outer::Inner()` and the test passed... 

You can imagine my surprise to see a test pass that I had expected to fail, and it was then that I went into the details of what was going on. Any `new ClassName` essentially results in the following AST:

ZEND_AST_NEW
-- ZEND_AST_ZVAL
    -- "ClassName"
-- (... args)

The original grammar, at the time, was to reuse the existing static property access AST until I could properly understand OPcache/JIT. My change had resulted in (approximately) this AST:

ZEND_AST_NEW
-- ZEND_AST_ZVAL
    -- ZEND_AST_STATIC_PROP
        -- "Outer::Inner"
-- (... args)

Which, effectively resulted in emitting opcodes that found the prop + string value I happened to put there as a placeholder until I figured out a better solution, handling autoloading properly and everything. This pretty much negated all efforts up to that point, and I was stunned.

So, I branched off from an earlier point and eventually wrote the version you see today. It's 1000x simpler and faster than the original implementation (literally), since it uses all pre-existing (optimized)) infrastructure instead of creating entirely new infrastructure. It doesn't have to check another hashmap (which is slow) for static props vs. constants vs. inner classes. 

In essence, while the diff can be improved further, it is quite simple; the core of it is less than 500 lines of code.

I'd recommend leaving any comments about the PR on the PR itself (or via private email if you'd prefer that). I'm by no means an expert on this code base, and if it is not what you'd expect, being an expert yourself, I'd love to hear any suggestions for improvements or other approaches.


> My biggest concern with this is that it makes methods and short-classes mutually incompatible.  So if you have a class that uses short-syntax, and as it evolves you realize it needs one method, sucks to be you, now you have to rewrite basically the whole class to a long-form constructor.  That sucks even more than rewriting a short-lambda arrow function to a long-form closure, except without the justification of capture semantics.

I literally fell out of my chair laughing. Thanks for that, and it is true. I look forward to discussing this further!

> ## Inner classes
> 
> I'm on board with the use case.  What I'm not sure on is inner classes vs file-private visibility, something that Ilija was working on at one point and Michał Brzuchalski suggested in his post.  Both solve largely the same problem with different spelling.
> 
> Arguably, inner classes have fewer issues with current autoload conventions.  I must ponder this further.

Indeed! See: https://externals.io/message/126331#126337

> 
> > […]
> 
> I think you have one too many negatives in that sentence.

Thank you, this is fixed!

— Rob

---

## #126609 — Rob Landers — 2025-03-06T22:05:57+00:00

URL: https://externals.io/message/126589#126609 · In reply to: #126607

> Hi
> 
> 
> > […]
> 
> Somewhat relatedly, the RFC does not mention how the choice of `::` as 
> the separator interacts with the following features (i.e. what will the 
> result of each of the statements be):

Sorry to double post, but before updating the RFC, I figure I'll go ahead and answer here and see if it is what you expect.

> 
>      Closure::fromCallable('Outer::Inner::method');

You end up with:

object(Closure)#1 (1) {
  ["function"]=>
  string(20) "Outer::Inner::method"
}

>      new ReflectionMethod('Outer::Inner::method');

The current implementation returns an error here, but it should give you an actual ReflectionMethod. I'll have to take a look and see what is going on.

>      defined('Outer::Inner');

This returns: `true`.

>      constant('Outer::Inner');

This returns:

string(12) "Outer::Inner"

>      $inner = 'Inner';
>      Outer::{$inner};

This does nothing (but resolves to "Outer::Inner")

> … and any other meta-programming functionality working on class 
> constants or static methods.
> 
> Also, what will happen for:
> 
>      class P {
>          class Inner { }
>      }
> 
>      class C extends P {
>           const Inner = 'x';
>      }
> 
> (and vice versa)

This is a really good one. If for no other reason than I did a really poor job of explaining resolution(?) in the RFC. `P::Inner` belongs to `P`, not to `C`, so you can do `new C::Inner()` and it will resolve to `P::Inner()`:

object(P::Inner)#2 (0) {
}

As with other static things in PHP, you can do some really strange things like this. This is similar to how you can redefine static constants in subclasses.

My main goal is to prevent exactly this type of confusion:

new C::Inner() 
vs. 
echo C::Inner.

> Somewhat relatedly, the RFC does not mention how the choice of `::` as 
> the separator

This felt the most natural to me, but as with all syntax on this list, I'd be interested in other colors to paint the bikeshed! Especially ones that I may not have previously considered!

— Rob

---

## #126610 — Ilija Tovilo — 2025-03-06T22:20:37+00:00

URL: https://externals.io/message/126589#126610 · In reply to: #126589

Hi Rob


>
> I'd like to introduce my RFC for discussion: https://wiki.php.net/rfc/short-and-inner-classes

Thank you for your proposal.

I'm very much against the idea of introducing yet another slightly
shorter form to declare a class. In your examples (they have been
removed in the meantime), it's unclear how the syntax interacts with
inherited constructors, trait-used constructors, whether repetition of
readonly parent properties leads to a "Cannot modify readonly
property" error, etc.

The concept of visibility for classes does seem useful to me. Some
questions that crossed my mind when reading the proposal:

> Inner classes may only be nested one level deep, may not be a parent class, and may not be declared abstract

These restrictions seem somewhat arbitrary. For example, you may want
a private class to extend another private class, creating some local
class hierarchy. I think there's value in relaxing this restriction,
if technically possible.

> PHP Fatal error:  Private inner class Box::Point cannot be used in the global scope

How is this implemented? I presume using a public nested class as type
hints should be allowed, but these classes may not be loaded when the
function is declared. We implement delayed variance checks for
methods, which do trigger the autoloader, but functions do not (since
they cannot violate variance rules).

> Visibility Rules: Private and protected inner classes are only instantiable within their outer class (or subclasses for protected) and cannot be used as type declarations outside their outer class. This encapsulation ensures that the inner class’s implementation details remain within their intended scope.

This introduces a weird case where methods with parameter or return
types referring to private classes may not be redeclared in their
subclasses, given that the type cannot be specified, even if the
methods themselves are not private or final. You do mention something
very similar in your e-mail with the __constructor case, but I really
fail to see how this provides any benefit.

I would also like to echo what has been said about the :: operator,
which feels out of place. I understand that \ comes with additional
autoloading challenges, namely requiring a fallback autoloading
strategy that currently does not conform to PSR-4.

Disclaimer: I have not looked at the implementation at all yet.

Ilija

---

## #126612 — tim@bastelstu.be — 2025-03-06T22:31:35+00:00

URL: https://externals.io/message/126589#126612 · In reply to: #126609

Hi


> > […]
> 
> You end up with:
> 
> object(Closure)#1 (1) {
>    ["function"]=>
>    string(20) "Outer::Inner::method"
> }

Okay, does calling the closure work and correctly call the `method` on 
the inner class? The question was intended to make sure that the 
implementation for callables uses the correct `::` to split. Here's 
another one that might be interesting:

     Closure::fromCallable(["Outer::Inner", "method"]);
     Closure::fromCallable(["Outer", "Inner::method"]);

> > […]
> 
> This returns:
> 
> string(12) "Outer::Inner"

Okay, so this behaves as a constant containing the class name. I assume 
it's with the full namespace if the outer class is namespaced? I'm not 
sure if I want this to work like this (i.e. whether this should be an 
error).

> > […]
> 
> This does nothing (but resolves to "Outer::Inner")

It's consistent with `constant()` and that's good.

> > […]
> 
> This is a really good one. If for no other reason than I did a really poor job of explaining resolution(?) in the RFC. `P::Inner` belongs to `P`, not to `C`, so you can do `new C::Inner()` and it will resolve to `P::Inner()`:

I don't think the RFC explains “resolution” at all. That's why I'm 
asking with those specific “edge-casey” examples, so that the RFC 
explicitly spells out the behavior. This is not something that should be 
“implementation defined”, but something where an explicit design 
decision has been made.

I also don't understand why `new C::Inner()` (w|sh)ould resolve to 
`P::Inner()`. I think my expectation of the code snippet above would be 
that it is an error.

Likewise, LSP being ignored for inner classes raises an interesting 
question about the behavior of:

     class P {
         class Inner {
             public function __construct(public string $foo) { }
         }

         public static function create() {
             return new static::Inner('x');
         }
     }

     class C extends P {
         class Inner {
             public function __construct(public int $bar) { }
         }
     }

What happens if I call `C::create()`? This should also be specified in 
the RFC (and tested with a .phpt test).

> As with other static things in PHP, you can do some really strange things like this. This is similar to how you can redefine static constants in subclasses.

We should remove the number of strange things, not add to them.

Best regards
Tim Düsterhus

---

## #126613 — tim@bastelstu.be — 2025-03-06T22:33:14+00:00

URL: https://externals.io/message/126589#126613 · In reply to: #126608

Hi


> I put a lot of thought into this issue off and on, all day. I've decided to remove short syntax from the RFC and focus on inner classes.

Good choice.

Don't forget to update the title of the RFC in the Overview page: 
https://wiki.php.net/rfc

Best regards
Tim Düsterhus

---

## #126614 — Rob Landers — 2025-03-06T23:14:22+00:00

URL: https://externals.io/message/126589#126614 · In reply to: #126612

> Hi
> 
> 
> > […]
> 
> Okay, does calling the closure work and correctly call the `method` on 
> the inner class? 

Yep, it works!

> The question was intended to make sure that the 
> implementation for callables uses the correct `::` to split. 

This is largely why nesting can only be one level deep. Multiple levels end up with far more intrusive changes to the engine and IMHO, severely impact readability. I'll update the RFC on this.

> Here's 
> another one that might be interesting:
> 
>      Closure::fromCallable(["Outer::Inner", "method"]);
>      Closure::fromCallable(["Outer", "Inner::method"]);

object(Closure)#1 (1) {
["function"]=>
string(20) "Outer::Inner::method"
}

and 

Uncaught TypeError: Failed to create closure from callable: class "Inner" not found

I believe this is correct? I haven't seen this form since my 5.3 days, so I am not sure if it is correct or a bug.

> 
> > […]
> 
> Okay, so this behaves as a constant containing the class name. I assume 
> it's with the full namespace if the outer class is namespaced? I'm not 
> sure if I want this to work like this (i.e. whether this should be an 
> error).

That's correct, it contains the fully qualified name. I may have to think on this, but I concur that this might be better as an error.

> 
> > […]
> 
> It's consistent with `constant()` and that's good.
> 
> > […]
> 
> I don't think the RFC explains “resolution” at all. That's why I'm 
> asking with those specific “edge-casey” examples, so that the RFC 
> explicitly spells out the behavior. This is not something that should be 
> “implementation defined”, but something where an explicit design 
> decision has been made.

100% agree. I'll update the RFC after thinking through some points in your email.

> 
> I also don't understand why `new C::Inner()` (w|sh)ould resolve to 
> `P::Inner()`. I think my expectation of the code snippet above would be 
> that it is an error.

100% agree. It actually should be an error, according to the RFC. To be honest, until I wrote my response to you, I forgot you could redefine constants. So, I'll have to take a look at this.

> 
> Likewise, LSP being ignored for inner classes raises an interesting 
> question about the behavior of:
> 
>      class P {
>          class Inner {
>              public function __construct(public string $foo) { }
>          }
> 
>          public static function create() {
>              return new static::Inner('x');
>          }
>      }
> 
>      class C extends P {
>          class Inner {
>              public function __construct(public int $bar) { }
>          }
>      }
> 
> What happens if I call `C::create()`? This should also be specified in 
> the RFC (and tested with a .phpt test).

I'll add a test for it (and detail in the RFC), but it will instantiate a C::Inner. This doesn't violate LSP, though, because C::Inner is distinct from P::Inner -- they are separate classes and completely unrelated. This is just like using static properties or constants.

To explain further, static::Inner is about the same as doing `new $class::Inner;`-ish. You are not referencing the same class depending on where you are calling the function from. The fact that `static` is a shorthand for this is what makes it weird.

I wouldn't be opposed to making `new static::Inner` (or even `new self::Inner`) an error, since it is confusing; it would force people to spell out the class name. However, I think it is useful when the different inner classes implement the same interfaces (explicitly or implicitly). I'd be interested to hear thoughts on this, but I'm now wondering if static:: allows casual violations of LSP, in general. 🤔

> 
> > […]
> 
> We should remove the number of strange things, not add to them.

Truth. 

Thank you for these questions Tim!

— Rob

---

## #126615 — Rob Landers — 2025-03-06T23:48:06+00:00

URL: https://externals.io/message/126589#126615 · In reply to: #126610

> Hi Rob
> 
> 
> > […]
> 
> Thank you for your proposal.
> 
> I'm very much against the idea of introducing yet another slightly
> shorter form to declare a class. In your examples (they have been
> removed in the meantime), it's unclear how the syntax interacts with
> inherited constructors, trait-used constructors, whether repetition of
> readonly parent properties leads to a "Cannot modify readonly
> property" error, etc.
> 
> The concept of visibility for classes does seem useful to me. Some
> questions that crossed my mind when reading the proposal:
> 
> > […]
> 
> These restrictions seem somewhat arbitrary. For example, you may want
> a private class to extend another private class, creating some local
> class hierarchy. I think there's value in relaxing this restriction,
> if technically possible.

They're not 100% arbitrary, but mostly due to technical limitations.

- One level deep: Nesting multiple levels results in ambiguous grammar.
- As a parent class: This also results in ambiguity.
- Abstract: If it cannot be a parent class, it doesn't make sense for it to be abstract.

> 
> > […]
> 
> How is this implemented? I presume using a public nested class as type
> hints should be allowed, but these classes may not be loaded when the
> function is declared. We implement delayed variance checks for
> methods, which do trigger the autoloader, but functions do not (since
> they cannot violate variance rules).

This happens at run time, when the function is called. I believe we can guarantee that it will pass/fail with one of the following cases:

- The type check passes and everything is fine
- The type check fails (due to not being the correct type); autoloading never happens
- The type check succeeds, but it is private/protected; to have an object of that type, you'd have to have loaded the outer class.

This is no different than having:

function foo(MadeUpClass $c) {}

and then never calling foo.

> 
> > […]
> 
> This introduces a weird case where methods with parameter or return
> types referring to private classes may not be redeclared in their
> subclasses, given that the type cannot be specified, even if the
> methods themselves are not private or final. You do mention something
> very similar in your e-mail with the __constructor case, but I really
> fail to see how this provides any benefit.

Yes. This is correct and inline with other languages with inner types. The idea behind it is to encapsulate behavior and hide information. The idea is that external code should not know anything about the internal structure. This grants control over inheritance, which you pointed out, though it is better to use final to do that. However, it could be useful if you wanted to create a class meant to be inherited (such as a url parser) but prevent modification to important methods via inheritance.

> 
> I would also like to echo what has been said about the :: operator,
> which feels out of place. I understand that \ comes with additional
> autoloading challenges, namely requiring a fallback autoloading
> strategy that currently does not conform to PSR-4.

It felt natural to me at the time, but I suspect there may be a better nomenclature; we just need to discover it.

> 
> Disclaimer: I have not looked at the implementation at all yet.
> 
> Ilija
> 

— Rob

---

## #126621 — Juris Evertovskis — 2025-03-07T16:29:07+00:00

URL: https://externals.io/message/126589#126621 · In reply to: #126615

> They're not 100% arbitrary, but mostly due to technical limitations.
> 
> - One level deep: Nesting multiple levels results in ambiguous grammar.
> - As a parent class: This also results in ambiguity.
> - Abstract: If it cannot be a parent class, it doesn't make sense for 
> it to be abstract.

Hey,

I thought that an abstract inner class would be one that a subclass (of 
the outer class) must implement, not one that must be extended. Similar 
to abstract methods.

But have you considered enums, traits and interfaces? I assume inner 
classes would work as expected if defined on traits or enums, but what 
is going to happen if I try to define one on an interface?

BR,
Juris

---

## #126726 — Rob Landers — 2025-03-12T10:10:48+00:00

URL: https://externals.io/message/126589#126726 · In reply to: #126589

> Hello PHP Internals,
> 
> I'd like to introduce my RFC for discussion: https://wiki.php.net/rfc/short-and-inner-classes
> 
> This RFC defines a short class syntax as well as the ability to nest classes inside another class. This introduces an unprecedented amount of control, flexibility, and expressiveness over how objects are used and instantiated in PHP. There is a PR (https://github.com/php/php-src/pull/17895) that implements this functionality -- all test failures are related to different/new/incorrect error messages being generated. However, the core functionality exists to take for a test ride.
> 
> So, what do I mean by "unprecedented amount of control"? With this change, you can declare an inner class as private or protected, preventing its usage outside of the outer class:
> 
> class User {
>   private class Id {}
> 
>   public function __construct(public self::Id $id) {}
> }
> 
> In the above example, the class `User` is impossible to construct even though it has a public constructor (except through reflection) because User::Id is private; User::Id cannot be instantiated, used as a type hint, or even via `instanceof` outside of the User class itself. This example isn't practical but demonstrates something that is nearly impossible in previous versions of PHP, where all classes are essentially publicly accessible from anywhere within the codebase.
> 
> As a number of inner classes will probably be used as DTOs, the RFC introduces a "short syntax" for declaring classes, which enhances expressiveness, even allowing the usage of traits, all in a single line:
> 
> // declare a readonly Point, that implements Vector2 and uses the Evolvable trait
> readonly class Point(public int $x, public int $y) implements Vector2 use Evolvable;
> 
> When combined with inner classes, it looks something like this:
> 
> class Pixel {
>   public readonly class Point(public int $x, public int $y) implements Vector2 use Evolvable;
> }
> 
> // Create a new pixel point with property $x and $y set to 0
> $p = new Pixel::Point(0, 0);
> 
> There are far more details in the RFC itself, so please check it out. I'm quite excited to hear your thoughts!
> 
> — Rob
> 
> PS. I know I tend to rush into things, but I want to make it clear that I'm not rushing this -- I've learned from my mistakes (thank you to those who have given me advice). I'm going to do this right.
> 
> 

Hello internals,

I've made some major updates to the text of the RFC to clarify behaviors and revisited the implementation (which is still under development, though I hope to have a draft by the end of this weekend). 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 prevent accidental violations of LSP.

Otherwise, there are not any big changes, but a lot of time was spent clarifying behavior and expanding on the reasoning for those decisions in the RFC itself.

— Rob

---

## #126729 — tim@bastelstu.be — 2025-03-13T11:01:42+00:00

URL: https://externals.io/message/126589#126729 · In reply to: #126726

Hi

Am 2025-03-12 11:10, schrieb Rob Landers:
> - Accessing inner classes is done via a new token: ":>" instead of 
> "::".

I don't particularly like that. It is “invented syntax” and I don't 
think that inner classes are sufficiently valuable to dedicate an entire 
operator to them that could serve a more useful purpose in the future. 
It also got 4 negative points in the rating back when the namespace 
separator was decided: https://wiki.php.net/rfc/namespaceseparator

Would `\\` (i.e. two backslashes) work? The outer class for inner 
classes effectively act as a namespace, so it makes sense to me to use 
syntax that is similar to namespaces.

I'll look into the rest when there is a new implementation, since I 
assume some details will still be clarified and fixed as part of 
implementing the proposal.

Best regards
Tim Düsterhus

---

## #126730 — Ilija Tovilo — 2025-03-13T11:41:50+00:00

URL: https://externals.io/message/126589#126730 · In reply to: #126729

Hi Rob


>
> Am 2025-03-12 11:10, schrieb Rob Landers:
> > […]
>
> I don't particularly like that. It is “invented syntax” and I don't
> think that inner classes are sufficiently valuable to dedicate an entire
> operator to them

I would like to echo these thoughts. I also think the RFC is kind of
missing the mark. Arguably the most common use-case for `@internal` is
to restrict a class to a specific package/module or namespace,
regardless of the user's class hierarchy. I believe you specifically
call this a non-goal:

> Currently, many libraries implement “internal” classes by using a naming convention or an @internal annotation in the docblock. Inner classes enable libraries to define an internal class that cannot be used outside the class it is defined inside. This feature is not meant to be used as a “module” system, but rather as a way to encapsulate logic internal to a class, such as DTOs or helper classes.

At least if I understand this statement correctly.

In that case, I see much less value in nested classes. They might
still be ok if they are extremely simple, but the proposal is
currently quite complex. It has a custom operator, it deals with
shadowing, LSP, runtime resolution and more, in addition to visibility
which is the actual goal. Maybe more use-cases are enabled, but I
don't think they are currently well described. Assuming visibility can
partially be implemented in a simpler way (e.g. top-level,
file-private classes), the costs seem to outweigh the benefits in my
opinion. Hence I would not be in favor of the RFC in this or a similar
form.

Ilija

---

## #126731 — Rob Landers — 2025-03-13T12:56:30+00:00

URL: https://externals.io/message/126589#126731 · In reply to: #126730

> Hi Rob
> 
> 
> > […]
> 
> I would like to echo these thoughts. I also think the RFC is kind of
> missing the mark. Arguably the most common use-case for `@internal` is
> to restrict a class to a specific package/module or namespace,
> regardless of the user's class hierarchy. I believe you specifically
> call this a non-goal:
> 
> > […]
> 
> At least if I understand this statement correctly.
> 
> In that case, I see much less value in nested classes. They might
> still be ok if they are extremely simple, but the proposal is
> currently quite complex. It has a custom operator, it deals with
> shadowing, LSP, runtime resolution and more, in addition to visibility
> which is the actual goal. Maybe more use-cases are enabled, but I
> don't think they are currently well described. Assuming visibility can
> partially be implemented in a simpler way (e.g. top-level,
> file-private classes), the costs seem to outweigh the benefits in my
> opinion. Hence I would not be in favor of the RFC in this or a similar
> form.
> 
> Ilija
> 

Hey Ilija,

> the proposal is
> currently quite complex.

Most of this is just describing how classes work already and going in-depth on where there may be confusion -- there are no significant changes to how classes actually work. The actual changes to the engine are basically just visibility rules, some syntax changes (to allow nesting `class` inside another class), and handling the new operator. The hard part is explaining how classes work, because they don't really have a defined behavior. In other words, I cannot just say "the way this works doesn't change anything."

> They might
> still be ok if they are extremely simple

And now you can understand why they WERE just simple classes (short classes). So, you can see why I originally bundled them together because of this EXACT argument. :sigh:

> to restrict a class to a specific package/module or namespace,
> regardless of the user's class hierarchy. I believe you specifically
> call this a non-goal:

I was specifically echoing this callout from Michał earlier in this thread and other emails/comments I have gotten off-list; including my own libraries. I usually have DTOs that I want to use specifically for certain things, but not expose them in the user application -- or even the rest of the library. This isn't a replacement for "modules" or "namespace privacy" but rather, complements it, if we ever implement it.

— Rob

---

## #126740 — Ilija Tovilo — 2025-03-13T20:41:41+00:00

URL: https://externals.io/message/126589#126740 · In reply to: #126731

Hi Rob


>
> > […]
>
> Most of this is just describing how classes work already and going in-depth on where there may be confusion -- there are no significant changes to how classes actually work. The actual changes to the engine are basically just visibility rules, some syntax changes (to allow nesting `class` inside another class), and handling the new operator. The hard part is explaining how classes work, because they don't really have a defined behavior. In other words, I cannot just say "the way this works doesn't change anything."

Well, you cut out the examples I gave:

> It has a custom operator, it deals with shadowing, LSP, runtime resolution and more, in addition to visibility which is the actual goal. Those are unrelated to existing behavior, they are introduced in the proposal.

* The custom operator and runtime resolution is not something that
technically needs to be there. The name of class Foo { class Bar {} }
could simply be Foo\Bar. I've mentioned before that this does not work
with PSR-4, but there's no reason it can't work with autoloading at
all. An extended autoloading spec could recursively search
Foo/Bar/Baz.php, then Foo/Bar.php, and so forth. Given this happens
only when loading the class, and nesting would usually be limited in
quantity and amount, that seems like a reasonable solution, and
Composers optimized autoloader could avoid it entirely. This would
also solve the same issue for sealed classes, assuming they're named
in a similar fashion.

* By shadowing I referred to static:>MyClass. This makes it more
modular, but it's also another layer of complexity and indirection.
You haven't really provided a reasoning and examples of how this could
be used. It's also not clear if this can be prevented. The inner class
can be marked as final, but that wouldn't stop you from shadowing the
inner class without extending it. Similarly, static:>MyClass would
have no type guarantees, given that it can be shadowed by any classes,
not just classes that are compatible.

class Outer {
    protected class Inner {
        public static function innerTest() {}
    }

    public static function outerTest() {
        static:>Inner::innerTest();
    }
}

class OuterV2 extends Outer {
    protected class Inner {} // This breaks outerTest()
}

* LSP issues have been mentioned before. You can use self:>Inner in
method signatures even if the inner classes are private. The method
could be called from sub-classes, but they couldn't ever override it,
since there's no way to satisfy the parent signature. This makes it
implicitly final. Not technically a problem, just odd.

As mentioned, maybe there are additional use-cases this complexity can
cover, but the RFC doesn't give many examples. If the primary reason
for this complexity is just visibility, then I don't think this is the
simplest and best way in which that goal could be achieved.

> > […]
>
> And now you can understand why they WERE just simple classes (short classes). So, you can see why I originally bundled them together because of this EXACT argument. :sigh:

The arguments above are not limited to complex classes. Simple classes
would apply to.

* The :> operator is still something new that I don't believe needs to be there.
* Shadowing simple classes can still cause incompatibilities between
constructors.
* The LSP issue applies.
* As soon as we try to add support for complex classes, we'll run into
the same questions again. Thinking ahead is always worth it, to
prevent us from running into issues later. Doesn't mean everything
needs to land at the same time ofc.

Ilija

---

## #126741 — Rob Landers — 2025-03-13T20:46:49+00:00

URL: https://externals.io/message/126589#126741 · In reply to: #126729

> Hi
> 
> Am 2025-03-12 11:10, schrieb Rob Landers:
> > […]
> 
> I don't particularly like that. It is “invented syntax” and I don't 
> think that inner classes are sufficiently valuable to dedicate an entire 
> operator to them that could serve a more useful purpose in the future. 
> It also got 4 negative points in the rating back when the namespace 
> separator was decided: https://wiki.php.net/rfc/namespaceseparator
> 
> Would `\\` (i.e. two backslashes) work? The outer class for inner 
> classes effectively act as a namespace, so it makes sense to me to use 
> syntax that is similar to namespaces.
> 
> I'll look into the rest when there is a new implementation, since I 
> assume some details will still be clarified and fixed as part of 
> implementing the proposal.
> 
> Best regards
> Tim Düsterhus
> 

I am not particularly attached to the separator. I specifically chose it due to being a mixture of :: and -> and -: seemed like a bad idea. In other words, an inner class felt natural to use :> -- however, I have some issues with it myself. Particularly, it is too much like |> and as shown in the namespace RFC, way too easy to typo. Personally, after using it for a few days, I'd almost rather go back to :: ...

I will give \\ a try, but it has to be typed quite a bit when referencing inner classes, so keeping it easy to type is a must. I feel like \\ requires a large movement to type, at least on a qwerty non-english keyboard. Maybe people using other keyboards can chime in.

> I don't think that inner classes are sufficiently valuable

I'm curious why some people feel this way and why some other people are saying the opposite (emphatically). I'll nudge the private emails I've received to speak up publicly on the list as well. But, why do you feel this way?

— Rob

---

## #126742 — Rob Landers — 2025-03-13T21:23:39+00:00

URL: https://externals.io/message/126589#126742 · In reply to: #126740

> Hi Rob
> 
> 
> > […]
> 
> Well, you cut out the examples I gave:

Ahh, ok. FWIW, I only cut out your examples to shorten the email. But I see what you mean here. I think I can word the RFC in such a way as to make it so I don't have to specify how classes work along with how inner classes work... I'll have to think about it. Maybe I can just explain more about how it actually works? I'm not sure. I'll probably look at the enums RFC to get a better idea of how to do this.

For example, I can explain that an inner class is basically a class with a special name and special scopes attached. Then I can simply explain how these special scopes interact with the rest of the language. Then I can get around defining class-like behaviors, and it may even be easier to reason about.

> > […]
> 
> * The custom operator and runtime resolution is not something that
> technically needs to be there. The name of class Foo { class Bar {} }
> could simply be Foo\Bar. I've mentioned before that this does not work
> with PSR-4, but there's no reason it can't work with autoloading at
> all. An extended autoloading spec could recursively search
> Foo/Bar/Baz.php, then Foo/Bar.php, and so forth. Given this happens
> only when loading the class, and nesting would usually be limited in
> quantity and amount, that seems like a reasonable solution, and
> Composers optimized autoloader could avoid it entirely. This would
> also solve the same issue for sealed classes, assuming they're named
> in a similar fashion.

I disagree completely. If you recall, last year, I tried to pass function autoloading (which would have helped with records) and people really, really, really didn't want to have to change how autoloading worked (and I'm not talking about the list -- I mean projects). So, suggesting a change to autoloading is probably a non-starter.

That being said, I don't hate it either.

> 
> * By shadowing I referred to static:>MyClass. This makes it more
> modular, but it's also another layer of complexity and indirection.
> You haven't really provided a reasoning and examples of how this could
> be used. It's also not clear if this can be prevented. The inner class
> can be marked as final, but that wouldn't stop you from shadowing the
> inner class without extending it. Similarly, static:>MyClass would
> have no type guarantees, given that it can be shadowed by any classes,
> not just classes that are compatible.
> 
> class Outer {
>     protected class Inner {
>         public static function innerTest() {}
>     }
> 
>     public static function outerTest() {
>         static:>Inner::innerTest();
>     }
> }
> 
> class OuterV2 extends Outer {
>     protected class Inner {} // This breaks outerTest()
> }

`static` is expressly forbidden with inner classes after Tim pointed out that it basically breaks LSP. It doesn't, technically, but it makes it hard to reason about for the exact example you gave.

One thing worth pointing out here, though, is that in your example you have Outer:>Inner and OuterV2:>Inner. These are two completely distinct classes. They are not related to each other at all. 

> 
> * LSP issues have been mentioned before. You can use self:>Inner in
> method signatures even if the inner classes are private. The method
> could be called from sub-classes, but they couldn't ever override it,
> since there's no way to satisfy the parent signature. This makes it
> implicitly final. Not technically a problem, just odd.

This is not the case any more. If you have a private inner class, you cannot use it as a type declaration on a protected/public method, property, or static member. A private class can still be returned and passed around once instantiated, and it can implement an interface/base class (or not). This is very similar to C# or Java.

For example, this is ok:

interface Shape {}

class ShapeFactory {
  private class Rect implements Shape {}

  public function makeRect()): Shape { return new ShapeFactory:>Rect(); }
}

But this is not:

class ShapeFactory {
  private class Rect {}

  // Private inner class ShapeFactory::Rect cannot be used as a return type for public methods
  public function makeRect()): ShapeFactory:>Rect { return new ShapeFactory:>Rect(); }
}

> As mentioned, maybe there are additional use-cases this complexity can
> cover, but the RFC doesn't give many examples. If the primary reason
> for this complexity is just visibility, then I don't think this is the
> simplest and best way in which that goal could be achieved.

I'll add some more realistic examples to the RFC.

> 
> > […]
> 
> The arguments above are not limited to complex classes. Simple classes
> would apply to.



> 
> * The :> operator is still something new that I don't believe needs to be there.
> * Shadowing simple classes can still cause incompatibilities between
> constructors.
> * The LSP issue applies.
> * As soon as we try to add support for complex classes, we'll run into
> the same questions again. Thinking ahead is always worth it, to
> prevent us from running into issues later. Doesn't mean everything
> needs to land at the same time ofc.
> 
> Ilija
> 

— Rob

---

## #126743 — Bob Weinand — 2025-03-13T22:26:57+00:00

URL: https://externals.io/message/126589#126743 · In reply to: #126741

Hey Rob,


> 
> > […]
>
> I am not particularly attached to the separator. I specifically chose 
> it due to being a mixture of :: and -> and -: seemed like a bad idea. 
> In other words, an inner class felt natural to use :> -- however, I 
> have some issues with it myself. Particularly, it is too much like |> 
> and as shown in the namespace RFC, way too easy to typo. Personally, 
> after using it for a few days, I'd almost rather go back to :: ...
>
> I will give \\ a try, but it has to be typed quite a bit when 
> referencing inner classes, so keeping it easy to type is a must. I 
> feel like \\ requires a large movement to type, at least on a qwerty 
> non-english keyboard. Maybe people using other keyboards can chime in.

Please go back to ::. The double colon was perfectly fine, the only 
thing which was weird was the implicit constant. It's fine for constants 
and inner classes to share the same name scoping (i.e. a constant must 
not share a name with an inner class). But an inner class should not be 
an actual constant.

But otherwise, this was perfectly fine.

> > […]
>
> I'm curious why some people feel this way and why some other people 
> are saying the opposite (emphatically). I'll nudge the private emails 
> I've received to speak up publicly on the list as well. But, why do 
> you feel this way?

I don't know why some people feel this way, but at least with the 
autoloading mechanisms we have in PHP there are definite limitations to 
multiple classes in one file:
- If you deserialize your data structure and it contains another class, 
whose name does not match the file name, you better hope to god that it 
has been autoloaded already. Surprising failures in production follow. 
(e.g.: the amphp\parallel process runner will try to serialize your 
exception. That's fine. But as soon as it's accidentally bubbling up and 
as it's not autoloadable, the hell breaks loose.)
- Enums or Data Transfer objects specific to one or multiple functions 
of only this specific class would naturally fit into the same file. But 
you can't do it ... because the autoloading might try to load the enum 
first, before the class whose constructor or function you want to call 
is actually loaded.
- Now, if you actually stuff multiple classes / enums into a single 
file, it's non-trivial to figure out (as the human reader, but obviously 
for the autoloader too) which file to access to read up the definition 
of a specific enum (short of using dedicated tooling for it).

Certainly, one may say - yeah, just religiously use different files for 
... every ... single ... class.
But what's the point of that dogma?
It definitely doesn't help the organization of dedicated helper 
structures tied to a single class.
It's a tool for organization. It's not complex to understand.

Outside of very puristic arguments I don't see any reason why one would 
not want inner classes.

Further, with short classes, should they hopefully be introduced as 
well, it will become trivial to write shapes for any internal 
datastructures - simply using a "class Point(int $x, int $y); private 
Point $pos;" rather than "/** @var list{int, int} */ private array $pos;".
This will be a very ergonomic way to reduce ad-hoc arrays which are only 
typehinted by phpdoc, giving proper typing and also safe access to 
structures via named accessors rather than [0] and [1] etc.

Further Questions on the (original) RFC:

- Why is there a conflict in static properties and the inner class name? 
The former always has a leading $ sigil.

- Any particular reason to disallow abstract inner classes? Feels arbitrary.


And a note: I consider the inheritance example misguided as "static 
constructors" (static methods which invoke new()) would be a better 
pattern here. Could you maybe come up with another example here?


Bob

---

Next page: https://externals.io/message/126589.md?page=2
