czw., 16 lip 2026 o 11:25 Wendell Adriel <[email protected]>
napisał(a):

> Hi everyone,
>
> I've been off for quite a while from internals, so I'm going to
> re-introduce myself briefly.
>
> I'm Wendell Adriel, I've been working with PHP since 2009.
> Worked over a decade with PHP enterprise applications, and currently I'm
> working as a Senior Software Engineer in the Laravel OSS Team. You can
> check more about me on my website (link in my email signature).
>
> I would like to propose native type declarations for PHP array keys and
> values. The RFC draft is available at:
>
> https://wiki.php.net/rfc/typed_array_declarations
>
> The proposal adds `array<TValue>` for integer-keyed arrays and
> `array<TKey, TValue>` when the key type must be declared explicitly. Plain
> `array` declarations remain unchanged.
>
> The RFC proposes three implementation levels that share the same syntax
> and Reflection metadata, but they provide different runtime guarantees:
>
> ```php
> final class ProductCatalog
> {
>     public array<string, Product> $products = [];
> }
>
> $catalog = new ProductCatalog();
>
> $catalog->products = ['featured' => new stdClass()]; // Whole-property
> assignment
> $catalog->products['featured'] = new stdClass();      // Direct dimension
> write
> ```
>
> - **Level 1** parses the declaration and exposes it through Reflection,
> but does not validate elements. Both writes are accepted.
> - **Level 2** validates keys and values when an array crosses a declared
> boundary, such as a complete property assignment, argument, return value,
> default, or typed class constant. The first write throws `TypeError`, but
> the direct dimension write is still accepted.
> - **Level 3** includes Level 2 and enforces the constraint for later
> property mutations and references. Both writes throw `TypeError` before the
> invalid value is stored.
>
> I currently recommend Level 3 for typed properties because it preserves
> the guarantee the declaration appears to make. There is no implementation
> yet, because I first wanted to see which implementation level would be
> accepted. I will work on one after incorporating early feedback.
>
> I would appreciate any feedback, especially on the runtime semantics and
> the appropriate implementation level.
>
> Thanks in advance!
>
> *---*
> *Best Regards,*
> *Wendell Adriel.*
> *Software Engineer & Architect*
> *https://wendelladriel.com <https://wendelladriel.com>*
>

Hi Wendell,

I've been implementing a prototype of persistent list-typed properties
(where list is a structural refinement of array, i.e. array_is_list() must
always hold), and during the implementation I ran into what appears to be a
more general issue that may also affect Level 3 typed arrays.

The interesting part is that this is not specific to list. It seems to be a
consequence of trying to maintain a persistent refinement invariant on
mutable IS_ARRAY values.

A simplified example for typed arrays would be:

class C {
    public array<string, int> $data = [];
}

parse_str("a=1&b=2", $c->data);

A typical internal implementation pattern is roughly:

1. initialize the output parameter as an empty array,
2. the empty array satisfies the declared type,
3. populate the HashTable in place,
4. return.

For nominal types this is fine. For structural refinements (such as list)
or future element-constrained arrays (array<string, int>, array<int, Foo>,
etc.), the invariant can be violated after the initial type check has
already succeeded.

What my prototype uncovered is that there are really two distinct classes
of operations:

Incremental mutations (DIM writes, append, unset, sort variants, etc.),
where the invariant can often be preserved with operation-specific checks.
Opaque out-parameter builders, where an empty array is first assigned and
then populated internally without further type validation.

The second category appears much harder to support in a general way.

This raises a broader architectural question:

Should Level 3 typed arrays rely on function-specific guards for these
APIs, or should internal APIs gradually move towards a model where the
result is built in a temporary zval and only assigned through a single
typed assignment after construction?

That approach would naturally preserve atomicity and would work not only
for list, but also for future typed arrays.

I'm not claiming this makes Level 3 impossible. Quite the opposite - I
think it highlights an implementation issue that any persistent refinement
over mutable arrays will eventually have to solve, regardless of whether
the refinement is list, array<T>, or something else.

I'd be interested to hear whether this has already been considered during
the design of Level 3, or whether this class of internal APIs simply hasn't
been explored yet.

Kind regards,
Michał Marcin Brzuchalski

Reply via email to