You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* 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
8
8
9
9
===== Introduction =====
10
10
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.
12
12
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>
14
20
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 =====
16
32
17
33
==== Short Class Syntax ====
18
34
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.
20
36
21
37
<code php>
38
+
39
+
// a simple class with two public properties
22
40
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;
23
44
</code>
24
45
25
46
This is equivalent to the following full class definition:
@@ -28,124 +49,184 @@ This is equivalent to the following full class definition:
28
49
class Point {
29
50
public function __construct(public int $x, public int $y) {}
30
51
}
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
+
}
31
58
</code>
32
59
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>
34
66
35
67
=== Default Values ===
36
68
37
-
Default values may be provided for properties:
69
+
Properties with type hints may have default values:
38
70
39
71
<code php>
40
72
class Point(int $x = 0, int $y = 0);
41
73
</code>
42
74
43
75
=== Inheritance and Behavior ===
44
76
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.
46
78
47
79
<code php>
48
80
class Point(int $x, int $y) extends BasePoint implements JsonSerializable use PointTrait, Evolvable;
49
81
</code>
50
82
51
-
Note that the original constructor from any parent class is overridden and not called by the short syntax.
52
-
53
83
=== Empty Classes ===
54
84
55
-
Short classes may also be empty:
85
+
Short classes may be empty:
56
86
57
87
<code php>
58
88
class Point() extends BasePoint use PointTrait;
59
89
</code>
60
90
61
91
=== Attributes ===
62
92
63
-
Attributes may also be used with short classes:
93
+
Attributes can be used with short classes:
64
94
65
95
<code php>
96
+
#[MyAttribute]
66
97
class Password(#[SensitiveParameter] string $password);
67
98
</code>
68
99
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
+
69
116
==== Inner Classes ====
70
117
71
-
Inner classes are classes that are defined within another class.
118
+
Inner classes allow defining classes within other classes, following visibility rules:
72
119
73
120
<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);
77
123
78
-
private class Baz {
124
+
private class PrivateInner {
79
125
public function __construct(public string $message) {}
80
126
}
81
127
}
82
128
83
-
$foo = new Foo::Bar('Hello, world!');
129
+
$foo = new Outer::Inner('Hello, world!');
84
130
echo $foo->message;
85
131
// outputs: Hello, world!
86
-
$baz = new Foo::Baz('Hello, world!');
87
-
// Fatal error: Uncaught Error: Cannot access private class Foo::Baz
Inner classes have scope similar to properties, which applies to parameters and returns types as well.
91
-
92
136
=== Modifiers ===
93
137
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:
98
139
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.
100
144
101
-
=== Visibility ===
145
+
=== Visibility Rules ===
102
146
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.
104
148
105
149
<code php>
106
-
class Foo {
107
-
private class Bar(public string $message);
150
+
class Outer {
151
+
private class PrivateInner(string $message);
108
152
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!');
112
155
}
113
156
}
114
-
</code>
115
157
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
+
}
117
162
118
-
From outside the class, public inner classes may be accessed using the ''%%::%%'' operator:
163
+
// this is ok:
164
+
$inner = new Outer()->getInner();
119
165
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
122
169
</code>
123
170
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.
127
172
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 ===
135
174
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.
137
176
138
177
<code php>
139
-
readonly class Vect(int $x, int $y);
178
+
readonly class Point(int $x, int $y);
140
179
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
+
}
143
213
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
+
}
146
219
}
220
+
221
+
$t = new Triangle::FromCoordinates(0, 0, 1, 1, 2, 2);
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
-
177
250
===== Backward Incompatible Changes =====
178
251
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.
188
253
189
254
===== Proposed PHP Version(s) =====
190
255
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.
192
257
193
258
===== RFC Impact =====
194
259
195
260
==== To SAPIs ====
196
261
197
-
Describe the impact to CLI, Development web server, embedded PHP etc.
262
+
None.
198
263
199
264
==== To Existing Extensions ====
200
265
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.
202
267
203
268
==== To Opcache ====
204
269
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.
216
271
217
272
===== Open Issues =====
218
273
219
-
Make sure there are no open issues when the vote starts!
274
+
Pending discussion.
220
275
221
276
===== Unaffected PHP Functionality =====
222
277
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.
226
279
227
280
===== Future Scope =====
228
281
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
230
283
231
284
===== Proposed Voting Choices =====
232
285
233
-
Include these so readers know where you are heading and can discuss the proposed voting options.
234
286
235
287
===== Patches and Tests =====
236
288
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]].
0 commit comments