Dear PHP Internals,
I am writing to propose a new feature for PHP that introduces the
concept of structs. This feature aims to provide a more concise and
expressive way to define and work with immutable data structures. Below
is a detailed description of the proposed syntax, usage, and behavior.
Syntax
struct Data
{
string $title;
Status $status;
?DateTimeImmutable $publishedAt = null;
}
The Data struct is essentially represented as a readonly class with a
constructor as follows:
readonly class Data
{
public function __construct(
public string $title,
public Status $status,
public ?DateTimeImmutable $publishedAt = null,
) {}
}
Assertions
The Data struct will always be readonly.
It has no methods besides the constructor.
Constructors
The Data struct can be constructed in three different ways, each of
which allows for named or positional arguments, which can be mixed:
1.1 Class like
$data = new Data('title', Status::PUBLISHED, new DateTimeImmutable());
1.2 Class like (Named Syntax)
$data = new Data(title: 'title', status: Status::PUBLISHED, publishedAt:
new DateTimeImmutable());
2.1 Proposed struct initialization syntax (Positional Arguments)
$data = Data{'title', Status::PUBLISHED, new DateTimeImmutable()};
2.2 Proposed struct initialization syntax (Named Syntax)
$data = Data{title: 'title', status: Status::PUBLISHED, publishedAt: new
DateTimeImmutable()};
3.1 Anonymous Struct (Named Arguments)
$data = struct {
string $title;
Status $status;
?DateTimeImmutable $publishedAt = null;
}('title', Status::PUBLISHED, new DateTimeImmutable());
3.2 Anonymous Struct (Named Arguments - Named Syntax)
$data = struct {
string $title;
Status $status;
?DateTimeImmutable $publishedAt = null;
}(title: 'title', status: Status::PUBLISHED, publishedAt: new
DateTimeImmutable());
Nesting
The proposed feature also supports nesting of structs. For example:
final class HasNestedStruct
{
NestedStruct {
string $title;
Status $status;
?DateTimeImmutable $publishedAt = null;
};
public function __construct(
public string $string,
public Data $normalStruct,
public NestedStruct $nestedStruct = NestedStruct{'title',
Status::PUBLISHED, new DateTimeImmutable()},
public struct InlineNamed { int $x} $inlineNamed = {x: 1},
public { int $x, int $y} $inlineAnonymous = {x: 1, y: 2},
) {}
}
This proposal aims to enhance the readability and maintainability of
code by providing a more concise and expressive way to work with
immutable data structures in PHP.
I believe this feature will be a valuable addition to the language as it
not only opens the door for future enhancements (eg. typed json
deserialization, etc.), but should also help reduce reliance on arrays
by providing a more expressive alternative.
Your feedback and suggestions are highly appreciated, and we look
forward to discussing this proposal further within the PHP internals
community.
Sincerely
Lanre
Dear PHP Internals,
I am writing to propose a new feature for PHP that introduces the
concept of structs. This feature aims to provide a more concise and
expressive way to define and work with immutable data structures. Below
is a detailed description of the proposed syntax, usage, and behavior.Syntax
struct Data
{
string $title;
Status $status;
?DateTimeImmutable $publishedAt = null;
}
The Data struct is essentially represented as a readonly class with a
constructor as follows:readonly class Data
{
public function __construct(
public string $title,
public Status $status,
public ?DateTimeImmutable $publishedAt = null,
) {}
}
Assertions
The Data struct will always be readonly.
It has no methods besides the constructor.
Constructors
The Data struct can be constructed in three different ways, each of
which allows for named or positional arguments, which can be mixed:1.1 Class like
$data = new Data('title', Status::PUBLISHED, new DateTimeImmutable());1.2 Class like (Named Syntax)
$data = new Data(title: 'title', status: Status::PUBLISHED, publishedAt:
new DateTimeImmutable());2.1 Proposed struct initialization syntax (Positional Arguments)
$data = Data{'title', Status::PUBLISHED, new DateTimeImmutable()};2.2 Proposed struct initialization syntax (Named Syntax)
$data = Data{title: 'title', status: Status::PUBLISHED, publishedAt: new
DateTimeImmutable()};3.1 Anonymous Struct (Named Arguments)
$data = struct {
string $title;
Status $status;
?DateTimeImmutable $publishedAt = null;
}('title', Status::PUBLISHED, new DateTimeImmutable());
3.2 Anonymous Struct (Named Arguments - Named Syntax)$data = struct {
string $title;
Status $status;
?DateTimeImmutable $publishedAt = null;
}(title: 'title', status: Status::PUBLISHED, publishedAt: new
DateTimeImmutable());
Nesting
The proposed feature also supports nesting of structs. For example:final class HasNestedStruct
{
NestedStruct {
string $title;
Status $status;
?DateTimeImmutable $publishedAt = null;
};public function __construct( public string $string, public Data $normalStruct, public NestedStruct $nestedStruct = NestedStruct{'title',
Status::PUBLISHED, new DateTimeImmutable()},
public struct InlineNamed { int $x} $inlineNamed = {x: 1},
public { int $x, int $y} $inlineAnonymous = {x: 1, y: 2},
) {}
}
This proposal aims to enhance the readability and maintainability of
code by providing a more concise and expressive way to work with
immutable data structures in PHP.
I believe this feature will be a valuable addition to the language as it
not only opens the door for future enhancements (eg. typed json
deserialization, etc.), but should also help reduce reliance on arrays
by providing a more expressive alternative.Your feedback and suggestions are highly appreciated, and we look
forward to discussing this proposal further within the PHP internals
community.Sincerely
Lanre--
To unsubscribe, visit: https://www.php.net/unsub.php
FWIW (and it isn't worth much), I'm not a fan of the braces styles
(2.1, 2.2) as it is very non-php-ish.
It'd be great to still have methods, e.g., $data->isAfter($date) or
something. Otherwise, it isn't very useful except as typed arrays,
without any of the usefulness of the array functions.
Also, what if I want a new $data object but with slightly different
property values?
If you want a new data object with slightly different values
struct Data2 extends Data
{
{string $t, array $meta} $title; // Overwrite parent type
Status $status;
{int $x, int $y} $inlineAnonymous = {x: 1, y: 2};
}
Regarding array functions and serialization, there are two approaches:
ArrayAccess: We can implement array functions similar to ArrayObject, making it possible to use array functions on readonly structs.
Conversion to/from Indexed Array: Since these structs are restricted to a default constructor with named/positional arguments, we can easily convert to and from indexed arrays. JSON serialization can also be supported with the property names as keys.
For example:
foreach (Data as ($propertyName =>)? $property) {} //not real syntax, but you get the idea
However, it's important to note that readonly structs are not meant to replace arrays or lists and, ultimately, are not arrays.
Allowing Methods in Structs:
Initially, I suggested that readonly structs have no methods besides the constructor. However, upon further consideration, I believe it may be beneficial to allow methods in structs. Even PHP enums allow methods, and considering this, I am open to discussing and potentially having a vote on allowing methods within structs as part of the RFC discussion.
Cheers,
Lanre
Dear PHP Internals,
I am writing to propose a new feature for PHP that introduces the
concept of structs. This feature aims to provide a more concise and
expressive way to define and work with immutable data structures. Below
is a detailed description of the proposed syntax, usage, and behavior.Syntax
struct Data
{
string $title;
Status $status;
?DateTimeImmutable $publishedAt = null;
}
The Data struct is essentially represented as a readonly class with a
constructor as follows:readonly class Data
{
public function __construct(
public string $title,
public Status $status,
public ?DateTimeImmutable $publishedAt = null,
) {}
}
Assertions
The Data struct will always be readonly.
It has no methods besides the constructor.
Constructors
The Data struct can be constructed in three different ways, each of
which allows for named or positional arguments, which can be mixed:1.1 Class like
$data = new Data('title', Status::PUBLISHED, new DateTimeImmutable());1.2 Class like (Named Syntax)
$data = new Data(title: 'title', status: Status::PUBLISHED, publishedAt:
new DateTimeImmutable());2.1 Proposed struct initialization syntax (Positional Arguments)
$data = Data{'title', Status::PUBLISHED, new DateTimeImmutable()};2.2 Proposed struct initialization syntax (Named Syntax)
$data = Data{title: 'title', status: Status::PUBLISHED, publishedAt: new
DateTimeImmutable()};3.1 Anonymous Struct (Named Arguments)
$data = struct {
string $title;
Status $status;
?DateTimeImmutable $publishedAt = null;
}('title', Status::PUBLISHED, new DateTimeImmutable());
3.2 Anonymous Struct (Named Arguments - Named Syntax)$data = struct {
string $title;
Status $status;
?DateTimeImmutable $publishedAt = null;
}(title: 'title', status: Status::PUBLISHED, publishedAt: new
DateTimeImmutable());
Nesting
The proposed feature also supports nesting of structs. For example:final class HasNestedStruct
{
NestedStruct {
string $title;
Status $status;
?DateTimeImmutable $publishedAt = null;
};public function __construct( public string $string, public Data $normalStruct, public NestedStruct $nestedStruct = NestedStruct{'title',
Status::PUBLISHED, new DateTimeImmutable()},
public struct InlineNamed { int $x} $inlineNamed = {x: 1},
public { int $x, int $y} $inlineAnonymous = {x: 1, y: 2},
) {}
}
This proposal aims to enhance the readability and maintainability of
code by providing a more concise and expressive way to work with
immutable data structures in PHP.
I believe this feature will be a valuable addition to the language as it
not only opens the door for future enhancements (eg. typed json
deserialization, etc.), but should also help reduce reliance on arrays
by providing a more expressive alternative.Your feedback and suggestions are highly appreciated, and we look
forward to discussing this proposal further within the PHP internals
community.Sincerely
Lanre--
To unsubscribe, visit: https://www.php.net/unsub.php
FWIW (and it isn't worth much), I'm not a fan of the braces styles
(2.1, 2.2) as it is very non-php-ish.It'd be great to still have methods, e.g., $data->isAfter($date) or
something. Otherwise, it isn't very useful except as typed arrays,
without any of the usefulness of the array functions.Also, what if I want a new $data object but with slightly different
property values?
Allowing Methods in Structs:
Initially, I suggested that readonly structs have no methods besides the
constructor. However, upon further consideration, I believe it may be
beneficial to allow methods in structs. Even PHP enums allow methods, and
considering this, I am open to discussing and potentially having a vote on
allowing methods within structs as part of the RFC discussion.
At that point, a struct would be no different from a readonly class with
public properties, meaning we'd have two ways to accomplish the same thing.
Considering that a readonly class can already implement interfaces such as
JsonSerializable, Iterator, and ArrayAccess, they already solve the bulk of
the issues that have been raised in this thread.
The main thing of interest that I could see from your proposal was the
ability to nest a struct in another struct or a class definition, as that
could provide some interesting type safety without the need to declare new
classes. This could be even more interesting if it allowed any struct
that fulfilled the same definitions:
class Post
{
public function __construct(
public readonly struct $author {
string $name;
UriInterface $uri;
UriInterface $avatarUri;
},
public readonly string $title,
public readonly string $content,
public readonly DateTimeInterface $publicationDate,
) {
}
// ...
}
struct User
{
string $name;
UriInterface $uri;
UriInterface $avatarUri;
string $email;
}
$post = new Post(new User(...), ...);
The idea here is that User fulfills the $author struct definition in the
Post class; it just has additional properties. If the proposal would
allow that, this could be pretty powerful.
I would personally stay away from solving the conversion to and from arrays
and allowing methods. If users need those, they can either use
de/serialization libraries or readonly classes, respectively. Not including
them in the initial proposal keeps it more targeted, and demonstrates a
language feature that does not currently exist.
I really like the idea of making structs opaque in that sense,
definitely going to put it to vote in the RFC. I agree with the decision
to exclude methods from structs, as they are intended to be pure data
types and If methods are required, classes can be used instead. I guess
based on the response from this list i might not need to put it up to a
vote.
Allowing Methods in Structs:
Initially, I suggested that readonly structs have no methods besides the
constructor. However, upon further consideration, I believe it may be
beneficial to allow methods in structs. Even PHP enums allow methods, and
considering this, I am open to discussing and potentially having a vote on
allowing methods within structs as part of the RFC discussion.At that point, a struct would be no different from a readonly class with
public properties, meaning we'd have two ways to accomplish the same thing.
Considering that a readonly class can already implement interfaces such as
JsonSerializable, Iterator, and ArrayAccess, they already solve the bulk of
the issues that have been raised in this thread.The main thing of interest that I could see from your proposal was the
ability to nest a struct in another struct or a class definition, as that
could provide some interesting type safety without the need to declare new
classes. This could be even more interesting if it allowed any struct
that fulfilled the same definitions:class Post { public function __construct( public readonly struct $author { string $name; UriInterface $uri; UriInterface $avatarUri; }, public readonly string $title, public readonly string $content, public readonly DateTimeInterface $publicationDate, ) { } // ... } struct User { string $name; UriInterface $uri; UriInterface $avatarUri; string $email; } $post = new Post(new User(...), ...);
The idea here is that User fulfills the $author struct definition in the
Post class; it just has additional properties. If the proposal would
allow that, this could be pretty powerful.I would personally stay away from solving the conversion to and from arrays
and allowing methods. If users need those, they can either use
de/serialization libraries or readonly classes, respectively. Not including
them in the initial proposal keeps it more targeted, and demonstrates a
language feature that does not currently exist.
2023-09-08 15:12 GMT+02:00, Lanre Waju lanre@online-presence.ca:
Dear PHP Internals,
I am writing to propose a new feature for PHP that introduces the
concept of structs. This feature aims to provide a more concise and
expressive way to define and work with immutable data structures. Below
is a detailed description of the proposed syntax, usage, and behavior.Syntax
struct Data
{
string $title;
Status $status;
?DateTimeImmutable $publishedAt = null;
}
The Data struct is essentially represented as a readonly class with a
constructor as follows:readonly class Data
{
public function __construct(
public string $title,
public Status $status,
public ?DateTimeImmutable $publishedAt = null,
) {}
}
Assertions
The Data struct will always be readonly.
It has no methods besides the constructor.
Constructors
The Data struct can be constructed in three different ways, each of
which allows for named or positional arguments, which can be mixed:1.1 Class like
$data = new Data('title', Status::PUBLISHED, new DateTimeImmutable());1.2 Class like (Named Syntax)
$data = new Data(title: 'title', status: Status::PUBLISHED, publishedAt:
new DateTimeImmutable());2.1 Proposed struct initialization syntax (Positional Arguments)
$data = Data{'title', Status::PUBLISHED, new DateTimeImmutable()};2.2 Proposed struct initialization syntax (Named Syntax)
$data = Data{title: 'title', status: Status::PUBLISHED, publishedAt: new
DateTimeImmutable()};3.1 Anonymous Struct (Named Arguments)
$data = struct {
string $title;
Status $status;
?DateTimeImmutable $publishedAt = null;
}('title', Status::PUBLISHED, new DateTimeImmutable());
3.2 Anonymous Struct (Named Arguments - Named Syntax)$data = struct {
string $title;
Status $status;
?DateTimeImmutable $publishedAt = null;
}(title: 'title', status: Status::PUBLISHED, publishedAt: new
DateTimeImmutable());
Nesting
The proposed feature also supports nesting of structs. For example:final class HasNestedStruct
{
NestedStruct {
string $title;
Status $status;
?DateTimeImmutable $publishedAt = null;
};public function __construct( public string $string, public Data $normalStruct, public NestedStruct $nestedStruct = NestedStruct{'title',
Status::PUBLISHED, new DateTimeImmutable()},
public struct InlineNamed { int $x} $inlineNamed = {x: 1},
public { int $x, int $y} $inlineAnonymous = {x: 1, y: 2},
) {}
}
This proposal aims to enhance the readability and maintainability of
code by providing a more concise and expressive way to work with
immutable data structures in PHP.
I believe this feature will be a valuable addition to the language as it
not only opens the door for future enhancements (eg. typed json
deserialization, etc.), but should also help reduce reliance on arrays
by providing a more expressive alternative.Your feedback and suggestions are highly appreciated, and we look
forward to discussing this proposal further within the PHP internals
community.Sincerely
Lanre--
To unsubscribe, visit: https://www.php.net/unsub.php
Aren't structs value objects in C#? Might be some semantic confusion
there, in choice of words.
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/struct
"Structure types have value semantics".
Java uses the "record" keyword. Worth taking a look at, maybe?
Olle
Hi internals!
I think it can be useful for structured data obtained from decoding JSONs
or from a relational database. However, maybe there is an even better idea,
which combines the best of two worlds: the world of your structs RFC, and
native PHP arrays - something like "array shapes".
For instance, taking the syntax from your proposed RFC (but perhaps the
keyword changed to shape
, or perhaps array class
to reuse keywords -
let's not bikeshed here) to define a shape, and adding a function like
array_shape_coerce($array, $shapeType)
which internally marks an array to
be of a defined shape if it matches its definition (throws an exception
otherwise, or perhaps returns true/false value or an enum for
success/failure) and allows it to be passed where a certain shape type is
expected. I think this could be a better approach than pure structs because
it leaves all array-based PHP machinery available to such "structs", like
array_map()
, array_filter()
etc.
There is an open question of whether array coerced to shapes can be
modified, and if yes, how to react to modifications. Perhaps the easiest
solution is to have another function along the lines
array_shape_seal($array, $shapeType)
which not only marks array to be of
shape (if the given array matches given shape), but also permits only such
modifications which keep the array to be of this shape. And if the regular
array_shape_coerce
is used, then any modifications are permitted, but the
array "loses" its shape.
Regards,
Illia / someniatko
Dear PHP Internals,
I am writing to propose a new feature for PHP that introduces the
concept of structs. This feature aims to provide a more concise and
expressive way to define and work with immutable data structures. Below
is a detailed description of the proposed syntax, usage, and behavior.Syntax
struct Data
{
string $title;
Status $status;
?DateTimeImmutable $publishedAt = null;
}
The Data struct is essentially represented as a readonly class with a
constructor as follows:readonly class Data
{
public function __construct(
public string $title,
public Status $status,
public ?DateTimeImmutable $publishedAt = null,
) {}
}
Assertions
The Data struct will always be readonly.
It has no methods besides the constructor.
Constructors
The Data struct can be constructed in three different ways, each of
which allows for named or positional arguments, which can be mixed:1.1 Class like
$data = new Data('title', Status::PUBLISHED, new DateTimeImmutable());1.2 Class like (Named Syntax)
$data = new Data(title: 'title', status: Status::PUBLISHED, publishedAt:
new DateTimeImmutable());2.1 Proposed struct initialization syntax (Positional Arguments)
$data = Data{'title', Status::PUBLISHED, new DateTimeImmutable()};2.2 Proposed struct initialization syntax (Named Syntax)
$data = Data{title: 'title', status: Status::PUBLISHED, publishedAt: new
DateTimeImmutable()};3.1 Anonymous Struct (Named Arguments)
$data = struct {
string $title;
Status $status;
?DateTimeImmutable $publishedAt = null;
}('title', Status::PUBLISHED, new DateTimeImmutable());
3.2 Anonymous Struct (Named Arguments - Named Syntax)$data = struct {
string $title;
Status $status;
?DateTimeImmutable $publishedAt = null;
}(title: 'title', status: Status::PUBLISHED, publishedAt: new
DateTimeImmutable());
Nesting
The proposed feature also supports nesting of structs. For example:final class HasNestedStruct
{
NestedStruct {
string $title;
Status $status;
?DateTimeImmutable $publishedAt = null;
};public function __construct( public string $string, public Data $normalStruct, public NestedStruct $nestedStruct = NestedStruct{'title',
Status::PUBLISHED, new DateTimeImmutable()},
public struct InlineNamed { int $x} $inlineNamed = {x: 1},
public { int $x, int $y} $inlineAnonymous = {x: 1, y: 2},
) {}
}
This proposal aims to enhance the readability and maintainability of
code by providing a more concise and expressive way to work with
immutable data structures in PHP.
I believe this feature will be a valuable addition to the language as it
not only opens the door for future enhancements (eg. typed json
deserialization, etc.), but should also help reduce reliance on arrays
by providing a more expressive alternative.Your feedback and suggestions are highly appreciated, and we look
forward to discussing this proposal further within the PHP internals
community.Sincerely
Lanre--
To unsubscribe, visit: https://www.php.net/unsub.php
I'm all data only for structures, but I really dont think they should be
readonly.
This type of modifier should be down to the developer to decide.
If a developer only needs a data struct,
they can just struct {}
but if they need a readonly struct,
they should specify the struct as readonly struct {}
Structs can have many uses, you will eliminate 99% of them by forcing
structs to
be readonly. No other languages force this on the developer, PHP shouldn't
either.
This type of limitation would make structs DOA for most developers.
Dear PHP Internals,
I am writing to propose a new feature for PHP that introduces the
concept of structs. This feature aims to provide a more concise and
expressive way to define and work with immutable data structures. Below
is a detailed description of the proposed syntax, usage, and behavior.Syntax
struct Data
{
string $title;
Status $status;
?DateTimeImmutable $publishedAt = null;
}
The Data struct is essentially represented as a readonly class with a
constructor as follows:readonly class Data
{
public function __construct(
public string $title,
public Status $status,
public ?DateTimeImmutable $publishedAt = null,
) {}
}
These as the most basic examples of what you're proposing are barely
different expressions in length, let alone anything else. I wouldn't say
the first example is particularly more helpful, more readable, more concise
or conceptually more expressive than the second. The rest of your examples
are similarly either very minor savings of a few characters shaved off here
and there, or just as verbose as instantiating a class with a constructor
anyway.
I'm not convinced by the rationale that this would be a new feature
worthwhile for improved expression or concision. I can see a potential
benefit in these "structs" (not sure that's the term//keyword I'd choose,
given it has different meanings in other languages) though, as a kind of
template for properly structured arrays, with a built-in ability to cast
them to arrays or treat them as arrays/iterables. This would potentially
help give some of the power we're missing by not having generics. So it
wouldn't just be equivalent to a shorthand for a readonly class with a
constructor, but one which also satisfied a couple of interfaces with the
implementation automatically provided.
That's my initial reaction/two cents.
-Dave
Dear PHP Internals,
I am writing to propose a new feature for PHP that introduces the
concept of structs. This feature aims to provide a more concise and
expressive way to define and work with immutable data structures. Below
is a detailed description of the proposed syntax, usage, and behavior.Syntax
struct Data
{
string $title;
Status $status;
?DateTimeImmutable $publishedAt = null;
}
The Data struct is essentially represented as a readonly class with a
constructor as follows:readonly class Data
{
public function __construct(
public string $title,
public Status $status,
public ?DateTimeImmutable $publishedAt = null,
) {}
}These as the most basic examples of what you're proposing are barely
different expressions in length, let alone anything else. I wouldn't say
the first example is particularly more helpful, more readable, more concise
or conceptually more expressive than the second. The rest of your examples
are similarly either very minor savings of a few characters shaved off here
and there, or just as verbose as instantiating a class with a constructor
anyway.I'm not convinced by the rationale that this would be a new feature
worthwhile for improved expression or concision. I can see a potential
benefit in these "structs" (not sure that's the term//keyword I'd choose,
given it has different meanings in other languages) though, as a kind of
template for properly structured arrays, with a built-in ability to cast
them to arrays or treat them as arrays/iterables. This would potentially
help give some of the power we're missing by not having generics. So it
wouldn't just be equivalent to a shorthand for a readonly class with a
constructor, but one which also satisfied a couple of interfaces with the
implementation automatically provided.That's my initial reaction/two cents.
-Dave
Indeed,
When I first saw this, I saw them as typed-arrays, and it would be
great if they were treated as such. Essentially, type-check, then
stored in a regular array and as far as the engine is concerned,
that's what they are (meaning they could be used when a library
expects an array, or gradually added to legacy code that passes around
opaque arrays). This could add a tremendous amount of expressibility
to PHP. This would mean a struct with the same properties and values
are equal. It'd be even awesome if you could cast between structs (and
to arrays), as long as the struct/array being casted from contained
all the right properties/values. For example:
$myStruct1 = (MyStruct1) $myStruct2; // myStruct2 could even be an
array, or another struct
is the same as:
$myStruct1 = new MyStruct1(...$myStruct2);
except ignoring extra properties/values. Maybe the casting could even
"hide" the extra properties in a private variable so that if it is
casted to an array, or serialized as json, or casted to a different
struct, the original data shows up.
As far as being able to increase the expression of regular 'ole PHP,
it would allow libraries to get rid of opaque arrays with hidden
options (looking at you Guzzle), and essentially not change but a few
lines of code, while making it easy for users to correctly use the
library.
Anyway, just thinking out loud. I have no idea if the original author
has an intent for any of this, but it could be awesome.
Robert Landers
Software Engineer
Utrecht NL
Dear PHP Internals,
I am writing to propose a new feature for PHP that introduces the
concept of structs. This feature aims to provide a more concise and
expressive way to define and work with immutable data structures. Below
is a detailed description of the proposed syntax, usage, and behavior.Syntax
struct Data
{
string $title;
Status $status;
?DateTimeImmutable $publishedAt = null;
}
The Data struct is essentially represented as a readonly class with a
constructor as follows:readonly class Data
{
public function __construct(
public string $title,
public Status $status,
public ?DateTimeImmutable $publishedAt = null,
) {}
}
Assertions
The Data struct will always be readonly.
It has no methods besides the constructor.
Constructors
The Data struct can be constructed in three different ways, each of
which allows for named or positional arguments, which can be mixed:1.1 Class like
$data = new Data('title', Status::PUBLISHED, new DateTimeImmutable());1.2 Class like (Named Syntax)
$data = new Data(title: 'title', status: Status::PUBLISHED, publishedAt:
new DateTimeImmutable());2.1 Proposed struct initialization syntax (Positional Arguments)
$data = Data{'title', Status::PUBLISHED, new DateTimeImmutable()};2.2 Proposed struct initialization syntax (Named Syntax)
$data = Data{title: 'title', status: Status::PUBLISHED, publishedAt: new
DateTimeImmutable()};3.1 Anonymous Struct (Named Arguments)
$data = struct {
string $title;
Status $status;
?DateTimeImmutable $publishedAt = null;
}('title', Status::PUBLISHED, new DateTimeImmutable());
3.2 Anonymous Struct (Named Arguments - Named Syntax)$data = struct {
string $title;
Status $status;
?DateTimeImmutable $publishedAt = null;
}(title: 'title', status: Status::PUBLISHED, publishedAt: new
DateTimeImmutable());
Nesting
The proposed feature also supports nesting of structs. For example:final class HasNestedStruct
{
NestedStruct {
string $title;
Status $status;
?DateTimeImmutable $publishedAt = null;
};public function __construct(
public string $string,
public Data $normalStruct,
public NestedStruct $nestedStruct = NestedStruct{'title',
Status::PUBLISHED, new DateTimeImmutable()},
public struct InlineNamed { int $x} $inlineNamed = {x: 1},
public { int $x, int $y} $inlineAnonymous = {x: 1, y: 2},
) {}
}
This proposal aims to enhance the readability and maintainability of
code by providing a more concise and expressive way to work with
immutable data structures in PHP.
I believe this feature will be a valuable addition to the language as it
not only opens the door for future enhancements (eg. typed json
deserialization, etc.), but should also help reduce reliance on arrays
by providing a more expressive alternative.Your feedback and suggestions are highly appreciated, and we look
forward to discussing this proposal further within the PHP internals
community.Sincerely
Lanre
As I have stated in the past, I am firmly opposed to anemic structs. They offer no benefit, much confusion, and more work for future RFCs.
The core concept -- that service objects and data objects are separate creatures that should not be comingled -- I fully agree with and advocate for. If I were writing PHP from scratch today, I would probably design it with separate constructs, or take a cue from Go/Rust and eliminate classes all together per se, as they just confuse matters. However, we are dealing with PHP as it exists today, and an entirely separate limited construct just doesn't make sense.
I also want to make clear that I am 1000% in favor of structured, typed data. The use of associative arrays as a pseudo data structure is the weakest part of PHP, and the more we can move people away from that towards more formally typed data, the better. For that reason, making it trivial to cast between an associative array and a structured object (as a few others in the thread have suggested) is a bad feature, because it further reinforces the idea that an associative array is "just as good" as making a defined type. This is simply flat out false, and we should avoid language features that pretend that it is true.
That said, as of PHP 8, we already have perfectly good struct-ish data structure: Classes with promoted properties and named arguments. As of PHP 8.2, the entire class can be declared readonly with a single keyword. For 95% of use cases, this is completely adequate as a struct-like structure:
readonly class Person
{
public function __construct(
public string $first,
public Status $last,
public ?DateTimeImmutable $birthday = null,
) {}
}
$p = new Person(first: 'Larry', last: 'Garfield');
So where does it fall short?
-
The proposal above suggests it's that it allows methods. Why is that an issue? Why are methods a problem on a data-centric object? This is never explained, and I don't believe it to be true. While methods that call out to other service objects would definitely be bad juju, a fullname() method on the above class poses no problems whatsoever. There is no theoretical purity being protected by disallowing methods. By the same logic, would structs also forbid property hooks, assuming those pass? I would hope not, as data objects are where those are most useful. In fact, I'd go a step further and note that a readonly struct that disallows methods precludes the "with-er" style of evolving an object, so if you want "the same thing but with this one change", you have to completely recreate a new struct from scratch. This is a worse experience in every way.
-
The proposal above suggests that it's because the
new
keyword is needed, and proposes both positional and named function-esque syntax, making it look more like Kotlin or Rust where there is nonew
keyword and the class name is itself the constructor. I will agree thatnew
is clumsy in many cases, particularly in compound expressions, but that's not an issue unique to data-centric objects. If we were to come up with some alternate constructor invocation to make it easier for data-centric objects, it would be equally useful on non-data objects as well. There's no reason to make it specific to just data structs. (I am also not certain if the parser could even handle that, since functions and classes are in a different keyspace currently so if both a class foo and function foo are defined,foo()
is ambiguous.) -
The proposal suggests nested the ability to have nested struct definitions. I can see where this is useful, certainly. However... I can also see where it's useful on service objects, too. Many languages have such a feature, often called "inner classes," and it works just fine on service objects as well as data objects. Inner classes would be an interesting feature in itself that would be worth its own RFC (I won't guarantee that I'd support it, depending on the details, but I am quite open to considering it), but there's no good reason to limit that functionality to just data classes.
-
The proposal implies that structs should be always readonly. As noted above, a readonly class is trivial to define now. Moreover, while I am an outspoken proponent of immutable data structures they are not appropriate in all cases. PSR-14 events, for example, are deliberately mutable because, given PHP's design, making them immutable would have required a lot of extra work from anyone writing a listener for very little benefit. Entities are another example of a data-object that logically needs to be mutable. Mutable data-centric product types have their place, and this approach would preclude that.
-
MWOP suggested in a reply that allowing a struct to conform to a struct definition structurally by the properties it has would be useful. Potentially yes. However, interface properties, part of the hooks RFC, would get us to almost the same place without the weird world-splitting between structs and classes.
-
When dealing with a mutable data value, objects pass by handle (feels like reference even if it's not), but data feels like it should pass by value. Valid! This is a long-standing gripe, and the growth of with-er style value objects (PSR-7 et al) is in a large part to avoid that risk of "spooky action at a distance." However... the above proposal does not address this at all!
I would argue that point 6 is the only valid argument for a separate construct from classes as they already exist. But there is no need to create a whole other construct (a very significant implementation lift) to achieve that. All that would need is a flag/marker on the class to indicate that it should use data-like passing semantics. Kotlin has a good example here, where you can declare a class a "data" class by just adding the data
keyword. That has a number of implications in Kotlin (many of which are not relevant for us), but in our case it would mean either to pass the object by value, or to automatically clone it every time it is passed. (The two would be almost the same to the end user, but likely have different implementation challenges. I cannot speak to what those would be personally, but it's an implementation detail not relevant for now.)
That very small change, when combined with all of the other improvements to the language in recent years, gives us all the benefits of data-centric structures without any of the downsides of a completely new construct. It would also allow the developer to opt-in to the class being readonly or not, as the situation requires. The downsides of a new construct include:
-
It would either have to be a new zval type, which is a ton of work, or built on classes, in which case you're fighting against all of the stuff classes do.
-
Which stuff that classes do should be supported by the very-similar syntax? Methods? Attributes? Can you clone it? Do you get a __clone() override if you do? Are traits supported? How does equality work? I can see an argument for where product types (which is what we're talking about) would benefit from all of the above. So we either cripple structs without useful features, or it becomes a lot of work to end up with "objects that pass funny." We can get "objects that pass funny" with a lot less effort with just a
data
keyword flag. -
If structs are entirely separate from objects, then any time we add a new feature to objects we'll have to debate, again, if that feature should be added to structs as well. And if so, we're looking at more work for the RFC implementer for very little gain. Or, we'll add a feature to structs (like inner classes) and then ask for it on classes, too, and again have double the work and double the debate.
-
The Reflection API is complicated enough as is, without having to deal with a whole other type of type. As someone who maintains a serializer, that would be a lot of work for me to support, with no actual benefit.
In short, there's only two versions of structs that could realistically end up existing: Crippled in some way, and "objects that pass funny."
So if what we really want are objects that pass by value, let's just implement by-value opt-in objects and be done with it. It's much less work, much more powerful, and avoids many more debates in the future.
--Larry Garfield