Skip to content

Commit fe910ea

Browse files
committed
rephrase things
1 parent 218f05e commit fe910ea

File tree

1 file changed

+146
-100
lines changed

1 file changed

+146
-100
lines changed

published/short-class.ptxt

Lines changed: 146 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,43 @@
44
* Date: 2025-02-08
55
* Author: Rob Landers, <rob@bottled.codes>
66
* Status: Draft (or Under Discussion or Accepted or Declined)
7-
* First Published at: http://wiki.php.net/rfc/short-class
7+
* First Published at: http://wiki.php.net/rfc/short-and-inner-classes
88

99
===== Introduction =====
1010

11-
This RFC proposes a new short syntax for class definitions in PHP and the ability to embed these classes within other classes.
11+
PHP has steadily evolved to enhance developer productivity and expressiveness, introducing features such as typed properties, constructor property promotion, and first-class callable syntax. However, defining simple data structures and organizing classes remains verbose.
1212

13-
===== Proposal =====
13+
This RFC proposes two related enhancements to PHP:
14+
15+
**Short class syntax**, allowing simple or data-oriented classes to be defined in a single line:
16+
17+
<code php>
18+
class Point(int $x, int $y);
19+
</code>
1420

15-
Data transfer objects (DTOs) are a common pattern in PHP applications and are usually simple data structures that hold data and have no behavior. With this RFC, we propose a simple and concise syntax for defining these classes, looking almost like named anonymous classes, as well as the ability to embed them within other classes.
21+
This syntax acts as a shorthand for defining classes with constructor property promotion, reducing boilerplate while maintaining clarity.
22+
23+
**Inner classes**, enabling the definition of classes within other classes with visibility control:
24+
25+
<code php>
26+
class Foo {
27+
public class Bar(public string $message);
28+
}
29+
</code>
30+
31+
===== Proposal =====
1632

1733
==== Short Class Syntax ====
1834

19-
The proposed syntax for a short class definition is as follows: a keyword ''%%class%%'', followed by the class name, then a list of public properties enclosed in parentheses. Optionally, a list of traits, interfaces, and a parent class may be defined.
35+
The proposed syntax for defining a short class consists of the class keyword, followed by the class name, and a list of properties in parentheses. Optionally, traits, interfaces, and a parent class can be specified.
2036

2137
<code php>
38+
39+
// a simple class with two public properties
2240
class Point(int $x, int $y);
41+
42+
// A readonly class with a parent class, interface, and traits
43+
readonly class Vector(int $x, int $y) extends BaseVector implements JsonSerializable use PointTrait, Evolvable;
2344
</code>
2445

2546
This is equivalent to the following full class definition:
@@ -28,124 +49,184 @@ This is equivalent to the following full class definition:
2849
class Point {
2950
public function __construct(public int $x, public int $y) {}
3051
}
52+
53+
readonly public class Vector extends BaseVector implements JsonSerializable {
54+
use PointTrait, Evolvable;
55+
56+
public function __construct(public int $x, public int $y) {}
57+
}
3158
</code>
3259

33-
Any properties defined within the parenthesis are defined as a public property of the class.
60+
Properties inside parentheses are automatically declared as class properties and default to public unless explicitly specified:
61+
62+
<code php>
63+
// declare $shapes as a private property
64+
class Geometry(private $shapes) use GeometryTrait;
65+
</code>
3466

3567
=== Default Values ===
3668

37-
Default values may be provided for properties:
69+
Properties with type hints may have default values:
3870

3971
<code php>
4072
class Point(int $x = 0, int $y = 0);
4173
</code>
4274

4375
=== Inheritance and Behavior ===
4476

45-
With class short syntax, no behavior may be defined, yet it can still utilize traits, interfaces, and other classes.
77+
Short classes can extend other classes, implement interfaces, and use traits, but they cannot define additional methods. The parent class constructor is overridden and not automatically called.
4678

4779
<code php>
4880
class Point(int $x, int $y) extends BasePoint implements JsonSerializable use PointTrait, Evolvable;
4981
</code>
5082

51-
Note that the original constructor from any parent class is overridden and not called by the short syntax.
52-
5383
=== Empty Classes ===
5484

55-
Short classes may also be empty:
85+
Short classes may be empty:
5686

5787
<code php>
5888
class Point() extends BasePoint use PointTrait;
5989
</code>
6090

6191
=== Attributes ===
6292

63-
Attributes may also be used with short classes:
93+
Attributes can be used with short classes:
6494

6595
<code php>
96+
#[MyAttribute]
6697
class Password(#[SensitiveParameter] string $password);
6798
</code>
6899

100+
=== Modifiers ===
101+
102+
Short classes support readonly, final, and abstract:
103+
104+
<code php>
105+
readonly class User(int $id, string $name);
106+
107+
final class Config(string $key, mixed $value);
108+
109+
abstract class Shape(float $area);
110+
</code>
111+
112+
=== How it works ===
113+
114+
Short classes are purely syntactic sugar and compile into standard class definitions.
115+
69116
==== Inner Classes ====
70117

71-
Inner classes are classes that are defined within another class.
118+
Inner classes allow defining classes within other classes, following visibility rules:
72119

73120
<code php>
74-
class Foo {
75-
class Bar(public string $message);
76-
enum Baz(One, Two, Three);
121+
class Outer {
122+
class Inner(public string $message);
77123

78-
private class Baz {
124+
private class PrivateInner {
79125
public function __construct(public string $message) {}
80126
}
81127
}
82128

83-
$foo = new Foo::Bar('Hello, world!');
129+
$foo = new Outer::Inner('Hello, world!');
84130
echo $foo->message;
85131
// outputs: Hello, world!
86-
$baz = new Foo::Baz('Hello, world!');
87-
// Fatal error: Uncaught Error: Cannot access private class Foo::Baz
132+
$baz = new Outer::PrivateInner('Hello, world!');
133+
// Fatal error: Uncaught Error: Cannot access private inner class Outer::PrivateInner
88134
</code>
89135

90-
Inner classes have scope similar to properties, which applies to parameters and returns types as well.
91-
92136
=== Modifiers ===
93137

94-
Properties support modifiers such as ''%%public%%'', ''%%protected%%'', and ''%%private%%'' as well as ''%%static%%'', ''%%final%%'' and ''%%readonly%%''. When using these as modifiers on an inner class, there are some intuitive rules:
95-
96-
* ''%%public%%'', ''%%private%%'', and ''%%protected%%'' apply to the visibility of the class.
97-
* ''%%static%%'', ''%%final%%'', and ''%%readonly%%'' apply to the class itself.
138+
Inner classes support modifiers such as ''%%public%%'', ''%%protected%%'', ''%%private%%'', ''%%final%%'' and ''%%readonly%%''. When using these as modifiers on an inner class, there are some intuitive rules:
98139

99-
Thus, an inner class with the modifier ''%%private readonly%%'' is only accessible within the class and any instances are readonly.
140+
* ''%%public%%'', ''%%private%%'', and ''%%protected%%'' apply to the visibility of the inner class.
141+
* ''%%final%%'', and ''%%readonly%%'' apply to the class itself.
142+
* ''%%static%%'' is not allowed as a modifier since PHP does not support static classes.
143+
* ''%%abstract%%'' is not allowed as an inner class cannot be parent classes.
100144

101-
=== Visibility ===
145+
=== Visibility Rules ===
102146

103-
A ''%%private%%'' or ''%%protected%%'' inner class is only accessible within the class it is defined in (or its subclasses in the case of protected classes). This also applies to methods and return types.
147+
Private and protected inner classes are only instantiatable within their outer class (or subclasses for protected) and may not be used as type hints outside of their outer class.
104148

105149
<code php>
106-
class Foo {
107-
private class Bar(public string $message);
150+
class Outer {
151+
private class PrivateInner(string $message);
108152

109-
// Fatal error: Uncaught Error: Cannot return private class Foo::Bar
110-
public function getMessage(): Bar {
111-
return new Bar('Hello, world!');
153+
public function getInner(): self::PrivateInner {
154+
return new self::PrivateInner('Hello, world!');
112155
}
113156
}
114-
</code>
115157

116-
=== Accessing Inner Classes ===
158+
// using a private inner class from outside the outer class, as a type hint is forbidden
159+
function doSomething(Outer::PrivateInner $inner) {
160+
echo $inner->message;
161+
}
117162

118-
From outside the class, public inner classes may be accessed using the ''%%::%%'' operator:
163+
// this is ok:
164+
$inner = new Outer()->getInner();
119165

120-
<code php>
121-
new Foo::Bar('Hello, world!');
166+
// but this is not:
167+
doSomething($inner);
168+
// Fatal error: Private inner class Outer::Inner cannot be used in the global scope
122169
</code>
123170

124-
This may also be used from inside the class or in subclasses at the developer’s discretion. Alternatively, inner classes may be accessed using ''%%self::%%'' or ''%%static::%%'' from inside the class, or just using the name itself:
125-
126-
<code php>
171+
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/protected class.
127172

128-
private function getBar(): Foo::Bar {
129-
$a = new Bar('Hello, world!');
130-
$b = new self::Bar('Hello, world!');
131-
$c = new static::Bar('Hello, world!');
132-
$d = new Foo::Bar('Hello, world!');
133-
}
134-
</code>
173+
=== Inheritance ===
135174

136-
Note that inner classes effectively "shadow" outer classes of the same name:
175+
Inner classes have inheritance similar to static properties; this allows you to redefine an inner class in a subclass, allowing rich hierarchies.
137176

138177
<code php>
139-
readonly class Vect(int $x, int $y);
178+
readonly class Point(int $x, int $y);
140179

141-
class Foo {
142-
class Vect(int $x, int $y, int $z);
180+
class Geometry {
181+
public array $points;
182+
protected function __construct(Point ...$points) {
183+
$this->points = $points;
184+
}
185+
186+
public class FromPoints extends Geometry {
187+
public function __construct(Point ...$points) {
188+
parent::__construct(...$points);
189+
}
190+
}
191+
192+
public class FromCoordinates extends Geometry {
193+
public function __construct(int ...$coordinates) {
194+
$points = [];
195+
for ($i = 0; $i < count($coordinates); $i += 2) {
196+
$points[] = new Point($coordinates[$i], $coordinates[$i + 1]);
197+
}
198+
parent::__construct(...$points);
199+
}
200+
}
201+
}
202+
203+
class Triangle extends Geometry {
204+
protected function __construct(public Point $p1, public Point $p2, public Point $p3) {
205+
parent::__construct($p1, $p2, $p3);
206+
}
207+
208+
public class FromPoints extends Triangle {
209+
public function __construct(Point $p1, Point $p2, Point $p3) {
210+
parent::__construct($p1, $p2, $p3);
211+
}
212+
}
143213

144-
// Vect is Foo::Vect not \Vect
145-
public function __construct(public Vect $vect) {}
214+
public class FromCoordinates extends Triangle {
215+
public function __construct(int $x1, int $y1, int $x2, int $y2, int $x3, int $y3) {
216+
parent::__construct(new Point($x1, $y1), new Point($x2, $y2), new Point($x3, $y3));
217+
}
218+
}
146219
}
220+
221+
$t = new Triangle::FromCoordinates(0, 0, 1, 1, 2, 2);
222+
223+
var_dump($t instanceof Triangle); // true
224+
var_dump($t instanceof Geometry); // true
225+
var_dump($t instanceof Triangle::FromCoordinates); // true
147226
</code>
148227

228+
However, no classes may not inherit from inner classes, but inner classes may inherit from other classes, including the outer class.
229+
149230
=== Names ===
150231

151232
Inner classes may not have any name that conflicts with a constant or static property of the same name.
@@ -162,85 +243,50 @@ class Foo {
162243
static $Bar = 'bar';
163244
class Bar(public string $message);
164245

165-
// Fatal error: Uncaught Error: Cannot redeclare Foo::Bar
246+
// Fatal error: Uncaught Error: Cannot redeclare Foo::$Bar
166247
}
167248
</code>
168249

169-
These rules are to prevent developer confusion because these instantiations all look similar, however, the following all result in the same inner class being instantiated:
170-
171-
<code php>
172-
new (Foo::Bar);
173-
new (Foo::$Bar);
174-
new Foo::Bar();
175-
</code>
176-
177250
===== Backward Incompatible Changes =====
178251

179-
Creating a new instance from an existing static member is now allowed:
180-
181-
<code php>
182-
class Foo {
183-
public const Bar = 'bar';
184-
}
185-
186-
new Foo::Bar(); // previously this is a syntax error, but now results in creating a new "bar" object.
187-
</code>
252+
This RFC introduces new syntax and behavior to PHP, which does not conflict with existing syntax. However, tooling utilizing AST or tokenization may need to be updated to support the new syntax.
188253

189254
===== Proposed PHP Version(s) =====
190255

191-
List the proposed PHP versions that the feature will be included in. Use relative versions such as "next PHP 8.x" or "next PHP 8.x.y".
256+
This RFC targets the next version of PHP.
192257

193258
===== RFC Impact =====
194259

195260
==== To SAPIs ====
196261

197-
Describe the impact to CLI, Development web server, embedded PHP etc.
262+
None.
198263

199264
==== To Existing Extensions ====
200265

201-
Will existing extensions be affected?
266+
Extensions accepting class names may need to be updated to support ''%%::%%'' in class names. None were discovered during testing, but it is possible there are extensions that may be affected.
202267

203268
==== To Opcache ====
204269

205-
It is necessary to develop RFC's with opcache in mind, since opcache is a core extension distributed with PHP.
206-
207-
Please explain how you have verified your RFC's compatibility with opcache.
208-
209-
==== New Constants ====
210-
211-
Describe any new constants so they can be accurately and comprehensively explained in the PHP documentation.
212-
213-
==== php.ini Defaults ====
214-
215-
If there are any php.ini settings then list: * hardcoded default values * php.ini-development values * php.ini-production values
270+
Most of the changes are in compilation and AST, so the impact to opcache is minimal.
216271

217272
===== Open Issues =====
218273

219-
Make sure there are no open issues when the vote starts!
274+
Pending discussion.
220275

221276
===== Unaffected PHP Functionality =====
222277

223-
List existing areas/features of PHP that will not be changed by the RFC.
224-
225-
This helps avoid any ambiguity, shows that you have thought deeply about the RFC's impact, and helps reduces mail list noise.
278+
There should be no change to existing PHP functionality.
226279

227280
===== Future Scope =====
228281

229-
This section details areas where the feature might be improved in future, but that are not currently proposed in this RFC.
282+
* inner enums
230283

231284
===== Proposed Voting Choices =====
232285

233-
Include these so readers know where you are heading and can discuss the proposed voting options.
234286

235287
===== Patches and Tests =====
236288

237-
Links to any external patches and tests go here.
238-
239-
If there is no patch, make it clear who will create a patch, or whether a volunteer to help with implementation is needed.
240-
241-
Make it clear if the patch is intended to be the final patch, or is just a prototype.
242-
243-
For changes affecting the core language, you should also provide a patch for the language specification.
289+
A complete implementation is available [[https://github.com/php/php-src/compare/master...bottledcode:php-src:rfc/short-class2?expand=1|on GitHub]].
244290

245291
===== Implementation =====
246292

0 commit comments

Comments
 (0)