Skip to content

Commit b7a8603

Browse files
committed
initial commit
1 parent 421b320 commit b7a8603

File tree

2 files changed

+559
-0
lines changed

2 files changed

+559
-0
lines changed

drafts/records.md

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
# PHP RFC: Records
2+
3+
- Version: 0.9
4+
- Date: 2024-07-19
5+
- Author: Robert Landers, [email protected]
6+
- Status: Draft (or Under Discussion or Accepted or Declined)
7+
- First Published at: <http://wiki.php.net/rfc/records>
8+
9+
## Introduction
10+
11+
In modern PHP development, the need for concise and immutable data
12+
structures is increasingly recognized. Inspired by the concept of
13+
"records", "data objects", or "structs" in other languages, this RFC
14+
proposes the addition of `record` objects in PHP. These records will
15+
provide a concise and immutable data structure, distinct from `readonly`
16+
classes, enabling developers to define immutable objects with less
17+
boilerplate code.
18+
19+
## Proposal
20+
21+
This RFC proposes the introduction of a new record keyword in PHP to
22+
define immutable data objects. These objects will allow properties to be
23+
initialized concisely and will provide built-in methods for common
24+
operations such as modifying properties and equality checks using a
25+
function-like instantiation syntax. Records can implement interfaces and
26+
use traits but cannot extend other records or classes.
27+
28+
#### Syntax and semantics
29+
30+
##### Definition
31+
32+
A `record` is defined by the word "record", followed by the name of its
33+
type, an open parenthesis containing zero or more typed parameters that
34+
become public, immutable, properties. They may optionally implement an
35+
interface using the `implements` keyword. A `record` body is optional.
36+
37+
A `record` may NOT contain a constructor; instead of defining initial
38+
values, property hooks should be used to produce computable values
39+
on-demand. Defining a constructor emits a compilation error.
40+
41+
A `record` body may contain property hooks, methods, and use traits.
42+
Regular properties may also be defined, but they are immutable by
43+
default and are no different than `const`.
44+
45+
Static properties and methods are forbidden in a `record` (this includes
46+
`const`, a regular property may be used instead). Attempting to define
47+
static properties, methods, constants results in a compilation error.
48+
49+
``` php
50+
namespace Geometry;
51+
52+
interface Shape {
53+
public function area(): float;
54+
}
55+
56+
trait Dimension {
57+
public function dimensions(): array {
58+
return [$this->width, $this->height];
59+
}
60+
}
61+
62+
record Vector2(int $x, int $y);
63+
64+
record Rectangle(Vector2 $leftTop, Vector2 $rightBottom) implements Shape {
65+
use Dimension;
66+
67+
public int $height = 1; // this will always and forever be "1", it is immutable.
68+
69+
public int $width { get => $this->rightBottom->x - $this->topLeft->x; }
70+
public int $height { get => $this->rightBottom->y - $this->topLeft->y; }
71+
72+
public function area(): float {
73+
return $this->width * $this->height;
74+
}
75+
}
76+
```
77+
78+
##### Usage
79+
80+
A `record` may be used as a `readonly class`, as the behavior of it is
81+
very similar with no key differences to assist in migration from
82+
`readonly class`.
83+
84+
``` php
85+
$rect1 = Rectangle(Point(0, 0), Point(1, -1));
86+
$rect2 = $rect1->with(topLeft: Point(0, 1));
87+
88+
var_dump($rect2->dimensions());
89+
```
90+
91+
##### Optional parameters and default values
92+
93+
A `record` can also be defined with optional parameters that are set if
94+
left out during instantiation.
95+
96+
``` php
97+
record Rectangle(int $x, int $y = 10);
98+
var_dump(Rectangle(10)); // output a record with x: 10 and y: 10
99+
```
100+
101+
##### Auto-generated `with` method
102+
103+
To enhance the usability of records, the RFC proposes automatically
104+
generating a `with` method for each record. This method allows for
105+
partial updates of properties, creating a new instance of the record
106+
with the specified properties updated.
107+
108+
The auto-generated `with` method accepts only named arguments defined in
109+
the constructor. No other property names can be used, and it returns a
110+
new record object with the given values.
111+
112+
``` php
113+
$point1 = Point(3, 4);
114+
$point2 = $point1->with(x: 5);
115+
$point3 = $point1->with(null, 10); // must use named arguments
116+
117+
echo $point1->x; // Outputs: 3
118+
echo $point2->x; // Outputs: 5
119+
```
120+
121+
A developer may define their own `with` method if they so choose.
122+
123+
#### Performance considerations
124+
125+
To ensure that records are both performant and memory-efficient, the RFC
126+
proposes leveraging PHP's copy-on-write (COW) semantics (similar to
127+
arrays) and interning values. Unlike interned strings, the garbage
128+
collector will be allowed to clean up these interned records when they
129+
are no longer needed.
130+
131+
``` php
132+
$point1 = Point(3, 4);
133+
$point2 = $point1; // No data duplication, $point2 references the same data as $point1
134+
$point3 = Point(3, 4); // No data duplication here either, it is pointing the the same memory as $point1
135+
136+
$point4 = $point1->with(x: 5); // Data duplication occurs here, creating a new instance with modified data
137+
```
138+
139+
##### Cloning and with()
140+
141+
Calling `clone` on a `record` results in the exact same record object
142+
being returned. As it is a "value" object, it represents a value and is
143+
the same thing as saying `clone 3`—you expect to get back a `3`.
144+
145+
`with` may be called with no arguments, and it is the same behavior as
146+
`clone`. This is an important consideration because a developer may call
147+
`$new = $record->with(...$array)` and we don't want to crash. If a
148+
developer wants to crash, they can do by `assert($new !== $record)`.
149+
150+
#### Equality
151+
152+
A `record` is always strongly equal (`===`) to another record with the
153+
same value in the properties, much like an `array` is strongly equal to
154+
another array containing the same elements. For all intents,
155+
`$recordA === $recordB` is the same as `$recordA == $recordB`.
156+
157+
Comparison operations will behave exactly like they do for classes.
158+
159+
### Reflection
160+
161+
Records in PHP will be fully supported by the reflection API, providing
162+
access to their properties and methods just like regular classes.
163+
However, immutability and special instantiation rules will be enforced.
164+
165+
#### ReflectionClass support
166+
167+
`ReflectionClass` can be used to inspect records, their properties, and
168+
methods. Any attempt to modify record properties via reflection will
169+
throw an exception, maintaining immutability. Attempting to create a new
170+
instance via `ReflectionClass` will cause a `ReflectionException` to be
171+
thrown.
172+
173+
``` php
174+
$point = Point(3, 4);
175+
$reflection = new \ReflectionClass($point);
176+
177+
foreach ($reflection->getProperties() as $property) {
178+
echo $property->getName() . ': ' . $property->getValue($point) . PHP_EOL;
179+
}
180+
```
181+
182+
#### Immutability enforcement
183+
184+
Attempts to modify record properties via reflection will throw an
185+
exception.
186+
187+
``` php
188+
try {
189+
$property = $reflection->getProperty('x');
190+
$property->setValue($point, 10); // This will throw an exception
191+
} catch (\ReflectionException $e) {
192+
echo 'Exception: ' . $e->getMessage() . PHP_EOL; // "Cannot modify a record property"
193+
}
194+
```
195+
196+
#### ReflectionFunction for implicit constructor
197+
198+
Using `ReflectionFunction` on a record will reflect the implicit
199+
constructor.
200+
201+
``` php
202+
$constructor = new \ReflectionFunction('Geometry\Point');
203+
echo 'Constructor Parameters: ';
204+
foreach ($constructor->getParameters() as $param) {
205+
echo $param->getName() . ' ';
206+
}
207+
```
208+
209+
#### New functions and methods
210+
211+
- Calling `is_object($record)` will return `true`.
212+
- A new function, `is_record($record)`, will return `true` for records,
213+
and `false` otherwise
214+
- Calling `get_class($record)` will return the record name
215+
216+
#### var_dump
217+
218+
Calling `var_dump` will look much like it does for objects, but instead
219+
of `object` it will say `record`.
220+
221+
record(Point)#1 (2) {
222+
["x"]=>
223+
int(1)
224+
["y"]=>
225+
int(2)
226+
}
227+
228+
### Considerations for implementations
229+
230+
A `record` cannot be named after an existing `record`, `class` or
231+
`function`. This is because defining a `record` creates both a `class`
232+
and a `function` with the same name.
233+
234+
### Auto loading
235+
236+
As invoking a record value by its name looks remarkably similar to
237+
calling a function, and PHP has no function autoloader, auto loading
238+
will not be supported in this implementation. If function auto loading
239+
were to be implemented in the future, an autoloader could locate the
240+
`record` and autoload it. The author of this RFC strongly encourages
241+
someone to put forward a function auto loading RFC if auto loading is
242+
desired for records.
243+
244+
## Backward Incompatible Changes
245+
246+
No backward incompatible changes.
247+
248+
## Proposed PHP Version(s)
249+
250+
PHP 8.5
251+
252+
## RFC Impact
253+
254+
### To SAPIs
255+
256+
N/A
257+
258+
### To Existing Extensions
259+
260+
N/A
261+
262+
### To Opcache
263+
264+
Unknown.
265+
266+
### New Constants
267+
268+
None
269+
270+
### php.ini Defaults
271+
272+
None
273+
274+
## Open Issues
275+
276+
Todo
277+
278+
## Unaffected PHP Functionality
279+
280+
None.
281+
282+
## Future Scope
283+
284+
## Proposed Voting Choices
285+
286+
Include these so readers know where you are heading and can discuss the
287+
proposed voting options.
288+
289+
## Patches and Tests
290+
291+
TBD
292+
293+
## Implementation
294+
295+
After the project is implemented, this section should contain
296+
297+
1. the version(s) it was merged into
298+
2. a link to the git commit(s)
299+
3. a link to the PHP manual entry for the feature
300+
4. a link to the language specification section (if any)
301+
302+
## References
303+
304+
Links to external references, discussions or RFCs
305+
306+
## Rejected Features
307+
308+
Keep this updated with features that were discussed on the mail lists.

0 commit comments

Comments
 (0)