On 1 Mar 2025, at 00:28, Larry Garfield <la...@garfieldtech.com> wrote:
> 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

Reply via email to