[PHP-DEV] RFC: short and inner classes

Hello internals,

I have significantly revamped the RFC (again). Key changes to the RFC:

  1. More (realistic) examples,

  2. Since enums are basically specialized classes, they are allowed to be nested as well (hat tip to Reddit),

  3. Using backslash as the class separator,

  4. Proper scoping (and shadowing),

  5. Nesting is allowed in interfaces and enums as well as classes; but not traits,

  6. (Hopefully) Clearer wording,

  7. Nesting in traits, or nested traits, are future scope,

  8. Nested interfaces are future scope too.

Some benefits of using \ as a separator:

  • a simple name can refer to nested classes:

Scope resolution was expanded to treat inner classes within the same class as “in scope.” This provides a more natural usage:

class Outer {

class Inner {}

public function foo(Inner $inner) {}

}

  • Standard “use” statements can alias them:

use Outer\Inner as Inner;

But it also has some draw backs:

  • The engine doesn’t know that Outer\Inner is a nested class and autoloaders will have to account for that. It will only ask for Outer\Inner.

  • You cannot simply refer to parent:>Inner, you have to explicitly ask for the parent by name: SomeParentClass\Inner.

A draft implementation (which is more of a proof-of-concept) is available on GitHub.

On Mon, Mar 31, 2025, at 21:45, Rob Landers wrote:

Hello internals,

I have significantly revamped the RFC (again). Key changes to the RFC:

  1. More (realistic) examples,

  2. Since enums are basically specialized classes, they are allowed to be nested as well (hat tip to Reddit),

  3. Using backslash as the class separator,

  4. Proper scoping (and shadowing),

  5. Nesting is allowed in interfaces and enums as well as classes; but not traits,

  6. (Hopefully) Clearer wording,

  7. Nesting in traits, or nested traits, are future scope,

  8. Nested interfaces are future scope too.

Some benefits of using \ as a separator:

  • a simple name can refer to nested classes:

Scope resolution was expanded to treat inner classes within the same class as “in scope.” This provides a more natural usage:

class Outer {

class Inner {}

public function foo(Inner $inner) {}

}

  • Standard “use” statements can alias them:

use Outer\Inner as Inner;

But it also has some draw backs:

  • The engine doesn’t know that Outer\Inner is a nested class and autoloaders will have to account for that. It will only ask for Outer\Inner.

  • You cannot simply refer to parent:>Inner, you have to explicitly ask for the parent by name: SomeParentClass\Inner.

A draft implementation (which is more of a proof-of-concept) is available on GitHub.

I accidentally forgot to include the link to the RFC: https://wiki.php.net/rfc/short-and-inner-classes

— Rob

Hi Rob.
The examples in this RFC only describe usage within classes as far as I can see.
Does this finally solve the factory methods issue? https://externals.io/message/126452

пн, 31 мар. 2025 г. в 23:38, Rob Landers rob@bottled.codes:

On Mon, Mar 31, 2025, at 21:45, Rob Landers wrote:

Hello internals,

I have significantly revamped the RFC (again). Key changes to the RFC:

  1. More (realistic) examples,

  2. Since enums are basically specialized classes, they are allowed to be nested as well (hat tip to Reddit),

  3. Using backslash as the class separator,

  4. Proper scoping (and shadowing),

  5. Nesting is allowed in interfaces and enums as well as classes; but not traits,

  6. (Hopefully) Clearer wording,

  7. Nesting in traits, or nested traits, are future scope,

  8. Nested interfaces are future scope too.

Some benefits of using \ as a separator:

  • a simple name can refer to nested classes:

Scope resolution was expanded to treat inner classes within the same class as “in scope.” This provides a more natural usage:

class Outer {

class Inner {}

public function foo(Inner $inner) {}

}

  • Standard “use” statements can alias them:

use Outer\Inner as Inner;

But it also has some draw backs:

  • The engine doesn’t know that Outer\Inner is a nested class and autoloaders will have to account for that. It will only ask for Outer\Inner.

  • You cannot simply refer to parent:>Inner, you have to explicitly ask for the parent by name: SomeParentClass\Inner.

A draft implementation (which is more of a proof-of-concept) is available on GitHub.

I accidentally forgot to include the link to the RFC: https://wiki.php.net/rfc/short-and-inner-classes

— Rob

On Wed, Apr 2, 2025, at 07:54, Viktor Khramov wrote:

Hi Rob.

The examples in this RFC only describe usage within classes as far as I can see.

Does this finally solve the factory methods issue? https://externals.io/message/126452

пн, 31 мар. 2025 г. в 23:38, Rob Landers rob@bottled.codes:

On Mon, Mar 31, 2025, at 21:45, Rob Landers wrote:

Hello internals,

I have significantly revamped the RFC (again). Key changes to the RFC:

  1. More (realistic) examples,

  2. Since enums are basically specialized classes, they are allowed to be nested as well (hat tip to Reddit),

  3. Using backslash as the class separator,

  4. Proper scoping (and shadowing),

  5. Nesting is allowed in interfaces and enums as well as classes; but not traits,

  6. (Hopefully) Clearer wording,

  7. Nesting in traits, or nested traits, are future scope,

  8. Nested interfaces are future scope too.

Some benefits of using \ as a separator:

  • a simple name can refer to nested classes:

Scope resolution was expanded to treat inner classes within the same class as “in scope.” This provides a more natural usage:

class Outer {

class Inner {}

public function foo(Inner $inner) {}

}

  • Standard “use” statements can alias them:

use Outer\Inner as Inner;

But it also has some draw backs:

  • The engine doesn’t know that Outer\Inner is a nested class and autoloaders will have to account for that. It will only ask for Outer\Inner.

  • You cannot simply refer to parent:>Inner, you have to explicitly ask for the parent by name: SomeParentClass\Inner.

A draft implementation (which is more of a proof-of-concept) is available on GitHub.

I accidentally forgot to include the link to the RFC: https://wiki.php.net/rfc/short-and-inner-classes

— Rob

Ah, good catch Viktor,

I added a factory pattern as an example.

— Rob

On Mon, Mar 31, 2025, at 21:45, Rob Landers wrote:

Hello internals,

I have significantly revamped the RFC (again). Key changes to the RFC:

  1. More (realistic) examples,
  2. Since enums are basically specialized classes, they are allowed to be nested as well (hat tip to Reddit),
  3. Using backslash as the class separator,
  4. Proper scoping (and shadowing),
  5. Nesting is allowed in interfaces and enums as well as classes; but not traits,
  6. (Hopefully) Clearer wording,
  7. Nesting in traits, or nested traits, are future scope,
  8. Nested interfaces are future scope too.

Some benefits of using \ as a separator:

  • a simple name can refer to nested classes:

Scope resolution was expanded to treat inner classes within the same class as “in scope.” This provides a more natural usage:

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

  • Standard “use” statements can alias them:

use Outer\Inner as Inner;

But it also has some draw backs:

  • The engine doesn’t know that Outer\Inner is a nested class and autoloaders will have to account for that. It will only ask for Outer\Inner.

  • You cannot simply refer to parent:>Inner, you have to explicitly ask for the parent by name: SomeParentClass\Inner.

A draft implementation (which is more of a proof-of-concept) is available on GitHub.

Hello internals,

As it seems that discussion has mostly died down, I’d like to put this towards a vote starting on May 1, 2025.

— Rob

On Sun, Apr 20, 2025 at 7:46 AM Rob Landers <rob@bottled.codes> wrote:

On Mon, Mar 31, 2025, at 21:45, Rob Landers wrote:

Hello internals,

I have significantly revamped the RFC (again). Key changes to the RFC:

1. More (realistic) examples,
2. Since enums are basically specialized classes, they are allowed to be nested as well (hat tip to Reddit),
3. Using backslash as the class separator,
4. Proper scoping (and shadowing),
5. Nesting is allowed in interfaces and enums as well as classes; but not traits,
6. (Hopefully) Clearer wording,
7. Nesting in traits, or nested traits, are future scope,
8. Nested interfaces are future scope too.

Some benefits of using \ as a separator:

- a simple name can refer to nested classes:

Scope resolution was expanded to treat inner classes within the same class as “in scope.” This provides a more natural usage:

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

- Standard “use” statements can alias them:

use Outer\Inner as Inner;

But it also has some draw backs:

- The engine doesn’t know that Outer\Inner is a nested class and autoloaders will have to account for that. It will only ask for Outer\Inner.

- You cannot simply refer to parent:>Inner, you have to explicitly ask for the parent by name: SomeParentClass\Inner.

A draft implementation (which is more of a proof-of-concept) is available on GitHub.

Hello internals,

As it seems that discussion has mostly died down, I'd like to put this towards a vote starting on May 1, 2025.

— Rob

I intend to vote no. Fundamentally, this proposal adds a form of
"packaging" which only affects classes. There's no packaging for
constants or functions, unless you put them onto a class to make them
class constants and static methods. This is weird to me. I would
prefer that we work out something more general.

I am also worried that naming collisions are possible. Something like this:

namespace A {
     class B {}
}

namespace {
    class A {
        class B {}
    }
}

Where `A\B` refers to two possible things. I don't like this, and to
my knowledge, this type of confusion has not been possible in the
past. Of course, feel free to point it out if so. Note that methods
and properties of the same name are disambiguated by syntax, where
here the syntax is the same.

On Tue, Apr 22, 2025, at 19:22, Levi Morrison wrote:

On Sun, Apr 20, 2025 at 7:46 AM Rob Landers <rob@bottled.codes> wrote:

On Mon, Mar 31, 2025, at 21:45, Rob Landers wrote:

Hello internals,

I have significantly revamped the RFC (again). Key changes to the RFC:

  1. More (realistic) examples,
  2. Since enums are basically specialized classes, they are allowed to be nested as well (hat tip to Reddit),
  3. Using backslash as the class separator,
  4. Proper scoping (and shadowing),
  5. Nesting is allowed in interfaces and enums as well as classes; but not traits,
  6. (Hopefully) Clearer wording,
  7. Nesting in traits, or nested traits, are future scope,
  8. Nested interfaces are future scope too.

Some benefits of using \ as a separator:

  • a simple name can refer to nested classes:

Scope resolution was expanded to treat inner classes within the same class as “in scope.” This provides a more natural usage:

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

  • Standard “use” statements can alias them:

use Outer\Inner as Inner;

But it also has some draw backs:

  • The engine doesn’t know that Outer\Inner is a nested class and autoloaders will have to account for that. It will only ask for Outer\Inner.

  • You cannot simply refer to parent:>Inner, you have to explicitly ask for the parent by name: SomeParentClass\Inner.

A draft implementation (which is more of a proof-of-concept) is available on GitHub.

Hello internals,

As it seems that discussion has mostly died down, I’d like to put this towards a vote starting on May 1, 2025.

— Rob

I intend to vote no. Fundamentally, this proposal adds a form of
“packaging” which only affects classes. There’s no packaging for
constants or functions, unless you put them onto a class to make them
class constants and static methods. This is weird to me. I would
prefer that we work out something more general.

Thank you for your feedback! It very much isn’t packaging, this is closer to “friend” classes in C++ or nested classes in other languages (C#, Kotlin, Swift, Java, etc). I do intend to focus on packaging (in-general) in the near future though.

I am also worried that naming collisions are possible. Something like this:

namespace A {
class B {}
}

namespace {
class A {
class B {}
}
}

Where A\B refers to two possible things. I don’t like this, and to
my knowledge, this type of confusion has not been possible in the
past. Of course, feel free to point it out if so. Note that methods
and properties of the same name are disambiguated by syntax, where
here the syntax is the same.

I should update the RFC to include this case, thanks for pointing this out. This would cause the ole’ “Cannot redeclare class A\B” error. There is not any ambiguity here.

— Rob

Hi

On 4/20/25 15:43, Rob Landers wrote:

As it seems that discussion has mostly died down, I'd like to put this towards a vote starting on May 1, 2025.

Unfortunately I did not have the time to follow the discussion after mid-March, so this might or might not have been discussed already. I just skimmed the RFC and a big issue from my PoV is the interaction of private nested classes and the shared namespace.

All existing private symbols in PHP are “invisible” or “non-existent” from the outside for all intents and purposes. As an example, it is possible to add a new private method to a class, without having an effect on child classes that already defined a method with the same name.

To my understanding this is different with private nested classes. A private nested class will “block the name” in the global class table, leading to naming conflicts with something the user shouldn't even know exists (because it's private).

This is a reason for me to vote against the RFC and a reason why I preferred Ilija's “file private” classes that are much simpler to reason about.

Best regards
Tim Düsterhus

On Thu, Apr 24, 2025, at 16:31, Tim Düsterhus wrote:

Hi

On 4/20/25 15:43, Rob Landers wrote:

As it seems that discussion has mostly died down, I’d like to put this towards a vote starting on May 1, 2025.

Unfortunately I did not have the time to follow the discussion after
mid-March, so this might or might not have been discussed already. I
just skimmed the RFC and a big issue from my PoV is the interaction of
private nested classes and the shared namespace.

All existing private symbols in PHP are “invisible” or “non-existent”
from the outside for all intents and purposes. As an example, it is
possible to add a new private method to a class, without having an
effect on child classes that already defined a method with the same name.

To my understanding this is different with private nested classes. A
private nested class will “block the name” in the global class table,
leading to naming conflicts with something the user shouldn’t even know
exists (because it’s private).

This is a reason for me to vote against the RFC and a reason why I
preferred Ilija’s “file private” classes that are much simpler to reason
about.

Best regards
Tim Düsterhus

Thank you for your feedback! I think you would then have the problem that was pointed out by Levi the other day; where you would then have ambiguity. If you could have both private and public names in the same namespace, then you would end up not knowing which one was being referred to. Also, it is worth pointing out that private symbols are not “invisible” or “non-existent” from outside classes. They emit their own errors: https://3v4l.org/PEGeA that indicate you tried to access something you shouldn’t be able to. This is different than when you try to access something that actually doesn’t exist: https://3v4l.org/nWVPV

— Rob

Hi

On 4/24/25 17:09, Rob Landers wrote:

Thank you for your feedback! I think you would then have the problem that was pointed out by Levi the other day; where you would then have ambiguity. If you could have both private and public names in the same namespace, then you would end up not knowing which one was being referred to.

That is a design error in the RFC then.

Also, it is worth pointing out that private symbols are *not* "invisible" or "non-existent" from outside classes. They emit their own errors: Online PHP editor | output for PEGeA that indicate you tried to access something you shouldn't be able to. This is different than when you try to access something that actually doesn't exist: Online PHP editor | output for nWVPV

Fair enough about the error message. You can also access them with Reflection or the Closure hack. They are nevertheless ”non-existent” from the public API PoV: Online PHP editor | output for Us6VV

     <?php

     class Foo {
  private string $bar = "foo";
  private function foo(): array {}
     }

     class Bar extends Foo {
         private array $bar = ;

         private function foo($foo): int {}
     }

     class Baz extends Bar {
         public self $bar;
     }

     $baz = new Baz();
     $baz->bar = $baz;

     var_dump($baz);

In subclasses I can “redefine” private properties or methods and they do not interact at all. Properties have separate storage locations and can have differing types. Methods do not need to have compatible signatures either.

Best regards
Tim Düsterhus

On Thu, Apr 24, 2025, at 17:20, Tim Düsterhus wrote:

Hi

On 4/24/25 17:09, Rob Landers wrote:

Thank you for your feedback! I think you would then have the problem that was pointed out by Levi the other day; where you would then have ambiguity. If you could have both private and public names in the same namespace, then you would end up not knowing which one was being referred to.

That is a design error in the RFC then.

This was very deliberate after much feedback and careful design. People were quite clear (including yourself, if I recall) that they didn’t want a new syntax. Since there is no new syntax, there is no way to tell (from the outside) whether A\B\C refers to a nested class or an outer class. That’s by design and mirrors other languages and how they handle nested classes.

Also, it is worth pointing out that private symbols are not “invisible” or “non-existent” from outside classes. They emit their own errors: https://3v4l.org/PEGeA that indicate you tried to access something you shouldn’t be able to. This is different than when you try to access something that actually doesn’t exist: https://3v4l.org/nWVPV

Fair enough about the error message. You can also access them with
Reflection or the Closure hack. They are nevertheless ”non-existent”
from the public API PoV: https://3v4l.org/Us6VV

<?php class Foo { private string $bar = "foo"; private function foo(): array {} } class Bar extends Foo { private array $bar = []; private function foo($foo): int {} } class Baz extends Bar { public self $bar; } $baz = new Baz(); $baz->bar = $baz; var_dump($baz); In subclasses I can “redefine” private properties or methods and they do not interact at all. Properties have separate storage locations and can have differing types. Methods do not need to have compatible signatures either. Best regards Tim Düsterhus

I’m not sure what you are saying here. I mean, what you are saying is self-evident but I don’t understand how it applies to nested classes. You can write the following without any issues:

class Foo {
private class Bar {}
}

class Bar extends Foo {
private class Bar extends Foo {}
}

And it will work just fine. You could even make the nested classes public if you want to, or one of them private and one of them public. As mentioned in the RFC, the nested class is bound to the lexical scope of the class it is written in. So Foo\Bar and Bar\Bar are different classes altogether in the example above.

— Rob

Hi

On 4/24/25 21:26, Rob Landers wrote:

This was very deliberate after much feedback and careful design. People were quite clear (including yourself, if I recall) that they didn't want a new syntax. Since there is no new syntax, there is no way to tell (from the outside) whether A\B\C refers to a nested class or an outer class. That's by design and mirrors other languages and how they handle nested classes.

To clarify: I was not against new syntax per se. I was against “invented syntax”. Double-backslash would have been new syntax, but reasonably closely resemble the existing namespace separator. `::` would have also worked for me, since the naming conflicts would have been limited to the class itself.

If I am not mistaken about Java (it's been a while since I did Java, and never professionally), there are two things that would prevent the naming conflict there:

1. Package names are lowercase by convention. Thus if you have two components with an uppercase first letter, then it's a nested class.
2. In the JVM bytecode, the names of nested classes are mangled to incorporate the parent class' name.

(2) is also what is happening for private properties in PHP and that is what prevents the conflicts.

I'm not sure what you are saying here. I mean, what you are saying is self-evident but I don't understand how it applies to nested classes. You can write the following without any issues:

class Foo {
   private class Bar {}
}

class Bar extends Foo {
   private class Bar extends Foo {}
}

And it will work just fine. You could even make the nested classes public if you want to, or one of them private and one of them public. As mentioned in the RFC, the nested class is bound to the lexical scope of the class it is written in. So Foo\Bar and Bar\Bar are different classes altogether in the example above.

I'm saying that I cannot add a private class Foo\Bar inside of the class Foo without checking whether a class Bar inside a namespace Foo already exists, since both would conflict. Even more problematic: I can't add a class Bar inside of namespace Foo, without needing to check the source code of class Foo to determine whether there is a private class Bar that I have no business of knowing that it exists.

This is also not some hypothetical problem. I've more than once created a namespace that matches a class name of the “parent namespace”. The PHP core Random\Engine interface + the Random\Engine\* namespace would be one example. Laminas Diactoros also has Laminas\Diactoros\Request as a class and Laminas\Diactoros\Request\* as a namespace.

And when I'm forced to always verify whether my private classes conflict with something, then nested classes do not provide a measurable value-add over a “regular class” in a namespace matching the parent class' class name that is marked `@internal`.

Best regards
Tim Düsterhus

On 29 April 2025 19:50:52 BST, "Tim Düsterhus" <tim@bastelstu.be> wrote:

I'm saying that I cannot add a private class Foo\Bar inside of the class Foo without checking whether a class Bar inside a namespace Foo already exists, since both would conflict. Even more problematic: I can't add a class Bar inside of namespace Foo, without needing to check the source code of class Foo to determine whether there is a private class Bar that I have no business of knowing that it exists.

I think you are insisting on a different definition of "private" for nested classes than exists anywhere else in the language, and one that I've not seen evidence of in any other similar language either. It seems you want members to be "hidden", such that you can't even know they exist.

More specifically, you want their *fully-qualified name* to somehow be available for reuse. That's not how private members of a class work - methods A::foo() and B::foo() have different fully-qualified names, as do properties A::$foo and B::$foo, regardless of visibility and inheritance. It's not how "namespace private", "module private", or "file private" classes would naturally work, because they all would need to be referenceable with fully-qualified names within the scope where they were visible.

It's also not a new problem: PHP doesn't enforce a file and directory layout, and libraries can and do define things "inside" each other's namespaces. When declaring a class, you have to be aware of whether a class with the same fully-qualified name has been, or will be, declared in another file.

There is a challenge in any feature that encourages multiple definitions in one file, that new conventions are needed to know at a glance when that is in use, because we have become accustomed to the (Java-inspired?) one-class-per-file convention. That's not about *hiding* things, though, because those multiple definitions might be public; nor is it unique to this proposal - it comes up with function autoloading, typedefs, and many more.

And when I'm forced to always verify whether my private classes conflict with something, then nested classes do not provide a measurable value-add over a “regular class” in a namespace matching the parent class' class name that is marked `@internal`.

The value-add is 1) that they enforce the visibility at the language level, and 2) that they allow a concise syntax for declaring a set of closely related classes.

I'm not wholly convinced that they do that better than "file private" or "module private", but I don't agree that "class hiding" is essential, or even particularly desirable.

Rowan Tommins
[IMSoP]

Hi

On 4/30/25 12:51, Rowan Tommins [IMSoP] wrote:

I think you are insisting on a different definition of "private" for nested classes than exists anywhere else in the language, and one that I've not seen evidence of in any other similar language either. It seems you want members to be "hidden", such that you can't even know they exist.

No, I don't think I'm using a different definition of "private" for nested classes.

More specifically, you want their *fully-qualified name* to somehow be available for reuse. That's not how private members of a class work - methods A::foo() and B::foo() have different fully-qualified names, as do properties A::$foo and B::$foo, regardless of visibility and inheritance. It's not how "namespace private", "module private", or "file private" classes would naturally work, because they all would need to be referenceable with fully-qualified names within the scope where they were visible.

The difference is that for private class members there is no chance of name collisions of the "fully-qualified" name, but there is for nested classes as proposed in this RFC. There is no way for anyone except for the author of the class `A` to create a property with the fully qualified name `A::$foo`. Granted, properties could be inherited from a parent class and might thus cause collisions with a private property in a subclass. However the same collision would happen if the child class would already have a public property of the same name. Importantly, the child class could just rename its private property without any effect on any other class and also parent classes can introduce private properties without any effect on any other class.

This is difference for nested classes as proposed in this RFC, since the namespace of nested classes is shared with the namespace of regular classes and the namespace of regular classes is not “closed off” for addition after the initial declaration.

It's also not a new problem: PHP doesn't enforce a file and directory layout, and libraries can and do define things "inside" each other's namespaces. When declaring a class, you have to be aware of whether a class with the same fully-qualified name has been, or will be, declared in another file.

This is correct, but all these other classes that could conflict are part of the *public API* of a library. But now with private nested classes I would also be aware of the private part of a library to avoid conflicts.

Best regards
Tim Düsterhus

On 4 May 2025 14:52:23 BST, "Tim Düsterhus" <tim@bastelstu.be> wrote:

It's also not a new problem: PHP doesn't enforce a file and directory layout, and libraries can and do define things "inside" each other's namespaces. When declaring a class, you have to be aware of whether a class with the same fully-qualified name has been, or will be, declared in another file.

This is correct, but all these other classes that could conflict are part of the *public API* of a library. But now with private nested classes I would also be aware of the private part of a library to avoid conflicts.

The classes that you'll need to be aware of will exist whether this feature is added or not, and you'll already need to avoid conflicting with them - usually by simply avoiding the main namespace prefix of the library.

Probably they will be currently marked "@internal" or highlighted in documentation in some way, to indicate that they are not intended as part of the public API; the only difference will be that now you'll get an error if you ignore that documentation.

Rowan Tommins
[IMSoP]

On Sun, May 4, 2025, at 15:52, Tim Düsterhus wrote:

Hi

On 4/30/25 12:51, Rowan Tommins [IMSoP] wrote:

I think you are insisting on a different definition of “private” for nested classes than exists anywhere else in the language, and one that I’ve not seen evidence of in any other similar language either. It seems you want members to be “hidden”, such that you can’t even know they exist.

No, I don’t think I’m using a different definition of “private” for
nested classes.

More specifically, you want their fully-qualified name to somehow be available for reuse. That’s not how private members of a class work - methods A::foo() and B::foo() have different fully-qualified names, as do properties A::$foo and B::$foo, regardless of visibility and inheritance. It’s not how “namespace private”, “module private”, or “file private” classes would naturally work, because they all would need to be referenceable with fully-qualified names within the scope where they were visible.
The difference is that for private class members there is no chance of
name collisions of the “fully-qualified” name, but there is for nested
classes as proposed in this RFC. There is no way for anyone except for
the author of the class A to create a property with the fully
qualified name A::$foo. Granted, properties could be inherited from a
parent class and might thus cause collisions with a private property in
a subclass. However the same collision would happen if the child class
would already have a public property of the same name. Importantly, the
child class could just rename its private property without any effect on
any other class and also parent classes can introduce private properties
without any effect on any other class.

This is difference for nested classes as proposed in this RFC, since the
namespace of nested classes is shared with the namespace of regular
classes and the namespace of regular classes is not “closed off” for
addition after the initial declaration.

It’s also not a new problem: PHP doesn’t enforce a file and directory layout, and libraries can and do define things “inside” each other’s namespaces. When declaring a class, you have to be aware of whether a class with the same fully-qualified name has been, or will be, declared in another file.

This is correct, but all these other classes that could conflict are
part of the public API of a library. But now with private nested
classes I would also be aware of the private part of a library to avoid
conflicts.

Best regards
Tim Düsterhus

Hi Tim,

I think these are fundamental problems (if they are a problem at all) with how PHP currently does namespaces and names. I’m curious if you would vote “no” for namespace visibility as well? While not intended to be used as “modules,” this has many of the same constraints and effects that full namespace visibility would have—including the same problems you allude to here.

As I was going to work on short syntax classes next if this one passed, as well as namespace visibility/modules after that (regardless of this RFC passing), I’d really like to know if this behavior is that big of an issue. If so, it is probably not worth pursuing namespace visibility if this RFC fails.

For everyone else who voted “no”; I would sincerely love to know the reason. Even if you just email me directly; it would really help me understand what can be improved in any future RFCs.

— Rob

Hi

Am 2025-05-06 21:33, schrieb Rowan Tommins [IMSoP]:

The classes that you'll need to be aware of will exist whether this feature is added or not, and you'll already need to avoid conflicting with them - usually by simply avoiding the main namespace prefix of the library.

If this feature was designed like all the other implementations of `private`, I would not need to be aware of the other classes, since PHP would name-mangle the private symbol for me to ensure it doesn't conflict. In the simplest case it could compile

     class Foo {
         private class Bar { }
     }

As:

     class Foo {

     }

     class Foo$Bar {

     }

And rewrite all references inside of `Foo` to `Foo$Bar` (using Java's name mangling). This is effectively what Ilija's proposal for file-private classes did: Short class syntax and inner classes - Externals. I think this would also be nicer on the autoloading impact. Since a private class is not usable outside of its container class, it doesn't make sense to attempt to autoload it individually, since to reference it, the container class must already be loaded (which means the private class is also already loaded).

Best regards
Tim Düsterhus

Hi

Am 2025-05-06 22:04, schrieb Rob Landers:

I think these are fundamental problems (if they are a problem at all) with how PHP currently does namespaces and names.

I don't think that this is a fundamental problem of namespaces and names. Ilija solved the naming conflict issue in his file-private class proposal.

I'm curious if you would vote "no" for namespace visibility as well?

I cannot answer this question. PHP doesn't have namespace visibility and whether or not I would be in favor of a proposal depends greatly on the proposed semantics. In the current nested classes RFC, the proposed semantics for `private` classes are inconsistent with how `private` in PHP currently behaves, which makes it unacceptable to me.

For everyone else who voted "no"; I would sincerely love to know the reason. Even if you just email me directly; it would really help me understand what can be improved in any future RFCs.

I unfortunately didn't have the time to follow the discussion and think about the RFC in depth. Besides the `private` semantics, the autoloading impact is also a no for me, since to be able to properly use nested classes, a composer update would be required, so that composer’s autoloading implementation becomes aware of nested classes to properly load them. This means that if a package uses nested classes, it will *silently* be broken until the *consumer* updates their composer installation. I don't think it is currently possible to define a minimum composer version as part of a package’s dependencies.

Best regards
TIm Düsterhus

On 07/05/2025 14:18, Tim Düsterhus wrote:

I don't think it is currently possible to define a minimum composer version as part of a package’s dependencies.

There are three "platform dependency" pseudo-packages available for packages to depend on different aspects of Composer's version: Composer platform dependencies - Composer If these didn't seem suitable, they could add another, like "composer-autoloader".

I think "autoloading tools and conventions would need to adapt" is definitely a cost to be weighed against the benefit, but not a blocker in itself.

--
Rowan Tommins
[IMSoP]

On 07/05/2025 14:10, Tim Düsterhus wrote:

And rewrite all references inside of `Foo` to `Foo$Bar` (using Java's name mangling). This is effectively what Ilija's proposal for file-private classes did: Short class syntax and inner classes - Externals. I think this would also be nicer on the autoloading impact. Since a private class is not usable outside of its container class, it doesn't make sense to attempt to autoload it individually, since to reference it, the container class must already be loaded (which means the private class is also already loaded).

I agree that this would be possible; I disagree that it is essential, or that it has anything to do with consistency with the rest of the language.

I'm not even sure if it would be a good idea - it would mean having what appears to be a fully-qualified name actually "shadowed" by a completely unrelated definition within a particular scope. For example "\Foo\Bar" would still refer to "namespace Foo, class Bar" in most of the program, but "class Foo, nested class Bar" in the context of class Foo. If the visibility was changed to "public", the shadowing would instantly become a conflict anyway; and I can't picture how "protected" would work.

--
Rowan Tommins
[IMSoP]