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/03/06 21:30] – fix wording 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, < | ||
Line 9: | Line 9: | ||
===== Introduction ===== | ===== Introduction ===== | ||
- | This RFC proposes a significant enhancement to the language: **Inner Classes**. | + | This RFC proposes a significant enhancement to the language: **Inner Classes**. |
- | Inner Classes | + | **Inner Classes** allows for the ability to define |
- | By adopting these enhancements, | + | PHP developers |
+ | |||
+ | This RFC introduces Inner Classes to clearly communicate | ||
===== Proposal ===== | ===== Proposal ===== | ||
- | Inner classes allow defining classes within other classes, following standard visibility rules. This allows developers to declare a class as '' | + | Inner classes allow defining classes within other classes, following standard visibility rules. This allows developers to declare a class as '' |
<code php> | <code php> | ||
Line 30: | Line 32: | ||
} | } | ||
- | $foo = new Outer::Inner(' | + | $foo = new Outer:>Inner(' |
echo $foo-> | echo $foo-> | ||
// outputs: Hello, world! | // outputs: Hello, world! | ||
- | $baz = new Outer::PrivateInner(' | + | $baz = new Outer:>PrivateInner(' |
- | // Fatal error: | + | // Fatal error: |
</ | </ | ||
- | === Modifiers === | + | Inner classes are just regular class definitions with a couple additional features. That means, except where otherwise noted, "how would inner classes work in situation X?" can be answered with "the same as any other class/ |
- | Inner classes support modifiers such as '' | + | ==== Declaration Syntax ==== |
- | * '' | + | Declaring an inner class is just like declaring any other class. You can define |
- | * '' | + | |
- | * '' | + | |
- | * '' | + | |
- | === Visibility Rules === | + | If an inner class does not explicitly declare visibility (private, protected, or public), it is implicitly considered public. This matches PHP’s existing class behavior for methods and properties. |
- | Private and protected | + | <code php> |
+ | class Outer { | ||
+ | private class Inner {} | ||
+ | | ||
+ | public abstract class AbstractInner {} | ||
+ | public final class FinalInner {} | ||
+ | } | ||
+ | </code> | ||
- | For example, you may return a private inner class from any method inside that same inner class or the outer class, but you may not use it as a type declaration in a function outside | + | If an inner class is not defined as '' |
+ | |||
+ | ==== Syntax rationale ==== | ||
+ | |||
+ | The '' | ||
+ | |||
+ | * Using '' | ||
+ | * Using '' | ||
+ | |||
+ | '' | ||
+ | |||
+ | ==== Binding ==== | ||
+ | |||
+ | Inner classes are explicitly bound to their outer classes and are intentionally not inherited through subclassing, | ||
<code php> | <code php> | ||
- | class Box { | + | class Outer { |
- | | + | class OuterInner |
- | public function __construct(public int $x, public int $y) {} | + | |
- | } | + | |
- | + | ||
- | private self::Point $center; | + | |
- | + | ||
- | public function __construct() { | + | |
- | $this-> | + | |
- | } | + | |
- | + | ||
- | public function getCenter(): | + | |
- | return $this-> | + | |
- | } | + | |
- | + | ||
- | public function setCenter(self:: | + | |
- | $this-> | + | |
- | | + | |
} | } | ||
- | $box = new Box(); | + | interface Foo { |
- | $center = $box->getCenter(); | + | class FooInner {} |
- | $center->x = 10; | + | } |
- | $box->setCenter($center); | + | |
- | var_dump($box); | + | trait Bar { |
+ | class BarInner {} | ||
+ | } | ||
+ | |||
+ | class All extends Outer implements Foo { | ||
+ | use Bar; | ||
+ | |||
+ | public function does_not_work() { | ||
+ | new self:>OuterInner(); // Fatal error: Class ' | ||
+ | new self:>FooInner(); // Fatal error: Class ' | ||
+ | new self:>BarInner(); // Fatal error: Class ' | ||
+ | |||
+ | new parent:> | ||
+ | } | ||
+ | } | ||
</ | </ | ||
- | Outputs: | + | === static resolution === |
- | < | + | The '' |
- | object(Box)#1 (1) { | + | |
- | | + | < |
- | | + | class Outer { |
- | | + | class Inner {} |
- | | + | |
- | | + | // Fatal error: Cannot use the static modifier on a parameter |
- | int(0) | + | public function foo(static:> |
- | } | + | |
+ | // Parse error: syntax error, unexpected token ":>", expecting ";" | ||
+ | | ||
+ | | ||
+ | // Fatal error: Cannot use "static" | ||
+ | class Baz extends static:>Inner {} | ||
+ | | ||
+ | | ||
+ | | ||
+ | } | ||
} | } | ||
</ | </ | ||
- | However, if we try to use it outside | + | This is to prevent casual LSP violations of inheritance and to maintain the strong binding of inner classes. |
+ | |||
+ | === parent resolution === | ||
+ | |||
+ | The '' | ||
<code php> | <code php> | ||
- | function mutateBox(Box:: | + | class Foo { |
- | | + | |
} | } | ||
- | $center = mutateBox($center); | + | class Baz extends Foo { |
+ | // parent:> | ||
+ | class Bar extends parent:> | ||
+ | // inside the class body, parent refers to Foo:> | ||
+ | public function doSomething(): parent {} | ||
+ | } | ||
+ | } | ||
</ | </ | ||
- | We receive | + | '' |
- | < | + | === self resolution === |
- | PHP Fatal error: | + | |
+ | The '' | ||
+ | |||
+ | < | ||
+ | class Outer { | ||
+ | | ||
+ | class Other {} | ||
+ | |||
+ | // extends Outer:>Middle:>Other | ||
+ | class Inner extends self:> | ||
+ | public function foo(): self {} // returns Outer:> | ||
+ | } | ||
+ | } | ||
+ | } | ||
</ | </ | ||
- | This gives a great deal of control | + | '' |
- | === Inheritance === | + | 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. |
- | Inner classes have inheritance similar | + | <code php> |
+ | 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:> | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | === Dynamic resolution === | ||
+ | |||
+ | Just as with '' | ||
<code php> | <code php> | ||
- | readonly class Point(int | + | // Using variables to dynamically instantiate inner classes: |
+ | $outer = " | ||
+ | $inner = " | ||
+ | $instance = new $outer:> | ||
- | class Geometry | + | // Instantiating inner class dynamically via a fully qualified class string: |
- | public | + | $dynamicClassName = " |
- | | + | $instance = new $dynamicClassName(); |
- | $this->points | + | </ |
+ | |||
+ | This provides flexibility and backwards compatibility for dynamic code that may not expect an inner class. | ||
+ | |||
+ | ==== 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> | ||
+ | class User { | ||
+ | public | ||
+ | | ||
+ | |||
+ | private | ||
+ | $this->name = $builder-> | ||
+ | $this-> | ||
} | } | ||
| | ||
- | public class FromPoints extends Geometry | + | public |
- | public function __construct(Point ...$points) { | + | public function __construct(public private(set) string|null |
- | | + | |
+ | public function withEmail(string $email): self { | ||
+ | | ||
+ | } | ||
+ | |||
+ | public function withName(string $name): self { | ||
+ | return new self($name, $this-> | ||
+ | } | ||
+ | |||
+ | public function build(): User { | ||
+ | return new User($this); | ||
} | } | ||
} | } | ||
- | | + | } |
- | public class FromCoordinates extends Geometry | + | |
- | public function | + | $user = new User:> |
- | $points | + | </ |
- | for ($i = 0; $i < count($coordinates); $i += 2) { | + | |
- | $points[] | + | This enables usages such as builder patterns and other helper classes to succinctly encapsulate behavior. |
- | } | + | |
- | parent:: | + | ==== Visibility from outside the outer class ==== |
+ | |||
+ | Inner classes are not visible outside their outer class unless they are public. Protected classes may be used and accessed from within their child classes. | ||
+ | |||
+ | <code php> | ||
+ | class Outer { | ||
+ | private class Other {} | ||
+ | protected | ||
+ | public function | ||
+ | $bar = new Outer:> | ||
+ | $bar = new Outer:>Other(); // allowed | ||
} | } | ||
} | } | ||
} | } | ||
- | class Triangle | + | class SubOuter |
- | | + | |
- | parent:: | + | |
+ | | ||
} | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Attempting to instantiate a private or protected inner class outside its outer class will result in a fatal error: | ||
+ | |||
+ | <code php> | ||
+ | new Outer:> | ||
+ | </ | ||
+ | |||
+ | === Method return type and argument declarations === | ||
+ | |||
+ | Inner classes may only be used as a return type or argument declarations for methods and functions that have the same visibility or lesser. Thus returning a '' | ||
+ | |||
+ | ^Inner Class Visibility^Method Visibility^Allowed^ | ||
+ | |'' | ||
+ | |'' | ||
+ | |'' | ||
+ | |'' | ||
+ | |'' | ||
+ | |'' | ||
+ | |'' | ||
+ | |'' | ||
+ | |'' | ||
+ | |||
+ | Methods and functions outside the outer class are considered public from the perspective of an inner class. Attempting to declare a return of a non-visible type will result in a '' | ||
+ | |||
+ | <code php> | ||
+ | class Outer { | ||
+ | private class Inner {} | ||
| | ||
- | public class FromPoints extends Triangle | + | public |
- | public function __construct(Point $p1, Point $p2, Point $p3) { | + | return new self:> |
- | | + | } |
- | } | + | } |
+ | |||
+ | // Fatal error: Uncaught TypeError: Public method getInner cannot return private | ||
+ | new Outer()-> | ||
+ | </ | ||
+ | |||
+ | === Properties === | ||
+ | |||
+ | 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 a private/ | ||
+ | |||
+ | <code php> | ||
+ | class Outer { | ||
+ | private class Inner implements FooBar {} | ||
+ | |||
+ | public function getInner(): FooBar | ||
+ | | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==== Accessing outer classes ==== | ||
+ | |||
+ | Inner classes do not implicitly have access to an outer class instance. This design choice avoids implicit coupling between classes and keeps inner classes simpler, facilitating potential future extensions (such as adding an explicit outer keyword) without backward compatibility issues. | ||
+ | |||
+ | Passing an outer instance explicitly remains an available option for accessing protected/ | ||
+ | |||
+ | <code php> | ||
+ | class Message { | ||
+ | | ||
+ | |||
+ | public function Serialize(): string { | ||
+ | return new Message:> | ||
} | } | ||
| | ||
- | | + | |
- | public function __construct(int $x1, int $y1, int $x2, int $y2, int $x3, int $y3) { | + | public string $message; |
- | | + | |
+ | public function __construct(Message | ||
+ | | ||
} | } | ||
} | } | ||
} | } | ||
+ | </ | ||
- | $t = new Triangle:: | + | ==== Reflection ==== |
- | var_dump($t instanceof Triangle); // true | + | Several new methods are added to '' |
- | var_dump($t instanceof Geometry); // true | + | |
- | var_dump($t instanceof Triangle:: | + | <code php> |
+ | $reflection = new ReflectionClass(' | ||
+ | |||
+ | $reflection-> | ||
+ | $reflection-> | ||
+ | $reflection-> | ||
+ | $reflection-> | ||
</ | </ | ||
- | However, no classes may inherit from inner classes. Inner classes may inherit from other classes, | + | For non-inner classes, |
- | It’s important to note that inheritance for inner classes does not violate the Liskov Substitution Principle (LSP). LSP states that objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program. | + | ==== Autoloading ==== |
- | **Visibility Rules**: Private and protected inner classes are only instantiable within | + | Inner classes are never autoloaded, |
- | **Inheritance**: | + | ==== Usage ==== |
- | === Names === | + | Inner classes may be defined in the body of any class-like structure, including but not limited to: |
- | Inner classes | + | * 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. | ||
+ | |||
+ | ==== 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> | <code php> | ||
- | class Foo { | + | abstract |
- | | + | |
- | public class Bar {} | + | public class Implementation { |
- | | + | public function run(): void {} |
- | // Fatal error: Uncaught Error: Cannot redeclare Foo::Bar | + | |
} | } | ||
- | class Foo { | + | // Allowed; abstract outer class does not force inner classes to be abstract. |
- | | + | new Service:> |
- | public class Bar {} | + | </ |
- | + | ||
- | // Fatal error: Uncaught Error: Cannot redeclare Foo::$Bar | + | <code php> |
+ | readonly | ||
+ | | ||
+ | |||
+ | public function __construct(array $items) { | ||
+ | $this-> | ||
+ | } | ||
+ | |||
+ | public function getMutableBuilder(): | ||
+ | return new self:> | ||
+ | } | ||
+ | |||
+ | public class Builder | ||
+ | private array $items; | ||
+ | |||
+ | public function __construct(array $items) { | ||
+ | $this-> | ||
+ | } | ||
+ | |||
+ | public function add(mixed $item): self { | ||
+ | $this-> | ||
+ | return $this; | ||
+ | } | ||
+ | |||
+ | public function build(): ImmutableCollection { | ||
+ | return new ImmutableCollection($this-> | ||
+ | | ||
+ | | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | In this example, even though ImmutableBuilder is a mutable inner class within a readonly outer class, the outer class maintains its immutability externally, while inner classes help internally with state management or transitional operations. | ||
+ | |||
+ | ==== Abstract inner classes ==== | ||
+ | |||
+ | It is worth exploring what an '' | ||
+ | |||
+ | Abstract inner classes may not be instantiated, | ||
+ | |||
+ | <code php> | ||
+ | class OuterParent { | ||
+ | | ||
+ | } | ||
+ | |||
+ | // Middle is allowed, does not have to implement InnerBase | ||
+ | class Middle extends OuterParent {} | ||
+ | |||
+ | // Last demonstrates extending the abstract inner class explicitly. | ||
+ | class Last extends OuterParent { | ||
+ | private abstract class InnerExtended extends OuterParent:> | ||
} | } | ||
</ | </ | ||
Line 213: | 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 ==== | ||
This change introduces a new opcode, AST, and other changes that affect opcache. These changes are included as part of the PR that implements this feature. | This change introduces a new opcode, AST, and other changes that affect opcache. These changes are included as part of the PR that implements this feature. | ||
+ | |||
+ | ==== 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 229: | Line 483: | ||
===== Future Scope ===== | ===== Future Scope ===== | ||
- | TBD. | + | * Inner enums |
+ | * Inner interfaces | ||
+ | * Inner traits | ||
+ | * '' | ||
===== Proposed Voting Choices ===== | ===== Proposed Voting Choices ===== | ||
Line 238: | Line 495: | ||
===== Patches and Tests ===== | ===== Patches and Tests ===== | ||
- | A complete implementation is available [[https:// | + | To be completed. |
===== Implementation ===== | ===== Implementation ===== |
rfc/short-and-inner-classes.1741296641.txt.gz · Last modified: 2025/03/06 21:30 by withinboredom