rfc:short-and-inner-classes
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
rfc:short-and-inner-classes [2025/02/22 20:30] – reword some sections withinboredom | rfc:short-and-inner-classes [2025/03/15 10:28] (current) – reword and clarify withinboredom | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== PHP RFC: Inner Classes | + | ====== PHP RFC: Inner Classes ====== |
- | * Version: 0.1 | + | * Version: 0.4 |
* Date: 2025-02-08 | * Date: 2025-02-08 | ||
* Author: Rob Landers, < | * Author: Rob Landers, < | ||
- | * Status: | + | * Status: Under Discussion |
* First Published at: http:// | * First Published at: http:// | ||
===== Introduction ===== | ===== Introduction ===== | ||
- | PHP has steadily evolved | + | This RFC proposes a significant enhancement |
- | This RFC proposes two related enhancements | + | **Inner Classes** allows for the ability |
- | **Short class syntax**, allowing simple | + | PHP developers currently rely heavily on annotations (e.g., @internal) |
+ | |||
+ | This RFC introduces Inner Classes to clearly communicate and enforce boundaries around encapsulated classes, facilitating well-known design patterns (e.g., Builder, DTO, serialization patterns) with native language support. This reduces boilerplate, | ||
+ | |||
+ | ===== Proposal ===== | ||
+ | |||
+ | Inner classes | ||
<code php> | <code php> | ||
- | class Point(int $x, int $y); | + | class Outer { |
+ | public class Inner { | ||
+ | public function __construct(public string | ||
+ | } | ||
+ | |||
+ | private class PrivateInner { | ||
+ | public function __construct(public string $message) {} | ||
+ | } | ||
+ | } | ||
+ | |||
+ | $foo = new Outer:> | ||
+ | echo $foo-> | ||
+ | // outputs: Hello, world! | ||
+ | $baz = new Outer:> | ||
+ | // Fatal error: Class ' | ||
</ | </ | ||
- | This syntax acts as a shorthand for defining | + | Inner classes |
- | **Inner classes**, enabling the definition of classes | + | ==== Declaration Syntax ==== |
+ | |||
+ | Declaring an inner class is just like declaring any other class. You can define '' | ||
+ | |||
+ | If an inner class does not explicitly declare | ||
<code php> | <code php> | ||
- | class Foo { | + | class Outer { |
- | public class Bar(public | + | private class Inner {} |
+ | protected readonly class ReadOnlyInner {} | ||
+ | public | ||
+ | | ||
} | } | ||
</ | </ | ||
- | ===== Proposal ===== | + | If an inner class is not defined as '' |
- | ==== Short Class Syntax ==== | + | ==== Syntax |
- | The proposed | + | The '' |
- | <code php> | + | * Using '' |
+ | * Using '' | ||
- | // a simple | + | '' |
- | class Point(int $x, int $y); | + | |
- | // A readonly class with a parent class, interface, and traits | + | ==== Binding ==== |
- | readonly class Vector(int $x, int $y) extends BaseVector implements JsonSerializable use PointTrait, Evolvable; | + | |
- | </ | + | |
- | This is equivalent | + | Inner classes are explicitly bound to their outer classes and are intentionally not inherited through subclassing, |
<code php> | <code php> | ||
- | class Point { | + | class Outer { |
- | | + | |
+ | } | ||
+ | |||
+ | interface Foo { | ||
+ | class FooInner {} | ||
+ | } | ||
+ | |||
+ | trait Bar { | ||
+ | class BarInner | ||
} | } | ||
- | readonly public | + | class All extends |
- | use PointTrait, Evolvable; | + | use Bar; |
| | ||
- | public function | + | public function |
+ | new self:> | ||
+ | new self:> | ||
+ | new self:> | ||
+ | |||
+ | new parent:> | ||
+ | | ||
} | } | ||
</ | </ | ||
- | Properties inside parentheses are automatically declared as class properties and default | + | === static resolution === |
+ | |||
+ | The '' | ||
<code php> | <code php> | ||
- | // declare $shapes as a private property | + | class Outer { |
- | class Geometry(private | + | class Inner {} |
+ | |||
+ | | ||
+ | | ||
+ | |||
+ | // Parse error: syntax error, unexpected token ":>", | ||
+ | public function bar(): static:> | ||
+ | |||
+ | // Fatal error: Cannot | ||
+ | class Baz extends static:> | ||
+ | |||
+ | public function foobar() { | ||
+ | return new static:> | ||
+ | } | ||
+ | } | ||
</ | </ | ||
- | === Default Values === | + | This is to prevent casual LSP violations of inheritance and to maintain the strong binding of inner classes. |
- | Properties with type hints may have default values: | + | === parent resolution === |
+ | |||
+ | The '' | ||
<code php> | <code php> | ||
- | class Point(int $x = 0, int $y = 0); | + | class Foo { |
+ | class Bar {} | ||
+ | } | ||
+ | |||
+ | class Baz extends Foo { | ||
+ | // parent:> | ||
+ | class Bar extends parent:> | ||
+ | // inside the class body, parent refers to Foo:> | ||
+ | public function doSomething(): parent {} | ||
+ | } | ||
+ | } | ||
</ | </ | ||
- | === Inheritance | + | '' |
- | Short classes can extend other classes, implement interfaces, and use traits, but they cannot define additional methods. | + | === self resolution === |
+ | |||
+ | The '' | ||
<code php> | <code php> | ||
- | class Point(int $x, int $y) extends BasePoint implements JsonSerializable use PointTrait, Evolvable; | + | class Outer { |
+ | class Middle { | ||
+ | class Other {} | ||
+ | |||
+ | // extends Outer:> | ||
+ | class Inner extends self:> | ||
+ | public function foo(): self {} // returns Outer:> | ||
+ | } | ||
+ | } | ||
+ | } | ||
</ | </ | ||
- | === Empty Classes === | + | '' |
- | Short classes may be empty: | + | When using self inside a class body to refer to an inner class, if the inner class is not found in the current class, it will fail with an error. |
<code php> | <code php> | ||
- | class Point() extends BasePoint use PointTrait; | + | class OuterParent { |
+ | class Inner {} | ||
+ | class Other {} | ||
+ | } | ||
+ | |||
+ | class MiddleChild extends OuterParent { | ||
+ | // Fatal Error: cannot find class MiddleChild:> | ||
+ | class Inner extends self:> | ||
+ | } | ||
+ | |||
+ | class OuterChild extends OuterParent { | ||
+ | class Inner {} | ||
+ | public function foo() { | ||
+ | $inner = new self:> | ||
+ | $inner = new parent:> | ||
+ | $inner = new self:> | ||
+ | $inner = new parent:> | ||
+ | } | ||
+ | } | ||
</ | </ | ||
- | === Attributes | + | === Dynamic resolution |
- | Attributes can be used with short classes: | + | Just as with '' |
<code php> | <code php> | ||
- | # | + | // Using variables to dynamically instantiate inner classes: |
- | class Password(# | + | $outer = " |
+ | $inner = " | ||
+ | $instance = new $outer:> | ||
+ | |||
+ | // Instantiating inner class dynamically via a fully qualified class string: | ||
+ | $dynamicClassName = " | ||
+ | $instance = new $dynamicClassName(); | ||
</ | </ | ||
- | === Modifiers === | + | This provides flexibility and backwards compatibility for dynamic code that may not expect an inner class. |
- | Short classes | + | ==== Visibility from inner classes |
+ | |||
+ | Inner classes have access to their outer class’s private and protected methods, properties, and inner classes. However, they do not have access to their siblings’ private and protected methods, properties, and inner classes. | ||
<code php> | <code php> | ||
- | readonly | + | class User { |
+ | public private(set) string | ||
+ | public private(set) string $email; | ||
+ | |||
+ | private function __construct(self:> | ||
+ | $this-> | ||
+ | $this-> | ||
+ | } | ||
+ | |||
+ | public readonly final class Builder { | ||
+ | public function __construct(public private(set) string|null $name = null, public private(set) | ||
+ | |||
+ | public function withEmail(string $email): self { | ||
+ | return new self($this-> | ||
+ | } | ||
+ | |||
+ | public function withName(string $name): self { | ||
+ | return new self($name, $this-> | ||
+ | } | ||
+ | |||
+ | public function build(): User { | ||
+ | return new User($this); | ||
+ | } | ||
+ | } | ||
+ | } | ||
- | final class Config(string | + | $user = new User:> |
- | + | ||
- | abstract class Shape(float $area); | + | |
</ | </ | ||
- | === How it works === | + | This enables usages such as builder patterns |
- | + | ||
- | Short classes are purely syntactic sugar and compile into standard class definitions. | + | |
- | ==== Inner Classes | + | ==== Visibility from outside the outer class ==== |
- | Inner classes | + | Inner classes |
<code php> | <code php> | ||
class Outer { | class Outer { | ||
- | class Inner(public string $message); | + | |
- | | + | |
- | private | + | public function |
- | public function | + | $bar = new Outer:> |
+ | $bar = new Outer:> | ||
+ | | ||
} | } | ||
} | } | ||
- | $foo = new Outer::Inner(' | + | class SubOuter extends Outer { |
- | echo $foo-> | + | public function Foo() { |
- | // outputs: Hello, world! | + | |
- | $baz = new Outer:: | + | $bar = new parent:>Other(); // Cannot access private inner class 'Outer:> |
- | // Fatal error: Uncaught Error: | + | } |
+ | } | ||
</ | </ | ||
- | === Modifiers === | + | Attempting to instantiate a private or protected inner class outside its outer class will result in a fatal error: |
+ | |||
+ | <code php> | ||
+ | new Outer:> | ||
+ | </ | ||
- | Inner classes support modifiers such as '' | + | === Method return type and argument declarations === |
- | * '' | + | Inner classes may only be used as a return type or argument declarations for methods |
- | * '' | + | |
- | * '' | + | |
- | * '' | + | |
- | === Visibility | + | ^Inner Class Visibility^Method Visibility^Allowed^ |
+ | |'' | ||
+ | |'' | ||
+ | |'' | ||
+ | |'' | ||
+ | |'' | ||
+ | |'' | ||
+ | |'' | ||
+ | |'' | ||
+ | |'' | ||
- | Private | + | Methods |
<code php> | <code php> | ||
class Outer { | class Outer { | ||
- | private class PrivateInner(string $message); | + | private class Inner {} |
| | ||
- | public function getInner(): self:: | + | public function getInner(): self:> |
- | return new self:: | + | return new self:>Inner(); |
} | } | ||
} | } | ||
- | // using a private | + | // Fatal error: Uncaught TypeError: Public method getInner cannot return |
- | function doSomething(Outer:: | + | new Outer()-> |
- | echo $inner->message; | + | |
- | } | + | |
- | + | ||
- | // this is ok: | + | |
- | $inner = new Outer()-> | + | |
- | + | ||
- | // but this is not: | + | |
- | doSomething($inner); | + | |
- | // Fatal error: Private inner class Outer:: | + | |
</ | </ | ||
- | Just like with other languages that support inner classes, it is better to return an interface or a base class from a method instead of exposing a private/ | + | === Properties === |
- | You may also not instantiate | + | The visibility of a type declaration on a property must also not increase the visibility of an inner class. Thus, a public property cannot declare a private type. |
+ | |||
+ | This gives a great deal of control to developers, preventing accidental misuse of inner classes. However, this **does not** preclude developers from returning | ||
<code php> | <code php> | ||
- | $x = new Outer::PrivateInner(); | + | class Outer { |
- | // Fatal error: Uncaught Error: Cannot access private inner class Outer:: | + | private class Inner implements FooBar {} |
+ | |||
+ | public function getInner(): FooBar { | ||
+ | return new self:>Inner(); // not an error | ||
+ | } | ||
+ | } | ||
</ | </ | ||
- | === Inheritance | + | ==== Accessing outer classes ==== |
- | Inner classes have inheritance similar | + | Inner classes |
- | <code php> | + | Passing an outer instance explicitly remains an available option for accessing protected/ |
- | readonly | + | |
- | class Geometry | + | <code php> |
- | public | + | class Message |
- | protected | + | public function __construct(private string |
- | $this-> | + | |
- | | + | |
| | ||
- | | + | public function |
- | | + | |
- | | + | |
- | } | + | |
} | } | ||
| | ||
- | | + | |
- | public function __construct(int ...$coordinates) { | + | public string $message; |
- | $points | + | |
- | for ($i = 0; $i < count($coordinates); $i += 2) { | + | public function __construct(Message |
- | $points[] = new Point($coordinates[$i], | + | $this-> |
- | } | + | |
- | parent:: | + | |
} | } | ||
} | } | ||
} | } | ||
+ | </ | ||
- | class Triangle extends Geometry { | + | ==== Reflection ==== |
- | | + | |
- | | + | Several new methods are added to '' |
+ | |||
+ | <code php> | ||
+ | $reflection = new ReflectionClass(' | ||
+ | |||
+ | $reflection-> | ||
+ | $reflection-> | ||
+ | $reflection-> | ||
+ | $reflection-> | ||
+ | </ | ||
+ | |||
+ | For non-inner classes, '' | ||
+ | |||
+ | ==== Autoloading ==== | ||
+ | |||
+ | Inner classes are never autoloaded, only their outermost class is autoloaded. If the outermost class does not exist, then their inner classes do not exist. | ||
+ | |||
+ | ==== Usage ==== | ||
+ | |||
+ | Inner classes may be defined in the body of any class-like structure, including but not limited to: | ||
+ | |||
+ | * in a class body | ||
+ | * in an anonymous class body | ||
+ | * in an enum body | ||
+ | * in a trait body | ||
+ | * in an interface body | ||
+ | |||
+ | Note: While traits and interfaces may define inner classes, classes using these traits or implementing these interfaces do not inherit their inner classes. Inner classes remain strictly scoped and bound to their defining context only. | ||
+ | |||
+ | ==== Outer class effects ==== | ||
+ | |||
+ | Inner classes are fully independent of their outer class’s declaration modifiers. Outer class modifiers such as abstract, final, or readonly do not implicitly cascade down or affect the inner classes defined within them. | ||
+ | |||
+ | Specifically: | ||
+ | |||
+ | * An abstract outer class can define concrete (non-abstract) inner classes. Inner classes remain instantiable, | ||
+ | * A final outer class does not force its inner classes to be final. Inner classes within a final class can be extended internally, providing flexibility within encapsulation boundaries. | ||
+ | * A readonly outer class can define mutable inner classes. This supports internal flexibility, | ||
+ | |||
+ | Examples: | ||
+ | |||
+ | <code php> | ||
+ | abstract class Service { | ||
+ | // Valid: Inner class is not abstract, despite the outer class being abstract. | ||
+ | public class Implementation { | ||
+ | public function run(): void {} | ||
} | } | ||
- | | + | } |
- | | + | |
- | public function __construct(Point $p1, Point $p2, Point $p3) { | + | // Allowed; abstract outer class does not force inner classes to be abstract. |
- | | + | new Service:> |
- | } | + | </ |
+ | |||
+ | <code php> | ||
+ | readonly class ImmutableCollection | ||
+ | | ||
+ | |||
+ | | ||
+ | $this-> | ||
} | } | ||
- | | + | |
- | | + | public function |
- | | + | |
- | | + | |
- | } | + | |
} | } | ||
- | } | ||
- | $t = new Triangle:: | + | public class Builder { |
+ | private array $items; | ||
- | var_dump($t instanceof Triangle); // true | + | public function __construct(array $items) { |
- | var_dump($t instanceof Geometry); // true | + | $this-> |
- | var_dump($t instanceof Triangle:: | + | } |
+ | |||
+ | public function add(mixed $item): self { | ||
+ | $this-> | ||
+ | | ||
+ | } | ||
+ | |||
+ | public function build(): ImmutableCollection { | ||
+ | return new ImmutableCollection($this-> | ||
+ | } | ||
+ | } | ||
+ | } | ||
</ | </ | ||
- | However, no classes may not inherit from inner classes, but inner classes may inherit from other classes, including | + | In this example, even though ImmutableBuilder is a mutable |
- | === Names === | + | ==== Abstract inner classes ==== |
- | Inner classes | + | It is worth exploring what an '' |
+ | |||
+ | Abstract inner classes may not be instantiated, | ||
<code php> | <code php> | ||
- | class Foo { | + | class OuterParent |
- | | + | |
- | | + | |
- | + | ||
- | // Fatal error: Uncaught Error: Cannot redeclare Foo::Bar | + | |
} | } | ||
- | class Foo { | + | // Middle is allowed, does not have to implement InnerBase |
- | | + | class Middle extends OuterParent |
- | class Bar(public string $message); | + | |
- | + | // Last demonstrates extending the abstract inner class explicitly. | |
- | | + | class Last extends OuterParent { |
+ | | ||
} | } | ||
</ | </ | ||
Line 257: | Line 447: | ||
===== Backward Incompatible Changes ===== | ===== Backward Incompatible Changes ===== | ||
- | This RFC introduces new syntax and behavior to PHP, which does not conflict with existing syntax. | + | * This RFC introduces new syntax and behavior to PHP, which does not conflict with existing syntax. |
+ | * Some error messages will be updated to reflect inner classes, and tests that depend on these error messages are likely to fail. | ||
+ | * Tooling using AST or tokenization may need to be updated to support the new syntax. | ||
===== Proposed PHP Version(s) ===== | ===== Proposed PHP Version(s) ===== | ||
Line 271: | Line 463: | ||
==== To Existing Extensions ==== | ==== To Existing Extensions ==== | ||
- | Extensions accepting class names may need to be updated to support '' | + | Extensions accepting class names may need to be updated to support '' |
==== To Opcache ==== | ==== To Opcache ==== | ||
- | Most of the changes are in compilation and AST, so the impact | + | This change introduces a new opcode, |
+ | |||
+ | ==== To Tooling ==== | ||
+ | |||
+ | Tooling that uses AST or tokenization may need to be updated to support the new syntax, such as syntax highlighting, | ||
===== Open Issues ===== | ===== Open Issues ===== | ||
Line 283: | Line 479: | ||
===== Unaffected PHP Functionality ===== | ===== Unaffected PHP Functionality ===== | ||
- | There should be no change to existing PHP functionality. | + | There should be no change to any existing PHP syntax. |
===== Future Scope ===== | ===== Future Scope ===== | ||
- | * inner enums | + | * Inner enums |
+ | * Inner interfaces | ||
+ | * Inner traits | ||
+ | * '' | ||
===== Proposed Voting Choices ===== | ===== Proposed Voting Choices ===== | ||
+ | |||
+ | As this is a significant change to the language, a 2/3 majority is required. | ||
===== Patches and Tests ===== | ===== Patches and Tests ===== | ||
- | A complete implementation is available [[https:// | + | To be completed. |
===== Implementation ===== | ===== Implementation ===== | ||
Line 302: | Line 503: | ||
===== References ===== | ===== References ===== | ||
- | Links to external references, discussions or RFCs | + | * [[https:// |
+ | * [[https:// | ||
===== Rejected Features ===== | ===== Rejected Features ===== | ||
- | Keep this updated with features that were discussed on the mail lists. | + | TBD |
rfc/short-and-inner-classes.1740256208.txt.gz · Last modified: 2025/02/22 20:30 by withinboredom