Hello,
Long time listener, first time caller here.
Last week I had a shower thought about how it would be neat if we could declare array shapes in PHP, and use them to provide structural interfaces for arrays.
After all, arrays are one of PHPs strongest features, and anyone who has ever tried to use arrays/maps in other languages after getting used to the versatility of PHP arrays will have to admit that nothing comes close to their flexibility and ease of use.
However, ironically, most of my friends, colleagues and fellow PHP devs usually tend to shun PHP arrays, which has led to the widespread use of Collection classes and the like. The usual argument is that arrays don’t provide any way to declare their structure, and thus don’t provide the ergonomics of autocompletion (even if you declare the array shapes with docblock annotations IDE support is iffy at best) and don’t provide any form of runtime type checking.
To me, providing structural interfaces using shapes seems like a perfect fit to the flexibility of arrays. I imagine you'd define them in a similar way to an interface, declaring and typing array elements rather than methods or property hooks. They'd provide a way to validate the contents of an array at runtime, and offer static analyzers and IDEs a robust way to do type inspections and provide autocompletions for arrays.
Of course my first assumption was that other people much smarter than me probably already had this idea, so I started doing my due diligence and discovered that a draft for a Shapes RFC was written by Kacper Donat back in 2021 [1], but unfortunately seems to have since been abandoned. This is a shame because it lines up about 90% with my ideas of what array shapes should look like in PHP.
I'm unable to find any reference to this RFC on the PHP Wiki nor could I find any threads discussing it in the PHP-Internals archive, so I assume they simply never got around to completing it, or there was some discussion elsewhere that prompted them to abandon the RFC.
Anyway, I would love to try and push this idea forward – either by contacting Kacpar, or writing my own RFC – and have a shot at implementing a proof of concept, but first I would like to get a feel for whether this proposal would find much footing here.
So please let me know how you feel about the idea of array shapes in PHP, and perhaps read the draft written by Kacper Donat [1] for a much more eloquent example of what I'm trying to propose here.
Thank you all very much in advance.
Kindest regards,
Alwin Garside
Generics
Hello,
Long time listener, first time caller here.
Last week I had a shower thought about how it would be neat if we could
declare array shapes in PHP, and use them to provide structural interfaces
for arrays.After all, arrays are one of PHPs strongest features, and anyone who has
ever tried to use arrays/maps in other languages after getting used to the
versatility of PHP arrays will have to admit that nothing comes close to
their flexibility and ease of use.However, ironically, most of my friends, colleagues and fellow PHP devs
usually tend to shun PHP arrays, which has led to the widespread use of
Collection classes and the like. The usual argument is that arrays don’t
provide any way to declare their structure, and thus don’t provide the
ergonomics of autocompletion (even if you declare the array shapes with
docblock annotations IDE support is iffy at best) and don’t provide any
form of runtime type checking.To me, providing structural interfaces using shapes seems like a perfect
fit to the flexibility of arrays. I imagine you'd define them in a similar
way to an interface, declaring and typing array elements rather than
methods or property hooks. They'd provide a way to validate the contents of
an array at runtime, and offer static analyzers and IDEs a robust way to do
type inspections and provide autocompletions for arrays.Of course my first assumption was that other people much smarter than me
probably already had this idea, so I started doing my due diligence and
discovered that a draft for a Shapes RFC was written by Kacper Donat back
in 2021 [1], but unfortunately seems to have since been abandoned. This is
a shame because it lines up about 90% with my ideas of what array shapes
should look like in PHP.I'm unable to find any reference to this RFC on the PHP Wiki nor could I
find any threads discussing it in the PHP-Internals archive, so I assume
they simply never got around to completing it, or there was some discussion
elsewhere that prompted them to abandon the RFC.Anyway, I would love to try and push this idea forward – either by
contacting Kacpar, or writing my own RFC – and have a shot at implementing
a proof of concept, but first I would like to get a feel for whether this
proposal would find much footing here.So please let me know how you feel about the idea of array shapes in PHP,
and perhaps read the draft written by Kacper Donat [1] for a much more
eloquent example of what I'm trying to propose here.Thank you all very much in advance.
Kindest regards,
Alwin Garside
Generics
Yes, I am aware that generics are being worked on. However, generics wouldn't provide structural interfaces for arrays. At most it would provide a way to infer/define generic types for array keys and/or values.
I guess I should've provided a code example straight away. Here's what I have in mind:
declare(strict_types=1);
shape MyArray {
"id": int;
"name": string;
}
function doArrayStuff(MyArray $array) { ... }
doArrayStuff(['id' => 1, 'name' => 'Alwin']); // Works, array conforms to shape
doArrayStuff(['id' => '1', 'name' => 'Alwin']); // Fails, 'id' should be int
The best that Generics would be able to offer for arrays is:
function doArrayStuff(array<string, int|string> $array) { ... }
doArrayStuff(['id' => 1, 'name' => 'Alwin']); // Works
doArrayStuff(['id' => '1', 'name' => 'Alwin']); // Works
Alwin
Hi
Anyway, I would love to try and push this idea forward – either by contacting Kacpar, or writing my own RFC – and have a shot at implementing a proof of concept, but first I would like to get a feel for whether this proposal would find much footing here.
I guess the pattern matching RFC could be the solution to what you are
looking for and I suggest to have a look:
https://wiki.php.net/rfc/pattern-matching
and
https://externals.io/message/123701
Best regards
Tim Düsterhus
Anyway, I would love to try and push this idea forward – either by contacting Kacpar, or writing my own RFC – and have a shot at implementing a proof of concept, but first I would like to get a feel for whether this proposal would find much footing here.
I guess the pattern matching RFC could be the solution to what you are looking for and I suggest to have a look:
https://wiki.php.net/rfc/pattern-matching
and
I see! I had skimmed through it before but now upon closer inspection it does seem to fulfill some of my wishes.
However, it seems the ability to actually use pattern matching as a structural interface hinges on whether it will be possible to declare patterns as types, which as of now seems to be under consideration. I may need to throw my two cents into that RFCs thread.
Thanks for the heads up!
Alwin
Hello,
Long time listener, first time caller here.
Last week I had a shower thought about how it would be neat if we could
declare array shapes in PHP, and use them to provide structural
interfaces for arrays.After all, arrays are one of PHPs strongest features, and anyone who
has ever tried to use arrays/maps in other languages after getting used
to the versatility of PHP arrays will have to admit that nothing comes
close to their flexibility and ease of use.However, ironically, most of my friends, colleagues and fellow PHP devs
usually tend to shun PHP arrays, which has led to the widespread use of
Collection classes and the like. The usual argument is that arrays
don’t provide any way to declare their structure, and thus don’t
provide the ergonomics of autocompletion (even if you declare the array
shapes with docblock annotations IDE support is iffy at best) and don’t
provide any form of runtime type checking.
Your friends are correct. Arrays in PHP are an awful thing. They are twice as memory hungry as objects, they provide no static guarantees, and they're a security hole waiting to happen (and has in fact happened).
And since PHP 8.0, we have something equivalent to array shapes already that is vastly more efficient: Constructor promotion.
// This tells me nothing.
$arrayPoint = ['x' => 1, 'y' => 2];
// This tells me everything.
class Point {
public function __construct(
public int $x,
public int $y,
) {}
}
$objectPoint = new Point(x: 1, y: 2);
$objectPoint will use half as much memory as $arrayPoint. (Not meaningful in something this small, but if you have 100 of them...) It provides all the static type validation you can get in PHP right now. It's self-documenting. It works with everything else in the language. And it's already been available for 5 years. And now you can also toss readonly, private(set), or hooks around on it if you feel like it.
This is a solved problem, and the solution is objects. Using arrays as pseudo-objects is the wrong approach 99.9% of the time. Collections are a different matter, as those are real lists.
For more details: https://www.youtube.com/watch?v=nNtulOOZ0GY
All that said!
A side effect of the pattern matching RFC (which we'll be formally proposing real-soon-now-I-swear) is that it effectively gives an array-shape validation check for free.
https://wiki.php.net/rfc/pattern-matching
So yeah, no thank you on a separate array shapes RFC.
--Larry Garfield
Your friends are correct. Arrays in PHP are an awful thing. They are twice as memory hungry as objects, they provide no static guarantees, and they're a security hole waiting to happen (and has in fact happened).
And since PHP 8.0, we have something equivalent to array shapes already that is vastly more efficient: Constructor promotion.
// This tells me nothing.
$arrayPoint = ['x' => 1, 'y' => 2];// This tells me everything.
class Point {
public function __construct(
public int $x,
public int $y,
) {}
}
$objectPoint = new Point(x: 1, y: 2);$objectPoint will use half as much memory as $arrayPoint. (Not meaningful in something this small, but if you have 100 of them...) It provides all the static type validation you can get in PHP right now. It's self-documenting. It works with everything else in the language. And it's already been available for 5 years. And now you can also toss readonly, private(set), or hooks around on it if you feel like it.
This is a solved problem, and the solution is objects. Using arrays as pseudo-objects is the wrong approach 99.9% of the time. Collections are a different matter, as those are real lists.
For more details: https://www.youtube.com/watch?v=nNtulOOZ0GY
All that said!
A side effect of the pattern matching RFC (which we'll be formally proposing real-soon-now-I-swear) is that it effectively gives an array-shape validation check for free.
https://wiki.php.net/rfc/pattern-matching
So yeah, no thank you on a separate array shapes RFC.
--Larry Garfield
I get you. Used analogously to objects, arrays are indeed awful by comparison.
However, arrays are very ergonomic to work with, especially for passing along a list of properties, which is why many frameworks and libraries still use them.
The combination of constructor promotion, named arguments and property hooks (thank you for those btw!) have definitely helped to make classes more usable in this regard, but they are still too "rigid" compared to structural interfaces as in, for example, TypeScript.
Consider if you have several interface defined for a set of options, you'll always need to declare a class to actually implement them.
interface ALotOfOptions {
public int $this { get; }
public ?float $that { get; }
public ?string $then { get; }
public ?string $there { get; }
// [...]
}
interface AdditionalOptions {
public ?string $thisToo { get; }
}
class AllTheOptions implements ALotOfOptions, AdditionalOptions {
public __construct(
public int $this { get; }
public ?float $that { get; }
public ?string $then { get; }
public ?string $there { get; }
// [...]
public ?string $thisToo { get; }
)
}
$options = new AllTheOptions(
this: 42,
that: 1.337,
there: 'here',
// [...]
);
function iWantOptions(ALotOfOptions & AdditionalOptions $options) { ... }
I guess you could do this with anonymous classes, but that doesn't make things any prettier.
With my hypothetical shapes you'd declare a structural interface, and both implement and instantiate it at the same time by initializing an array.
shape ALotOfOptions {
'this': int;
'that': ?float;
'then': ?string;
'there': ?string;
// [...]
}
shape AdditionalOptions {
'thisToo': ?string;
}
$options = [
'this' => 42,
'that' => 1.337,
'there' => 'here',
// [...]
];
function iWantOptions(ALotOfOptions & AdditionalOptions $options) { ... }
As you can see, there is a less boilerplate code, and it becomes easier to combine sets of options. This is a very popular pattern in TypeScript, for example when working with AWS CDK, and in my experience it is very ergonomic to work with.
Are PHP arrays the ideal solution for this? No, not really. But at the moment they are the only realistic option if you want to provide a flexible and ergonomic solution. I know arrays are inferior to objects in terms of type-safety and memory consumption, but they have their legit use cases (besides serving as glorified lists), and often are simply the more ergonomic option. The truth of the matter is, arrays aren't going anywhere anytime soon, and I think it would not be a bad idea to at least make them less treacherous to work with for the people that have legit use cases for them.
Having said all that!
After Tim suggested your RFC I have taken a better look, and patterns are indeed a very interesting proposition. However, for pattern matching to cover my wishes regarding arrays shapes would depend on several features described as future scopes, namely 'Optional array key marker' and 'Patterns as variables/types'. Plus I'm a bit unclear about what the syntax would exactly look like. From what I can gather it would look something like this:
pattern [
'this' => int,
?'that' => float,
?'then' => string,
?'there' => string,
// [...]
] as ALotOfOptions;
pattern [
?'thisToo': string,
] as AdditionalOptions;
I'm not too sure how I feel about the as
keyword at the end... But that's a story for a different thread.
At any rate, it's clear that Pattern Matching makes any array shape RFC completely redundant.
I'll be rooting for the Pattern Matching RFC! Seems like it would be a very powerful addition to PHP. And I hope that the future scopes I mentioned will find their way to their own respective RFC(s) soon after, then everybody will be happy. :-)
Alwin
Hi!
However, arrays are very ergonomic to work with, especially for passing along a list of properties, which is why many frameworks and libraries still use them
I am not agree that arrays must be typed in this way. They won’t be this arrays as they are.
Instead this week I’ve started working on PHP Schema for PHP arrays, such as JSON or YAML schemas.
This is may help you to structure your code if you want to type these types (or vendors).
I’ll make a contract for PHP schema, validator, PHP plugin and some kind of validation online. Hope it’ll cover all the needs.
If it sounds good for you I’d suggest to collaborate and work on these together.
——
Best regards,
Dmitrii Derepko
@xepozz
Hello,
Long time listener, first time caller here.
Last week I had a shower thought about how it would be neat if we could declare array shapes in PHP, and use them to provide structural interfaces for arrays.
After all, arrays are one of PHPs strongest features, and anyone who has ever tried to use arrays/maps in other languages after getting used to the versatility of PHP arrays will have to admit that nothing comes close to their flexibility and ease of use.
However, ironically, most of my friends, colleagues and fellow PHP devs usually tend to shun PHP arrays, which has led to the widespread use of Collection classes and the like. The usual argument is that arrays don’t provide any way to declare their structure, and thus don’t provide the ergonomics of autocompletion (even if you declare the array shapes with docblock annotations IDE support is iffy at best) and don’t provide any form of runtime type checking.
To me, providing structural interfaces using shapes seems like a perfect fit to the flexibility of arrays. I imagine you'd define them in a similar way to an interface, declaring and typing array elements rather than methods or property hooks. They'd provide a way to validate the contents of an array at runtime, and offer static analyzers and IDEs a robust way to do type inspections and provide autocompletions for arrays.
Of course my first assumption was that other people much smarter than me probably already had this idea, so I started doing my due diligence and discovered that a draft for a Shapes RFC was written by Kacper Donat back in 2021 [1], but unfortunately seems to have since been abandoned. This is a shame because it lines up about 90% with my ideas of what array shapes should look like in PHP.
I'm unable to find any reference to this RFC on the PHP Wiki nor could I find any threads discussing it in the PHP-Internals archive, so I assume they simply never got around to completing it, or there was some discussion elsewhere that prompted them to abandon the RFC.
Anyway, I would love to try and push this idea forward – either by contacting Kacpar, or writing my own RFC – and have a shot at implementing a proof of concept, but first I would like to get a feel for whether this proposal would find much footing here.
So please let me know how you feel about the idea of array shapes in PHP, and perhaps read the draft written by Kacper Donat [1] for a much more eloquent example of what I'm trying to propose here.
Thank you all very much in advance.
Kindest regards,
Alwin Garside
Hi Alwin,
You may be interested in http://wiki.php.net/rfc/records, whose goal from the beginning was a new type that behaved like arrays (value semantics, copy on write) as well as the ability to attach behavior to them.
I’ve since abandoned it due to the poor reception it got on the list and decided to pursue inner classes instead. However, I spent nearly a year thinking through that RFC, so feel free to pick my brain via email, or a call.
The first implementation didn’t use classes at all, instead were implemented on arrays and enforced an array shape. This meant that records were a subtype of arrays (so you could pass a record as an argument in lieu of an array). This worked pretty well, but feedback I got from colleagues challenged me to add the ability for behavior. So, I had to abandon that implementation. So, if you look at that RFC and remove all the behavior, you’re basically left with a record keyword and the ability to specify types and props.
The biggest challenges with that implementation (and array shapes in general), happen to be around WHEN to do type checking and how to get a handle on references. Further, there’s quite a few parts of php that reach into the implementation of arrays and do something different than the canonical implementation (opcache, is a good example here) that can end up breaking things in fun ways. Particularly if you somehow end up with one of these in one of the magic global arrays.
Anyway, best of luck to you and don’t hesitate to reach out!
— Rob
Hi Alwin,
You may be interested in http://wiki.php.net/rfc/records, whose goal from the beginning was a new type that behaved like arrays (value semantics, copy on write) as well as the ability to attach behavior to them.
I’ve since abandoned it due to the poor reception it got on the list and decided to pursue inner classes instead. However, I spent nearly a year thinking through that RFC, so feel free to pick my brain via email, or a call.
The first implementation didn’t use classes at all, instead were implemented on arrays and enforced an array shape. This meant that records were a subtype of arrays (so you could pass a record as an argument in lieu of an array). This worked pretty well, but feedback I got from colleagues challenged me to add the ability for behavior. So, I had to abandon that implementation. So, if you look at that RFC and remove all the behavior, you’re basically left with a record keyword and the ability to specify types and props.
The biggest challenges with that implementation (and array shapes in general), happen to be around WHEN to do type checking and how to get a handle on references. Further, there’s quite a few parts of php that reach into the implementation of arrays and do something different than the canonical implementation (opcache, is a good example here) that can end up breaking things in fun ways. Particularly if you somehow end up with one of these in one of the magic global arrays.
Anyway, best of luck to you and don’t hesitate to reach out!
— Rob
Hey Rob,
I did stumble upon your Records RFC at some point, but I felt it tries to solve a different problem, because it provides a nominal interface rather than a structural interface like I have in mind, never mind behavior.
But thanks either way for offering your help! I might contact you just to ask some things about the internals of PHP, mostly out of curiosity. :-)
Alwin