|
| 1 | +# PHP RFC: Short and Inner Classes |
| 2 | + |
| 3 | +* Version: 0.1 |
| 4 | +* Date: 2025-02-08 |
| 5 | +* Author: Rob Landers, rob@bottled.codes |
| 6 | +* Status: Draft (or Under Discussion or Accepted or Declined) |
| 7 | +* First Published at: <http://wiki.php.net/rfc/short-class> |
| 8 | + |
| 9 | +## Introduction |
| 10 | + |
| 11 | +This RFC proposes a new short syntax for class definitions in PHP and the ability to embed these classes within other |
| 12 | +classes. |
| 13 | + |
| 14 | +## Proposal |
| 15 | + |
| 16 | +Data transfer objects (DTOs) are a common pattern in PHP applications and are usually simple data structures that hold |
| 17 | +data and have no behavior. |
| 18 | +With this RFC, |
| 19 | +we propose a simple and concise syntax for defining these classes, looking almost like named anonymous classes, as well as |
| 20 | +the ability to embed them within other classes. |
| 21 | + |
| 22 | +### Short Class Syntax |
| 23 | + |
| 24 | +The proposed syntax for a short class definition is as follows: a keyword `class`, |
| 25 | +followed by the class name, then a list of public properties enclosed in parentheses. |
| 26 | +Optionally, a list of traits, interfaces, and a parent class may be defined. |
| 27 | + |
| 28 | +```php |
| 29 | +class Point(int $x, int $y); |
| 30 | +``` |
| 31 | + |
| 32 | +This is equivalent to the following full class definition: |
| 33 | + |
| 34 | +```php |
| 35 | +class Point { |
| 36 | + public function __construct(public int $x, public int $y) {} |
| 37 | +} |
| 38 | +``` |
| 39 | + |
| 40 | +Any properties defined within the parenthesis are defined as a public property of the class. |
| 41 | + |
| 42 | +#### Default Values |
| 43 | + |
| 44 | +Default values may be provided for properties: |
| 45 | + |
| 46 | +```php |
| 47 | +class Point(int $x = 0, int $y = 0); |
| 48 | +``` |
| 49 | + |
| 50 | +#### Inheritance and Behavior |
| 51 | + |
| 52 | +With class short syntax, no behavior may be defined, yet it can still utilize traits, interfaces, and other classes. |
| 53 | + |
| 54 | +```php |
| 55 | +class Point(int $x, int $y) extends BasePoint implements JsonSerializable use PointTrait, Evolvable; |
| 56 | +``` |
| 57 | + |
| 58 | +Note that the original constructor from any parent class is overridden and not called by the short syntax. |
| 59 | + |
| 60 | +#### Empty Classes |
| 61 | + |
| 62 | +Short classes may also be empty: |
| 63 | + |
| 64 | +```php |
| 65 | +class Point() extends BasePoint use PointTrait; |
| 66 | +``` |
| 67 | + |
| 68 | +#### Attributes |
| 69 | + |
| 70 | +Attributes may also be used with short classes: |
| 71 | + |
| 72 | +```php |
| 73 | +class Password(#[SensitiveParameter] string $password); |
| 74 | +``` |
| 75 | + |
| 76 | +### Inner Classes |
| 77 | + |
| 78 | +Inner classes are classes that are defined within another class. |
| 79 | + |
| 80 | +```php |
| 81 | +class Foo { |
| 82 | + class Bar(public string $message); |
| 83 | + |
| 84 | + private class Baz { |
| 85 | + public function __construct(public string $message) {} |
| 86 | + } |
| 87 | +} |
| 88 | + |
| 89 | +$foo = new Foo::Bar('Hello, world!'); |
| 90 | +echo $foo->message; |
| 91 | +// outputs: Hello, world! |
| 92 | +$baz = new Foo::Baz('Hello, world!'); |
| 93 | +// Fatal error: Uncaught Error: Cannot access private class Foo::Baz |
| 94 | +``` |
| 95 | + |
| 96 | +Inner classes have scope similar to properties, which applies to parameters and returns types as well. |
| 97 | + |
| 98 | +#### Modifiers |
| 99 | + |
| 100 | +Properties support modifiers such as `public`, `protected`, and `private` as well as `static`, `final` and `readonly`. |
| 101 | +When using these as modifiers on an inner class, there are some intuitive rules: |
| 102 | + |
| 103 | +- `public`, `private`, and `protected` apply to the visibility of the class. |
| 104 | +- `static`, `final`, and `readonly` apply to the class itself. |
| 105 | + |
| 106 | +Thus, an inner class with the modifier `private readonly` is only accessible within the class |
| 107 | +and any instances are readonly. |
| 108 | + |
| 109 | +#### Visibility |
| 110 | + |
| 111 | +A `private` or `protected` inner class is only accessible within the class it is defined in |
| 112 | +(or its subclasses in the case of protected classes). |
| 113 | +This also applies to methods and return types. |
| 114 | + |
| 115 | +```php |
| 116 | +class Foo { |
| 117 | + private class Bar(public string $message); |
| 118 | + |
| 119 | + // Fatal error: Uncaught Error: Cannot return private class Foo::Bar |
| 120 | + public function getMessage(): Bar { |
| 121 | + return new Bar('Hello, world!'); |
| 122 | + } |
| 123 | +} |
| 124 | +``` |
| 125 | + |
| 126 | +#### Accessing Inner Classes |
| 127 | + |
| 128 | +From outside the class, public inner classes may be accessed using the `::` operator: |
| 129 | + |
| 130 | +```php |
| 131 | +new Foo::Bar('Hello, world!'); |
| 132 | +``` |
| 133 | + |
| 134 | +This may also be used from inside the class or in subclasses at the developer’s discretion. Alternatively, inner classes |
| 135 | +may be accessed using `self::` or `static::` from inside the class, or just using the name itself: |
| 136 | + |
| 137 | +```php |
| 138 | + |
| 139 | +private function getBar(): Foo::Bar { |
| 140 | + $a = new Bar('Hello, world!'); |
| 141 | + $b = new self::Bar('Hello, world!'); |
| 142 | + $c = new static::Bar('Hello, world!'); |
| 143 | + $d = new Foo::Bar('Hello, world!'); |
| 144 | +} |
| 145 | + |
| 146 | +``` |
| 147 | + |
| 148 | +Note that inner classes effectively "shadow" outer classes of the same name: |
| 149 | + |
| 150 | +```php |
| 151 | +readonly class Vect(int $x, int $y); |
| 152 | + |
| 153 | +class Foo { |
| 154 | + class Vect(int $x, int $y, int $z); |
| 155 | + |
| 156 | + // Vect is Foo::Vect not \Vect |
| 157 | + public function __construct(public Vect $vect) {} |
| 158 | +} |
| 159 | +``` |
| 160 | + |
| 161 | +#### Names |
| 162 | + |
| 163 | +Inner classes may not have any name that conflicts with a constant or static method of the same name. |
| 164 | + |
| 165 | +```php |
| 166 | +class Foo { |
| 167 | + const Bar = 'bar'; |
| 168 | + class Bar(public string $message); |
| 169 | + |
| 170 | + // Fatal error: Uncaught Error: Cannot redeclare Foo::Bar |
| 171 | +} |
| 172 | + |
| 173 | +class Foo { |
| 174 | + static function Bar() {} |
| 175 | + class Bar(public string $message); |
| 176 | + |
| 177 | + // Fatal error: Uncaught Error: Cannot redeclare Foo::Bar |
| 178 | +} |
| 179 | +``` |
| 180 | + |
| 181 | +These rules are to prevent developer confusion because these instantiations all look similar, |
| 182 | +but without this rule, they would all work: |
| 183 | + |
| 184 | +```php |
| 185 | +new (Foo::Bar); // create a new class from the name stored in Foo::Bar |
| 186 | +new (Foo::Bar()); // create a new instance from the name returned by Foo::Bar() |
| 187 | +new Foo::Bar(); // create a new instance of the class Foo::Bar |
| 188 | +``` |
| 189 | + |
| 190 | +## Backward Incompatible Changes |
| 191 | + |
| 192 | +What breaks, and what is the justification for it? |
| 193 | + |
| 194 | +## Proposed PHP Version(s) |
| 195 | + |
| 196 | +List the proposed PHP versions that the feature will be included in. Use |
| 197 | +relative versions such as "next PHP 8.x" or "next PHP 8.x.y". |
| 198 | + |
| 199 | +## RFC Impact |
| 200 | + |
| 201 | +### To SAPIs |
| 202 | + |
| 203 | +Describe the impact to CLI, Development web server, embedded PHP etc. |
| 204 | + |
| 205 | +### To Existing Extensions |
| 206 | + |
| 207 | +Will existing extensions be affected? |
| 208 | + |
| 209 | +### To Opcache |
| 210 | + |
| 211 | +It is necessary to develop RFC's with opcache in mind, since opcache is |
| 212 | +a core extension distributed with PHP. |
| 213 | + |
| 214 | +Please explain how you have verified your RFC's compatibility with |
| 215 | +opcache. |
| 216 | + |
| 217 | +### New Constants |
| 218 | + |
| 219 | +Describe any new constants so they can be accurately and comprehensively |
| 220 | +explained in the PHP documentation. |
| 221 | + |
| 222 | +### php.ini Defaults |
| 223 | + |
| 224 | +If there are any php.ini settings then list: \* hardcoded default values |
| 225 | +\* php.ini-development values \* php.ini-production values |
| 226 | + |
| 227 | +## Open Issues |
| 228 | + |
| 229 | +Make sure there are no open issues when the vote starts! |
| 230 | + |
| 231 | +## Unaffected PHP Functionality |
| 232 | + |
| 233 | +List existing areas/features of PHP that will not be changed by the RFC. |
| 234 | + |
| 235 | +This helps avoid any ambiguity, shows that you have thought deeply about |
| 236 | +the RFC's impact, and helps reduces mail list noise. |
| 237 | + |
| 238 | +## Future Scope |
| 239 | + |
| 240 | +This section details areas where the feature might be improved in |
| 241 | +future, but that are not currently proposed in this RFC. |
| 242 | + |
| 243 | +## Proposed Voting Choices |
| 244 | + |
| 245 | +Include these so readers know where you are heading and can discuss the |
| 246 | +proposed voting options. |
| 247 | + |
| 248 | +## Patches and Tests |
| 249 | + |
| 250 | +Links to any external patches and tests go here. |
| 251 | + |
| 252 | +If there is no patch, make it clear who will create a patch, or whether |
| 253 | +a volunteer to help with implementation is needed. |
| 254 | + |
| 255 | +Make it clear if the patch is intended to be the final patch, or is just |
| 256 | +a prototype. |
| 257 | + |
| 258 | +For changes affecting the core language, you should also provide a patch |
| 259 | +for the language specification. |
| 260 | + |
| 261 | +## Implementation |
| 262 | + |
| 263 | +After the project is implemented, this section should contain - the |
| 264 | +version(s) it was merged into - a link to the git commit(s) - a link to |
| 265 | +the PHP manual entry for the feature - a link to the language |
| 266 | +specification section (if any) |
| 267 | + |
| 268 | +## References |
| 269 | + |
| 270 | +Links to external references, discussions or RFCs |
| 271 | + |
| 272 | +## Rejected Features |
| 273 | + |
| 274 | +Keep this updated with features that were discussed on the mail lists. |
0 commit comments