[PHP-DEV] RFC: short and inner classes

On Sat, Mar 15, 2025, at 00:21, Rowan Tommins [IMSoP] wrote:

On 05/03/2025 23:11, Rob Landers wrote:

I’d like to introduce my RFC for discussion:

https://wiki.php.net/rfc/short-and-inner-classes

As a user, I find the concept of inner classes quite confusing.

However, I was looking at some code earlier and thought an “inner enum”

would be useful, to replace various “MODE_FOO” type class constants -

but I see those are left to future scope. :frowning:

Actually, it appears I did not limit it in the implementation… So, maybe we could play with it and see what breaks. I simply left traits, enums, and interfaces to future scope because 1) I didn’t have a working implementation yet, 2) had no idea what would break and 3) every time someone suggests a change to enums, the discussion explodes.

Having inner enums, traits, and interfaces is actually quite simple. Might as well see how simple. :slight_smile: But fwiw, I do plan on a near immediate RFC(s) for these if this thing passes, as well as short classes – I also have a semi-finished draft with short enums as well, but that one is actually physically impossible without making ‘enum’ a true reserved word. The “hack” (if you will) to bypass that requirement is still in place and makes the grammar (likely) impossible.

I will have to fix this tomorrow, because I am not a fan of having inner classes on interfaces, at least. I will play with it on enums and traits and see what breaks. I suspect inner classes on traits will cause utter chaos.

I’m aware they exist in a lot of other languages, though, so I thought

I’d look around at how the proposed version compares. It seems there’s

quite a zoo out there…

One common theme I do note is that all four of the pages I found are

titled “nested classes” or “nested types”; in some cases, "inner

class"/“inner type” has a specific meaning, which I don’t think matches

the RFC’s proposal.

I actually borrowed heavily from C#. I’m familiar with its usage and rules, and it fits nicely with PHP paradigms.

— Rob

On Sat, Mar 15, 2025, at 01:01, Rob Landers wrote:

On Sat, Mar 15, 2025, at 00:21, Rowan Tommins [IMSoP] wrote:

On 05/03/2025 23:11, Rob Landers wrote:

I’d like to introduce my RFC for discussion:

https://wiki.php.net/rfc/short-and-inner-classes

As a user, I find the concept of inner classes quite confusing.

However, I was looking at some code earlier and thought an “inner enum”

would be useful, to replace various “MODE_FOO” type class constants -

but I see those are left to future scope. :frowning:

Actually, it appears I did not limit it in the implementation… So, maybe we could play with it and see what breaks. I simply left traits, enums, and interfaces to future scope because 1) I didn’t have a working implementation yet, 2) had no idea what would break and 3) every time someone suggests a change to enums, the discussion explodes.

Having inner enums, traits, and interfaces is actually quite simple. Might as well see how simple. :slight_smile: But fwiw, I do plan on a near immediate RFC(s) for these if this thing passes, as well as short classes – I also have a semi-finished draft with short enums as well, but that one is actually physically impossible without making ‘enum’ a true reserved word. The “hack” (if you will) to bypass that requirement is still in place and makes the grammar (likely) impossible.

I will have to fix this tomorrow, because I am not a fan of having inner classes on interfaces, at least. I will play with it on enums and traits and see what breaks. I suspect inner classes on traits will cause utter chaos.

I’m aware they exist in a lot of other languages, though, so I thought

I’d look around at how the proposed version compares. It seems there’s

quite a zoo out there…

One common theme I do note is that all four of the pages I found are

titled “nested classes” or “nested types”; in some cases, "inner

class"/“inner type” has a specific meaning, which I don’t think matches

the RFC’s proposal.

I actually borrowed heavily from C#. I’m familiar with its usage and rules, and it fits nicely with PHP paradigms.

— Rob

After playing around with it in enums, traits, and even interfaces, I’ve decided to allow it in them all. I’ve updated the RFC, fixed an issue in the implementation, and clarified some poorly worded sections. I’ve also added more realistic examples.

— Rob

On 14 March 2025 23:37:08 GMT, Rob Landers <rob@bottled.codes> wrote:

I could get behind `::`, but I feel that it introduces human ambiguity. I don't believe it would introduce compiler ambiguity, but as a human, I have to hope the programmers are using a style that makes it obvious what are inner classes and what are constants/methods.

As far as I can see, all four languages I looked up last night (Java, C#, Swift, Kotlin) use the same syntax for accessing a nested type as for accessing a property or method, so we'd be following the crowd to use "::"

That said, I think they all also use that same syntax for namespace (or equivalent) lookups, so the same argument can be made for "\". (Why PHP separates those isn't entirely clear to me.)

Having some new syntax makes them feel more "exotic" to me. The C# and Swift docs give the impression that nesting types is "just something that can happen", rather than a whole new language concept to learn.

Java's "inner classes" are something different, and I think we should avoid using that term.

Furthermore, I'm relatively certain this approach can be slightly modified to support "namespace private/protected" classes, in general. So, that will also possibly be a follow-up RFC and having them mixed up will complicate things. In any case, I am not a fan of using the namespace separator here.

To me namespace, module, or file visibility seem like much more natural additions to the language, and to solve most of the same problems as nesting types.

I guess a public nested class is a bit like a "friend class"? In that it has special access to its surrounding class, but otherwise might as well just be sharing a namespace with it. But it's also necessarily in the same file, so marking those members "file private" rather than "private" would also allow that.

A private nested class can only be explicitly mentioned within that narrow context, which again feels very much equivalent to "file private".

$user = new User:>Builder("Rob")->withEmail("rob@bottled.codes")->build();

The user builder is intrinsically tied to the User class itself, it isn't just a namespace. The user builder shares scope with the user class and is able to be the only way to construct a user (barring reflection).

If we had either file or namespace visibility, exactly the same thing could be achieved with those, and would look like this:

$user = new User\Builder("Rob")->withEmail("rob@bottled.codes")->build();

The "User" class would have a "file private" or "namespace private" constructor, callable from the "User\Builder" class but not elsewhere; the build() method would return the "User" instance.

I think I'm coming to the conclusion that we should use backslash: nested types can be viewed as a shorthand way of having a class and namespace with the same name, plus applying some visibility rules to that namespace.

Rowan Tommins
[IMSoP]

Le 15 mars 2025 à 12:53, Rowan Tommins [IMSoP] imsop.php@rwec.co.uk a écrit :

On 14 March 2025 23:37:08 GMT, Rob Landers rob@bottled.codes wrote:

I could get behind ::, but I feel that it introduces human ambiguity. I don’t believe it would introduce compiler ambiguity, but as a human, I have to hope the programmers are using a style that makes it obvious what are inner classes and what are constants/methods.

As far as I can see, all four languages I looked up last night (Java, C#, Swift, Kotlin) use the same syntax for accessing a nested type as for accessing a property or method, so we’d be following the crowd to use “::”

That said, I think they all also use that same syntax for namespace (or equivalent) lookups, so the same argument can be made for "". (Why PHP separates those isn’t entirely clear to me.)

According to my archeological research, it was originally designed to reuse :: as namespace separator, but it was finally changed to something else due to ambiguity between static class elements and namespaced functions/constants. See https://wiki.php.net/rfc/namespaceissues and https://wiki.php.net/rfc/backslashnamespaces (where :: is assumed to be the namespace separator).

—Claude

Claude, exactly! By using ‘::’ you cannot distinguish between a class and a function. So this is not an option because it leads to a headache. Just my 2 cents

···

Iliya Miroslavov Iliev
i.miroslavov@gmail.com

On Sat, Mar 15, 2025, at 12:53, Rowan Tommins [IMSoP] wrote:

On 14 March 2025 23:37:08 GMT, Rob Landers <rob@bottled.codes> wrote:

I could get behind ::, but I feel that it introduces human ambiguity. I don’t believe it would introduce compiler ambiguity, but as a human, I have to hope the programmers are using a style that makes it obvious what are inner classes and what are constants/methods.

As far as I can see, all four languages I looked up last night (Java, C#, Swift, Kotlin) use the same syntax for accessing a nested type as for accessing a property or method, so we’d be following the crowd to use “::”

That said, I think they all also use that same syntax for namespace (or equivalent) lookups, so the same argument can be made for "". (Why PHP separates those isn’t entirely clear to me.)

Having some new syntax makes them feel more “exotic” to me. The C# and Swift docs give the impression that nesting types is “just something that can happen”, rather than a whole new language concept to learn.

Java’s “inner classes” are something different, and I think we should avoid using that term.

Furthermore, I’m relatively certain this approach can be slightly modified to support “namespace private/protected” classes, in general. So, that will also possibly be a follow-up RFC and having them mixed up will complicate things. In any case, I am not a fan of using the namespace separator here.

To me namespace, module, or file visibility seem like much more natural additions to the language, and to solve most of the same problems as nesting types.

I guess a public nested class is a bit like a “friend class”? In that it has special access to its surrounding class, but otherwise might as well just be sharing a namespace with it. But it’s also necessarily in the same file, so marking those members “file private” rather than “private” would also allow that.

A private nested class can only be explicitly mentioned within that narrow context, which again feels very much equivalent to “file private”.

$user = new User:>Builder(“Rob”)->withEmail(“rob@bottled.codes”)->build();

The user builder is intrinsically tied to the User class itself, it isn’t just a namespace. The user builder shares scope with the user class and is able to be the only way to construct a user (barring reflection).

If we had either file or namespace visibility, exactly the same thing could be achieved with those, and would look like this:

$user = new User\Builder(“Rob”)->withEmail(“rob@bottled.codes”)->build();

The “User” class would have a “file private” or “namespace private” constructor, callable from the “User\Builder” class but not elsewhere; the build() method would return the “User” instance.

I think I’m coming to the conclusion that we should use backslash: nested types can be viewed as a shorthand way of having a class and namespace with the same name, plus applying some visibility rules to that namespace.

Rowan Tommins

[IMSoP]

I don’t think \ is viable. If we used slash it would complicate autoloading and be impossible to differentiate between

namespace Outer;

class Inner {}

And

class Outer {

class Inner {}

}

These would both resolve to the same class name for Outer\Inner. Which one it resolves to would depend on how you implement autoloading (which may be desirable for BC reasons). Then there becomes the question of either letting user-land implement the autoloading changes, or have php walk “up” the namespace chain in the hopes it implements an inner class.

So, maybe, it could be useful to use \ but in the long run, I’m not sure it makes sense.

— Rob

On 17 March 2025 07:11:23 GMT, Rob Landers <rob@bottled.codes> wrote:

namespace Outer;

class Inner {}

And

class Outer {
class Inner {}
}

These would both resolve to the same class name for Outer\Inner.

That's the frame challenge: is that actually a good thing, because it makes the description of nested types a lot simpler?

Which one it resolves to would depend on how you implement autoloading

That's just the same as having the same class defined in two files on disk - PHP doesn't know which will be used until an autoloader runs.

From what I understand of the proposal, the calling code won't know anything different based on it being "nested" or "namespaced", it will just see a class with a long name with some punctuation in.

Then there becomes the question of either letting user-land implement the autoloading changes, or have php walk “up” the namespace chain in the hopes it implements an inner class.

I think it should be left to the implementation - PHP makes no assumption about one class per file anyway, so am implementation could already do this with a namespace{} block.

It is a reasonable point that the convention for when an autoloader should walk up, and how far, would need to be defined. Then again, I'm increasingly convinced autoloading is a dead-end for the language, and hope to see more enhancements to preloading and a module system instead.

So, maybe, it could be useful to use \ but in the long run, I’m not sure it makes sense.

I rather think the other way round: in the short term, a new separator would save users a bit of pain with autoloading, but in the long run it will look like a weird anomaly that no other language needs.

Rowan Tommins
[IMSoP]

On Mon, Ma0r 17, 2025 at 9:56 AM Rowan Tommins [IMSoP] <imsop.php@rwec.co.uk> wrote:

On 17 March 2025 07:11:23 GMT, Rob Landers rob@bottled.codes wrote:

Which one it resolves to would depend on how you implement autoloading

That’s just the same as having the same class defined in two files on disk - PHP doesn’t know which will be used until an autoloader runs.

From what I understand of the proposal, the calling code won’t know anything different based on it being “nested” or “namespaced”, it will just see a class with a long name with some punctuation in.

The problem for me is not autoloading, but that you can’t have the two classes defined at the same time, while using some other punctuation it would allow it.
I believe that there are other operators that we can use, to allow this.

I rather think the other way round: in the short term, a new separator would save users a bit of pain with autoloading, but in the long run it will look like a weird anomaly that no other language needs.

There was much discussion about various ways to solve the problem stated in the RFC, but I think we need to look at it as a broader picture again.
I’ll share what I’ve been thinking about in the past week and didn’t yet had time to reply here.

Right now, PHP only has public classes.
For better encapsulation and cohesion, we want to define some new ways to define classes with some limited restrictions or improved access.
Taking examples from other languages, we can have two ways to do this:

  • namespace private classes, or file-private classes - this controls the visibility of the class at the namespace or file level. They’re essentially about managing the scope of classes without changing their fundamental behavior.
  • nested (inner) classes - this allows a class defined inside another to access private members of its enclosing class, increasing cohesion. And we can have further here just public visibility, or protected/private visibility, for better encapsulation.

Given this, I think we could even implement all of these in separate RFCs:

  • public nested classes
  • file-private classes
  • namespace private classes
  • protected/private nested classes
  • other-grouping-level-that-will-exist-in-the-future private classes

Of all of this, the first, public nested classes are the most simple and would drive cohesion. And maybe we can have only that for now.
The other features would be mostly important for encapsulation, with limiting the class visibility.

We just need to agree on a separator/operator, and IMHO, it should be clear and distinct from namespace separator, as nested classes are a separate concept.
Maybe having a vote on the operator would be enough.

(As a note, and this might have been discussed already, but I would prefer to use the term nested class instead of inner class, as in java the inner classes means non-static classes, and I don’t think we should go that way.)


Alex

On 17/03/2025 10:37, Alexandru Pătrănescu wrote:

    From what I understand of the proposal, the calling code won't
    know anything different based on it being "nested" or
    "namespaced", it will just see a class with a long name with some
    punctuation in.

The problem for me is not autoloading, but that you can't have the two classes defined at the same time, while using some other punctuation it would allow it.
I believe that there are other operators that we can use, to allow this.

Again, I challenge the premise: why would you want to have both defined at the same time?

Do developers in Java, C#, Kotlin, or Swift complain about this limitation?

Is there any other language which makes a syntax distinction between "class Foo in namespace Bar" and "class Foo nested in class Bar"?

(As a note, and this might have been discussed already, but I would prefer to use the term nested class instead of inner class, as in java the inner classes means non-static classes, and I don't think we should go that way.)

100% agree.

If we did implement something more like "inner classes", a special syntax might make more sense - there would be a very specific relationship between the inner and outer classes. I don't think "has special visibility of members, like a friend-class or file-private feature" needs to be highlighted in the syntax that way.

--
Rowan Tommins
[IMSoP]

On Wed, 5 Mar 2025 at 23:14, Rob Landers rob@bottled.codes wrote:

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.

I have read and reread this RFC, and I am struggling to see

  1. What problem does this solve that anonymous classes do not?
  2. As with any syntax change and new operator there needs to be very careful consideration, do we need a new operation, or could :: if the parent is static or -> if the class is initialized?
  3. The idea that extending the parent class doesnt no inherit the child classes doesnt make sense to me.
    As then if you extend a parent class and call a function of that class which could rely on the existence of an inner class, I can see a lot of headaches caused by this exact scenario.
    As a developer, if I extend a class, I expect the entire dependance of that class to be inherited, otherwise the extending class won’t work.

On Mon, Mar 17, 2025, at 16:30, fennic log wrote:

On Wed, 5 Mar 2025 at 23:14, Rob Landers rob@bottled.codes wrote:

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.

I have read and reread this RFC, and I am struggling to see

  1. What problem does this solve that anonymous classes do not?

Thank you for asking this question. :slight_smile: Anonymous classes can be useful to “hack your way around” the problem that inner (nested?) classes solve. Anonymous classes are inline, leading to complex/long functions that would do nothing but define a class. Anonymous classes can (could?) be hacked to allow for reuse (extending), but only exist where they are defined. An inner class can be extended and reused just fine. Finally, nested classes allow for more readable code without sacrificing encapsulation.

  1. As with any syntax change and new operator there needs to be very careful consideration, do we need a new operation, or could :: if the parent is static or -> if the class is initialized?

There’s quite a long thread already about this very topic. That being said, the inner class has no bearing on whether the outer class is instantiated or not. Originally, I used :: as the separator, but it seems there are some good arguments for \, so we will see.

  1. The idea that extending the parent class doesnt no inherit the child classes doesnt make sense to me.

As then if you extend a parent class and call a function of that class which could rely on the existence of an inner class, I can see a lot of headaches caused by this exact scenario.

As a developer, if I extend a class, I expect the entire dependance of that class to be inherited, otherwise the extending class won’t work.

I’m not sure what you mean. When you inherit a class, you do not necessarily inherit everything from its superclass. You are free to override it however you want. Since we are defining “types” in the sense of PHP, we cannot merely inherit a “type”, otherwise we would cause all kinds of issues with LSP. This is specifically why inheritance works the way it does in this RFC and why static:: is forbidden.

— Rob

On Mon, Mar 17, 2025, at 12:29, Rowan Tommins [IMSoP] wrote:

On 17/03/2025 10:37, Alexandru Pătrănescu wrote:

From what I understand of the proposal, the calling code won’t know anything different based on it being “nested” or “namespaced”, it will just see a class with a long name with some punctuation in.

The problem for me is not autoloading, but that you can’t have the two classes defined at the same time, while using some other punctuation it would allow it.

I believe that there are other operators that we can use, to allow this.

Again, I challenge the premise: why would you want to have both defined at the same time?

Do developers in Java, C#, Kotlin, or Swift complain about this limitation?

Is there any other language which makes a syntax distinction between “class Foo in namespace Bar” and “class Foo nested in class Bar”?

No, almost no other language I know of seems to make a distinction like PHP does. It appears that PHP is unique in this. Does that mean we can learn more from these other languages, or less?

I’d be more than happy to put forward a short RFC or poll on the syntax choices, as Alexander suggested. Even if this RFC fails, it would be good to get a consensus for the future.

(As a note, and this might have been discussed already, but I would prefer to use the term nested class instead of inner class, as in java the inner classes means non-static classes, and I don’t think we should go that way.)

100% agree.

If we did implement something more like “inner classes”, a special syntax might make more sense - there would be a very specific relationship between the inner and outer classes. I don’t think “has special visibility of members, like a friend-class or file-private feature” needs to be highlighted in that way.

I am inclined to agree with you both on this. Originally I had planned for an “outer” and “$outer” that made it more like Java inner classes, but decided to leave it to a future RFC if someone ever wanted to implement it. When I first started thinking about this RFC, static classes had fairly recently failed, and I didn’t want to define what a “static inner class” was. This is part of the reason why “static” isn’t allowed as an inner class modifier. This may be a potential future RFC if people have need of it.

— Rob

Hey Rob,

···

On 17.3.2025 17:53:26, Rob Landers wrote:

On Mon, Mar 17, 2025, at 16:30, fennic log wrote:

  1. As with any syntax change and new operator there needs to be very careful consideration, do we need a new operation, or could :: if the parent is static or -> if the class is initialized?

There’s quite a long thread already about this very topic. That being said, the inner class has no bearing on whether the outer class is instantiated or not. Originally, I used :: as the separator, but it seems there are some good arguments for \, so we will see.

I have not grasped any single argument in favour of , except “other languages are doing it too”, “existing tooling splitting on backslash would continue to work” and “we could use the existing use statement as is”.

The problems are numerous though:

  1. Unlike other languages (e.g. C# and java), namespaces and classes may share a name in PHP. E.g. class “Y” in a namespace “X” and class “X” may not exist both. In PHP that’s allowed. Inner classes compete with namespaced classes for their fully qualified name.
  2. Autoloading will have to become aware of inner classes.
  3. Autoloading will have to do a non-cacheable check for existence on the inner class, before iteratively testing whether any parent has a respective .php file. Currently autoloading takes a class name, transforms it to a single canonical path, and includes it. Always succeeding.
  4. Humans, navigating to a path, will not be able to navigate to a file directly but have to search for it.
  5. Tooling splitting on backslash might assume the 1:1 mapping to the filesystem and break. It’s really not a catch all. Some things may be broken and some not, but all need to be probably revisited anyway.
  6. It makes accessing via parent, self or static weird. These keywords have always been followed by a double colon. (Even if this particular RFC does not end up adding them) And it will conflict with a namespace named “parent” for example.
  7. At least right now, every backslash identified symbol is trivially universally public. With \ as an inner class separator, this would no longer be the case. Maybe that will eventually change, but today this can be relied upon.

Also, just because other languages are doing a mistake, it does not mean we have to repeat it. They are generally doing it because their identifier separator is universal and it’s consistent. It does not mean that it’s without its own problems.

Using the double colon is a very minor BC break (accessing a class by a class constant value?! That’s also quite inconsistent that it works at all, as you can’t do that with normal constants, only class constants.).
Using another sigil would also be possible (like :>). But for the backslash I only see drawbacks.

Also, nothing precludes us from allowing “use Foo\Bar::Inner;”.

  1. The idea that extending the parent class doesnt no inherit the child classes doesnt make sense to me.

As then if you extend a parent class and call a function of that class which could rely on the existence of an inner class, I can see a lot of headaches caused by this exact scenario.

As a developer, if I extend a class, I expect the entire dependance of that class to be inherited, otherwise the extending class won’t work.

I’m not sure what you mean. When you inherit a class, you do not necessarily inherit everything from its superclass. You are free to override it however you want. Since we are defining “types” in the sense of PHP, we cannot merely inherit a “type”, otherwise we would cause all kinds of issues with LSP. This is specifically why inheritance works the way it does in this RFC and why static:: is forbidden.

I don’t understand the problem here.

for each nested class in parent class:
class_alias(substitute parent class with child class in class name, class name)

Very simple. There’s no magic needed, it can be simply class aliases. This will also make static, self and parent trivially work.

On 15.3.2025 00:37:08, Rob Landers wrote:

Classes don’t actually know their inner classes – they aren’t like properties. In essence, an inner class is just a regular class with a funny name and access to scopes it wouldn’t normally have access to. We could probably add getOuterClass(): string if that is useful. It is possible to keep track of a class’s inner classes, but then that introduces a paradox chicken/egg type problem during construction, which may or may not be a problem.

I don’t see the chicken-and-egg problem here. You can simply collect all the inner class names at compilation. You don’t even need to actually store the zend_class_entry - just collect the names and fetch from the class table at runtime (and ignore missing ones, e.g. if there was an error during linking).

On 17 March 2025 18:05:49 GMT, Bob Weinand <bobwei9@hotmail.com> wrote:

I have not grasped any single argument in favour of \, except "other languages are doing it too", "existing tooling splitting on backslash would continue to work" and "we could use the existing use statement as is".

This wording feels a bit disingenuous - clearly, you *can* grasp some advantages. It's fine if you don't think those advantages outweigh the disadvantages, but that's different from believing they don't exist.

In other words, it's like the famous Monty Python joke:

All right, but apart from the sanitation, medicine, education, wine, public order, irrigation, roads, the fresh water system and public health, what have the Romans ever done for us?

Also, just because other languages are doing a mistake, it does not mean we have to repeat it. They are generally doing it because their identifier separator is universal and it's consistent. It does not mean that it's without its own problems.

Absolutely, but where there's a wide adoption of a particular pattern or style, it's worth at least asking whether we're making things better or worse by doing something different.

If we look at that, and decide we can do something better, great!

Using the double colon is a very minor BC break (accessing a class by a class constant value?! That's also quite inconsistent that it works at all, as you can't do that with normal constants, only class constants.).
Using another sigil would also be possible (like :>). But for the backslash I only see drawbacks.

Also, nothing precludes us from allowing "use Foo\Bar::Inner;".

Personally, I would be equally happy with either \ or :: and less happy with anything that required us choosing yet another set of punctuation, for what is otherwise quite a minor feature in its language impact.

Rowan Tommins
[IMSoP]

On Mon, Mar 17, 2025, at 19:05, Bob Weinand wrote:

Hey Rob,

On 17.3.2025 17:53:26, Rob Landers wrote:

On Mon, Mar 17, 2025, at 16:30, fennic log wrote:

  1. As with any syntax change and new operator there needs to be very careful consideration, do we need a new operation, or could :: if the parent is static or -> if the class is initialized?

There’s quite a long thread already about this very topic. That being said, the inner class has no bearing on whether the outer class is instantiated or not. Originally, I used :: as the separator, but it seems there are some good arguments for \, so we will see.

I have not grasped any single argument in favour of , except “other languages are doing it too”, “existing tooling splitting on backslash would continue to work” and “we could use the existing use statement as is”.

The problems are numerous though:

  1. Unlike other languages (e.g. C# and java), namespaces and classes may share a name in PHP. E.g. class “Y” in a namespace “X” and class “X” may not exist both. In PHP that’s allowed. Inner classes compete with namespaced classes for their fully qualified name.

  2. Autoloading will have to become aware of inner classes.

  3. Autoloading will have to do a non-cacheable check for existence on the inner class, before iteratively testing whether any parent has a respective .php file. Currently autoloading takes a class name, transforms it to a single canonical path, and includes it. Always succeeding.

  4. Humans, navigating to a path, will not be able to navigate to a file directly but have to search for it.

  5. Tooling splitting on backslash might assume the 1:1 mapping to the filesystem and break. It’s really not a catch all. Some things may be broken and some not, but all need to be probably revisited anyway.

  6. It makes accessing via parent, self or static weird. These keywords have always been followed by a double colon. (Even if this particular RFC does not end up adding them) And it will conflict with a namespace named “parent” for example.

  7. At least right now, every backslash identified symbol is trivially universally public. With \ as an inner class separator, this would no longer be the case. Maybe that will eventually change, but today this can be relied upon.

Also, just because other languages are doing a mistake, it does not mean we have to repeat it. They are generally doing it because their identifier separator is universal and it’s consistent. It does not mean that it’s without its own problems.

Using the double colon is a very minor BC break (accessing a class by a class constant value?! That’s also quite inconsistent that it works at all, as you can’t do that with normal constants, only class constants.).

Using another sigil would also be possible (like :>). But for the backslash I only see drawbacks.

Also, nothing precludes us from allowing “use Foo\Bar::Inner;”.

  1. The idea that extending the parent class doesnt no inherit the child classes doesnt make sense to me.

As then if you extend a parent class and call a function of that class which could rely on the existence of an inner class, I can see a lot of headaches caused by this exact scenario.

As a developer, if I extend a class, I expect the entire dependance of that class to be inherited, otherwise the extending class won’t work.

I’m not sure what you mean. When you inherit a class, you do not necessarily inherit everything from its superclass. You are free to override it however you want. Since we are defining “types” in the sense of PHP, we cannot merely inherit a “type”, otherwise we would cause all kinds of issues with LSP. This is specifically why inheritance works the way it does in this RFC and why static:: is forbidden.

I don’t understand the problem here.

for each nested class in parent class:

class_alias(substitute parent class with child class in class name, class name)

Very simple. There’s no magic needed, it can be simply class aliases. This will also make static, self and parent trivially work.

PHP doesn’t go out of its way to prevent the developer from violating LSP – but where it can, it does. If a type were inherited, then the subclasses wouldn’t be able to guarantee that an inner/nested class implemented the parent inner/nested class. Or, if it did, it would require that all subclasses using a class of the same name MUST inherit from the parent class as well.

This is non-trivial to implement as the parent class may or may not have been compiled yet when we are compiling the subclass. So, we have no idea what the parent class actually looks like until runtime. Further, it is much simpler to reason about each class as a distinct type vs. maybe inheriting a type from a supertype.

Thus, if you want to inherit from the super-class’s inner classes, you can, but this RFC won’t force you to do so. In my mind, this is the biggest argument for a \, because the enclosing class acts more like a namespace than a type, from the perspective of the inner class.

If we were to embrace \, then it could be argued that a namespace is technically a class in itself, (but a partial class, to borrow from C# terminology) and every class in a namespace is essentially just a public class in that super-class/namespace.

Nobody has argued that perspective, but I believe it may be interesting (and would lay a foundation for namespace private/public class behavior/visibility). That being said, it truly does cause issues with autoloading – at least, PSR-4 autoloading – and I’m not sure whether we should solve that problem here; however, it is something to be cognizant of, for sure. There are other types of autoloading supported by tools, such as composer, that do not have any issues with using \ as a separator.

On 15.3.2025 00:37:08, Rob Landers wrote:

Classes don’t actually know their inner classes – they aren’t like properties. In essence, an inner class is just a regular class with a funny name and access to scopes it wouldn’t normally have access to. We could probably add getOuterClass(): string if that is useful. It is possible to keep track of a class’s inner classes, but then that introduces a paradox chicken/egg type problem during construction, which may or may not be a problem.

I don’t see the chicken-and-egg problem here. You can simply collect all the inner class names at compilation. You don’t even need to actually store the zend_class_entry - just collect the names and fetch from the class table at runtime (and ignore missing ones, e.g. if there was an error during linking).

:upside_down_face:sometimes the obvious solutions are the best solutions.

— Rob

On Fri, 14 Mar 2025, Bilge wrote:

On 14/03/2025 21:08, Rowan Tommins [IMSoP] wrote:
> On 14/03/2025 17:37, Rob Landers wrote:
> > What about a hybrid approach? Maybe something like `\\` that Tim
> > suggested?
>
> As a perfect example of this, check out how the second paragraph is
> rendered wrong here: RFC: short and inner classes - Externals
> (compare here: php.internals: Re: Re: RFC: short and inner classes)

Just because some (incompetent) developers cannot grasp proper
escaping should have no bearing whatsoever on language design.

I'm just catching up with this thread, but ad-hominems are not wanted
on this list.

Nor is it acceptable to indicate that because one developer might have
less time, to call "RIP PHP".

Please remember that we're trying to improve the language here.

I would urge you to familiarise yourself with the Mailinglist Rules
again:

cheers,
Derick

Hey,

···

On 17.3.2025 19:58:39, Rob Landers wrote:

On Mon, Mar 17, 2025, at 19:05, Bob Weinand wrote:

. The idea that extending the parent class doesnt no inherit the child classes doesnt make sense to me.

As then if you extend a parent class and call a function of that class which could rely on the existence of an inner class, I can see a lot of headaches caused by this exact scenario.

As a developer, if I extend a class, I expect the entire dependance of that class to be inherited, otherwise the extending class won’t work.

I’m not sure what you mean. When you inherit a class, you do not necessarily inherit everything from its superclass. You are free to override it however you want. Since we are defining “types” in the sense of PHP, we cannot merely inherit a “type”, otherwise we would cause all kinds of issues with LSP. This is specifically why inheritance works the way it does in this RFC and why static:: is forbidden.

I don’t understand the problem here.

for each nested class in parent class:

class_alias(substitute parent class with child class in class name, class name)

Very simple. There’s no magic needed, it can be simply class aliases. This will also make static, self and parent trivially work.

PHP doesn’t go out of its way to prevent the developer from violating LSP – but where it can, it does. If a type were inherited, then the subclasses wouldn’t be able to guarantee that an inner/nested class implemented the parent inner/nested class. Or, if it did, it would require that all subclasses using a class of the same name MUST inherit from the parent class as well.

This is non-trivial to implement as the parent class may or may not have been compiled yet when we are compiling the subclass. So, we have no idea what the parent class actually looks like until runtime. Further, it is much simpler to reason about each class as a distinct type vs. maybe inheriting a type from a supertype.

Thus, if you want to inherit from the super-class’s inner classes, you can, but this RFC won’t force you to do so. In my mind, this is the biggest argument for a \, because the enclosing class acts more like a namespace than a type, from the perspective of the inner class.

If we were to embrace \, then it could be argued that a namespace is technically a class in itself, (but a partial class, to borrow from C# terminology) and every class in a namespace is essentially just a public class in that super-class/namespace.

Nobody has argued that perspective, but I believe it may be interesting (and would lay a foundation for namespace private/public class behavior/visibility). That being said, it truly does cause issues with autoloading – at least, PSR-4 autoloading – and I’m not sure whether we should solve that problem here; however, it is something to be cognizant of, for sure. There are other types of autoloading supported by tools, such as composer, that do not have any issues with using \ as a separator.

Okay, I see the point with LSP. I’m not sure whether we need to preserve LSP for that specific scenario, but neither can I say that we should ignore it.

(Effectively implementing LSP would mean that there’s an implicit interface matching all public method signatures of the parent class, for child classes - which is doable, but possibly too much for the initial RFC.)

I would however ask, should we not implement LSP compatible inner classes, to enforce that no child class may name a class the same than any non-private inner class declared by any of its parents, until we resolve this question (in possibly a future version of PHP).
I do not think we should bar ourselves from allowing this in the future.

However nice grand unified naming schemes may seem, I don’t think it’s a good idea to pursue. Clarity and explicitness shall reign supreme here.
I also don’t think that the proposed visibilities are applicable to namespaced classes. In particular and in practice shared internal classes are not necessarily directly organized in a way it makes sense to organize inner classes. Also visibilities like protected propagate along the inheritance chain, something which is not given with (outer) namespaced classes.
The less we mix these slightly different concepts, the better.

“It’s similar, except in these and those cases” is the death of all consistent experiences. Thus, let’s not even attempt to pretend it is.
And not pretending starts with using a different symbol than a backslash.

Bob

On Tue, Mar 18, 2025, at 03:37, Bob Weinand wrote:

Hey,

On 17.3.2025 19:58:39, Rob Landers wrote:

On Mon, Mar 17, 2025, at 19:05, Bob Weinand wrote:

. The idea that extending the parent class doesnt no inherit the child classes doesnt make sense to me.

As then if you extend a parent class and call a function of that class which could rely on the existence of an inner class, I can see a lot of headaches caused by this exact scenario.

As a developer, if I extend a class, I expect the entire dependance of that class to be inherited, otherwise the extending class won’t work.

I’m not sure what you mean. When you inherit a class, you do not necessarily inherit everything from its superclass. You are free to override it however you want. Since we are defining “types” in the sense of PHP, we cannot merely inherit a “type”, otherwise we would cause all kinds of issues with LSP. This is specifically why inheritance works the way it does in this RFC and why static:: is forbidden.

I don’t understand the problem here.

for each nested class in parent class:

class_alias(substitute parent class with child class in class name, class name)

Very simple. There’s no magic needed, it can be simply class aliases. This will also make static, self and parent trivially work.

PHP doesn’t go out of its way to prevent the developer from violating LSP – but where it can, it does. If a type were inherited, then the subclasses wouldn’t be able to guarantee that an inner/nested class implemented the parent inner/nested class. Or, if it did, it would require that all subclasses using a class of the same name MUST inherit from the parent class as well.

This is non-trivial to implement as the parent class may or may not have been compiled yet when we are compiling the subclass. So, we have no idea what the parent class actually looks like until runtime. Further, it is much simpler to reason about each class as a distinct type vs. maybe inheriting a type from a supertype.

Thus, if you want to inherit from the super-class’s inner classes, you can, but this RFC won’t force you to do so. In my mind, this is the biggest argument for a \, because the enclosing class acts more like a namespace than a type, from the perspective of the inner class.

If we were to embrace \, then it could be argued that a namespace is technically a class in itself, (but a partial class, to borrow from C# terminology) and every class in a namespace is essentially just a public class in that super-class/namespace.

Nobody has argued that perspective, but I believe it may be interesting (and would lay a foundation for namespace private/public class behavior/visibility). That being said, it truly does cause issues with autoloading – at least, PSR-4 autoloading – and I’m not sure whether we should solve that problem here; however, it is something to be cognizant of, for sure. There are other types of autoloading supported by tools, such as composer, that do not have any issues with using \ as a separator.

Okay, I see the point with LSP. I’m not sure whether we need to preserve LSP for that specific scenario, but neither can I say that we should ignore it.

(Effectively implementing LSP would mean that there’s an implicit interface matching all public method signatures of the parent class, for child classes - which is doable, but possibly too much for the initial RFC.)

I would however ask, should we not implement LSP compatible inner classes, to enforce that no child class may name a class the same than any non-private inner class declared by any of its parents, until we resolve this question (in possibly a future version of PHP).

I do not think we should bar ourselves from allowing this in the future.

I’m not sure I understand what you are asking. But I think you are saying the following should be illegal?

class ParentOuter {

class ParentInner {}

}

class ChildOuter extends ParentOuter {

class ParentInner {} // not allowed

}

However nice grand unified naming schemes may seem, I don’t think it’s a good idea to pursue. Clarity and explicitness shall reign supreme here.

I also don’t think that the proposed visibilities are applicable to namespaced classes. In particular and in practice shared internal classes are not necessarily directly organized in a way it makes sense to organize inner classes. Also visibilities like protected propagate along the inheritance chain, something which is not given with (outer) namespaced classes.

The less we mix these slightly different concepts, the better.

“It’s similar, except in these and those cases” is the death of all consistent experiences. Thus, let’s not even attempt to pretend it is.

This is true.

And not pretending starts with using a different symbol than a backslash.

I have been thinking about this for a couple of days now… When thinking through the ramifications of my decision to use :> over ::, this will also affect generics, most likely – whenever that happens. This is because if this RFC passes, generics will want to be consistent with whatever exists currently.

If we were to use :>, then generics would probably look something like this to be consistent:

class Box {

public function add(self:>T $item) {}

}

The same thing would also probably apply to ::

class Box {

public function add(self::T $item) {}

}

With \, it nearly follows exactly what you would expect-ish:

use \Box\T as T;

class Box {

public function add(T $item) {}

// or without use

public function add(Box\T $item) {}

}

With \, we can also just automatically check the current class as part of namespace resolution when compiling:

class Box {

public function add(T $item) {}

}

This would also make it easier to user inner classes:

class Outer {

public class Inner {}

public function foo(Inner $bar) {}

}

The other syntax options do not allow this; at least, I don’t think so. I’m very heavily leaning towards \ as the separator.

— Rob

On 19.3.2025 16:04:06, Rob Landers wrote:

On Tue, Mar 18, 2025, at 03:37, Bob Weinand wrote:

Okay, I see the point with LSP. I'm not sure whether we need to preserve LSP for that specific scenario, but neither can I say that we should ignore it.

(Effectively implementing LSP would mean that there's an implicit interface matching all public method signatures of the parent class, for child classes - which is doable, but possibly too much for the initial RFC.)

I would however ask, should we not implement LSP compatible inner classes, to enforce that no child class may name a class the same than any non-private inner class declared by any of its parents, until we resolve this question (in possibly a future version of PHP).
I do not think we should bar ourselves from allowing this in the future.

I'm not sure I understand what you are asking. But I think you are saying the following should be illegal?

class ParentOuter {
class ParentInner {}
}

class ChildOuter extends ParentOuter {
class ParentInner {} // not allowed
}

Precisely.

And not pretending starts with using a different symbol than a backslash.

I have been thinking about this for a couple of days now... When thinking through the ramifications of my decision to use :> over ::, this will also affect generics, most likely -- whenever that happens. This is because if this RFC passes, generics will want to be consistent with whatever exists currently.

If we were to use :>, then generics would probably look something like this to be consistent:

class Box<T> {
public function add(self:>T $item) {}
}

The same thing would also probably apply to ::

class Box<T> {
public function add(self::T $item) {}
}

With `\`, it nearly follows exactly what you would expect-ish:

use \Box\T as T;

class Box<T> {
public function add(T $item) {}

// or without use
public function add(Box\T $item) {}
}

With `\`, we can also just automatically check the current class as part of namespace resolution when compiling:

class Box<T> {
public function add(T $item) {}
}

This would also make it easier to user inner classes:

class Outer {
public class Inner {}
public function foo(Inner $bar) {}
}

The other syntax options do not allow this; at least, I don't think so. I'm very heavily leaning towards `\` as the separator.

— Rob

I'm failing to understand why you'd think this would be related at all?

If we get generics,

class Box<T> {
public function add(T $item) {}
}

would just work, without any use or such. There will not be a symbol Box::T or Box\T, just all mentions of T within the Box class will be taken as "this is the generic placeholder" and the compiler takes care.
It's not like that T will be directly accessible from outside of the class or actually a proper type, unlike inner classes.

A generic is not an inner class nor will it look like it. Also, there's no accessing of a parents generic - you write class Child<T> extends ParentClass<T> - or something along these lines, getting the T available for your class.

Bob

On Wed, Mar 19, 2025, at 21:09, Bob Weinand wrote:

On 19.3.2025 16:04:06, Rob Landers wrote:

On Tue, Mar 18, 2025, at 03:37, Bob Weinand wrote:

Okay, I see the point with LSP. I’m not sure whether we need to preserve LSP for that specific scenario, but neither can I say that we should ignore it.

(Effectively implementing LSP would mean that there’s an implicit interface matching all public method signatures of the parent class, for child classes - which is doable, but possibly too much for the initial RFC.)

I would however ask, should we not implement LSP compatible inner classes, to enforce that no child class may name a class the same than any non-private inner class declared by any of its parents, until we resolve this question (in possibly a future version of PHP).

I do not think we should bar ourselves from allowing this in the future.

I’m not sure I understand what you are asking. But I think you are saying the following should be illegal?

class ParentOuter {

class ParentInner {}

}

class ChildOuter extends ParentOuter {

class ParentInner {} // not allowed

}

Precisely.

And not pretending starts with using a different symbol than a backslash.

I have been thinking about this for a couple of days now… When thinking through the ramifications of my decision to use :> over ::, this will also affect generics, most likely – whenever that happens. This is because if this RFC passes, generics will want to be consistent with whatever exists currently.

If we were to use :>, then generics would probably look something like this to be consistent:

class Box {

public function add(self:>T $item) {}

}

The same thing would also probably apply to ::

class Box {

public function add(self::T $item) {}

}

With \, it nearly follows exactly what you would expect-ish:

use \Box\T as T;

class Box {

public function add(T $item) {}

// or without use

public function add(Box\T $item) {}

}

With \, we can also just automatically check the current class as part of namespace resolution when compiling:

class Box {

public function add(T $item) {}

}

This would also make it easier to user inner classes:

class Outer {

public class Inner {}

public function foo(Inner $bar) {}

}

The other syntax options do not allow this; at least, I don’t think so. I’m very heavily leaning towards \ as the separator.

— Rob

I’m failing to understand why you’d think this would be related at all?

If we get generics,

class Box {

public function add(T $item) {}

}

would just work, without any use or such. There will not be a symbol Box::T or Box\T, just all mentions of T within the Box class will be taken as “this is the generic placeholder” and the compiler takes care.

It’s not like that T will be directly accessible from outside of the class or actually a proper type, unlike inner classes.

A generic is not an inner class nor will it look like it. Also, there’s no accessing of a parents generic - you write class Child extends ParentClass - or something along these lines, getting the T available for your class.

Bob

Yes, that is the question right? It might not affect anything there, but there would probably be an argument to keep it consistent with inner classes. In PHP, a class is a type; thus an inner class is an inner type, and generic types are also an inner type that only exist in the scope of their enclosing class, just like private inner classes.

If my logic is incorrect, let me know.

— Rob