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:
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 throwsTypeError, 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 throwTypeErrorbefore 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
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
Hello,
Thanks for the RFC. I am also a long time silent follower of the list.
I would like to highlight a possible mistake. I copy the text here:
For by-value boundaries, one parameterized array is a subtype of another
when both its key and value types are subtypes:
array<K1, V1> <: array<K2, V2>
when K1 <: K2 and V1 <: V2
There are some problems here:
- You have limited K to int or string, but none of these types can be
subtyped. As a result K1<:K2 never applies. We can only have K1:=K2. - You also claim that V is covariant. However, this holds true only for
read-only arrays. Write operations of an array use V in contra-variant
positions.
I am afraid that we cannot have sound covariant subtyping of an array,
unless we clearly separate the read interface from the write one.
Lazare INEPOLOGLOU
Architecte Logiciel
On Thu, 16 Jul 2026 at 11:27, Wendell Adriel wendelladriel.ti@gmail.com
wrote:
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
arraydeclarations remain unchanged.The RFC proposes three implementation levels that share the same syntax
and Reflection metadata, but they provide different runtime guarantees: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 throwsTypeError, 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 throwTypeErrorbefore 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
There are some problems here:
- You have limited K to int or string, but none of these types can be
subtyped. As a result K1<:K2 never applies. We can only have K1:=K2.- You also claim that V is covariant. However, this holds true only for
read-only arrays. Write operations of an array use V in contra-variant
positions.
I am afraid that we cannot have sound covariant subtyping of an array,
unless we clearly separate the read interface from the write one.
Hey Lazare,
Thank you for the feedback.
I'm going to analyze this and update the RFC document accordingly ASAP.
---
Best Regards,
Wendell Adriel.
Software Engineer & Architect
https://wendelladriel.com https://wendelladriel.com
There are some problems here:
- You have limited K to int or string, but none of these types can be
subtyped. As a result K1<:K2 never applies. We can only have K1:=K2.- You also claim that V is covariant. However, this holds true only for
read-only arrays. Write operations of an array use V in contra-variant
positions.
I am afraid that we cannot have sound covariant subtyping of an array,
unless we clearly separate the read interface from the write one.
Hello there,
Lazare, once again, thanks for the feedback:
I just published a v0.2 of the RFC with changes based on your feedback if
you want to take a look at it.
Also, for other members of Internals, I'd be happy to get your feedback on
this.
I plan to start implementation as soon as we agree on an Implementation
Level I should aim for.
Thanks in advance.
---
Best Regards,
Wendell Adriel.
Software Engineer & Architect
https://wendelladriel.com https://wendelladriel.com
Em qui., 16 de jul. de 2026 às 15:18, Wendell Adriel <
wendelladriel.ti@gmail.com> escreveu:
There are some problems here:
- You have limited K to int or string, but none of these types can be
subtyped. As a result K1<:K2 never applies. We can only have K1:=K2.- You also claim that V is covariant. However, this holds true only for
read-only arrays. Write operations of an array use V in contra-variant
positions.I am afraid that we cannot have sound covariant subtyping of an array,
unless we clearly separate the read interface from the write one.Hey Lazare,
Thank you for the feedback.
I'm going to analyze this and update the RFC document accordingly ASAP.---
Best Regards,
Wendell Adriel.
Software Engineer & Architect
https://wendelladriel.com https://wendelladriel.com
There are some problems here:
- You have limited K to int or string, but none of these types can be subtyped. As a result K1<:K2 never applies. We can only have K1:=K2.
- You also claim that V is covariant. However, this holds true only for read-only arrays. Write operations of an array use V in contra-variant positions.
I am afraid that we cannot have sound covariant subtyping of an array, unless we clearly separate the read interface from the write one.
Hello there,
Lazare, once again, thanks for the feedback:
I just published a v0.2 of the RFC with changes based on your feedback if you want to take a look at it.Also, for other members of Internals, I'd be happy to get your feedback on this.
I plan to start implementation as soon as we agree on an Implementation Level I should aim for.Thanks in advance.
---
Best Regards,
Wendell Adriel.
Software Engineer & Architect**
https://wendelladriel.comEm qui., 16 de jul. de 2026 às 15:18, Wendell Adriel wendelladriel.ti@gmail.com escreveu:
There are some problems here:
- You have limited K to int or string, but none of these types can be subtyped. As a result K1<:K2 never applies. We can only have K1:=K2.
- You also claim that V is covariant. However, this holds true only for read-only arrays. Write operations of an array use V in contra-variant positions.
I am afraid that we cannot have sound covariant subtyping of an array, unless we clearly separate the read interface from the write one.
Hey Lazare,Thank you for the feedback.
I'm going to analyze this and update the RFC document accordingly ASAP.---
Best Regards,
Wendell Adriel.
Software Engineer & Architect**
https://wendelladriel.com
Hi Wendell,
Thanks for updating the RFC.
My largest concern is that this proposal substantially overlaps with the reified generics RFC, which is being held until after the current code freeze in late August or early September at the earliest.
In particular, both proposals need to answer many of the same questions around parameterized types, variance, Reflection, runtime enforcement, inference, and type identity. I think we should avoid committing PHP to a separate set of array-specific rules before the broader generics proposal has been discussed. Otherwise, we risk either constraining the generics design or ending up with two parameterization models that behave differently.
My second concern is the proposed variance model. It is extremely complex for a mutable built-in collection. Collections are normally invariant unless their API clearly separates reading from writing. The generics RFC follows that model: generic parameters are invariant by default, and covariance or contravariance is permitted only where their usage can be proven safe.
Here, an array<Dog> is treated as compatible with array<Animal> only at selected by-value boundaries, based on PHP's copy-on-write separation. It then becomes invariant for writable aliases and requires additional rules for shared references.
That does not appear to establish a normal subtype relationship. It establishes context-dependent boundary compatibility whose soundness depends on engine-level array separation and reference behavior. I think that will be challenging for users to understand and difficult for the engine to enforce consistently.
Making parameterized arrays invariant would be substantially simpler:
array<int, Dog> !== array<int, Animal>
If covariance is desirable, I think it should come from a separate read-only collection or interface rather than from mutable arrays.
My third concern is the one-argument syntax:
array<TValue>
This implicitly means:
array<int, TValue>
That is surprising. It is not a general array of values because string keys are rejected, but it is not a list either because the integer keys need not be contiguous or zero-based. I would expect array<TValue> to constrain only the value type, with the key type remaining int|string. A future list<TValue> could express the more restrictive numeric-keyed form.
Lastly, I am concerned about repeated runtime validation. Since ordinary arrays do not retain a trusted element-type identity, an untyped boundary loses any information established by an earlier check:
function foo(array<Bar> $arr): int
{
$acc = 0;
foreach ($arr as $bar) {
$acc += $bar->count;
}
return $acc;
}
function bar(array $arr): int
{
return foo($arr);
}
$arr = get_super_huge_array();
echo bar($arr);
At the call to foo(), PHP must recursively validate the entire array again, even if the same array was previously checked elsewhere. For large or nested arrays, this makes a type declaration potentially introduce an O(n) or recursive O(n) cost at every typed boundary.
This is another reason I think typed arrays should be considered together with the wider generics design. A broader design may be able to provide type identity, inference, specialized collection types, or another mechanism that avoids repeatedly rediscovering the element type by traversing the value.
I would strongly prefer that this RFC wait until the reified generics RFC has been discussed, or at minimum limit itself to syntax and Reflection experimentation without committing to independent variance and runtime semantics.
— Rob
Hey Rob,
Thanks for the feedback.
My largest concern is that this proposal substantially overlaps with the
reified generics RFC, which is being held until after the current code
freeze in late August or early September at the earliest.
In particular, both proposals need to answer many of the same questions
around parameterized types, variance, Reflection, runtime enforcement,
inference, and type identity. I think we should avoid committing PHP to a
separate set of array-specific rules before the broader generics proposal
has been discussed. Otherwise, we risk either constraining the generics
design or ending up with two parameterization models that behave
differently.
That makes total sense.
For sure, we don't want to create two different models or impact the
generics design.
My second concern is the proposed variance model. It is extremely complex
for a mutable built-in collection. Collections are normally invariant
unless their API clearly separates reading from writing. The generics RFC
follows that model: generic parameters are invariant by default, and
covariance or contravariance is permitted only where their usage can be
proven safe.
Making parameterized arrays invariant would be substantially simpler:
array<int, Dog> !== array<int, Animal>
If covariance is desirable, I think it should come from a separate
read-only collection or interface rather than from mutable arrays.
Yeah, I was divided when writing the RFC if I should include this or not,
but I agree that making them invariant would be much easier/simpler to
approach and I'm totally fine if we want to move in this direction.
My third concern is the one-argument syntax:
array<TValue>
This implicitly means:
array<int, TValue>
That is surprising. It is not a general array of values because string
keys are rejected, but it is not a list either because the integer keys
need not be contiguous or zero-based. I would expect array<TValue> to
constrain only the value type, with the key type remaining int|string. A
future list<TValue> could express the more restrictive numeric-keyed form.
That makes total sense, I can update the RFC in this direction.
Lastly, I am concerned about repeated runtime validation. Since ordinary
arrays do not retain a trusted element-type identity, an untyped boundary
loses any information established by an earlier check:
PHP must recursively validate the entire array again, even if the same
array was previously checked elsewhere. For large or nested arrays, this
makes a type declaration potentially introduce an O(n) or recursive O(n)
cost at every typed boundary.
This is another reason I think typed arrays should be considered together
with the wider generics design. A broader design may be able to provide
type identity, inference, specialized collection types, or another
mechanism that avoids repeatedly rediscovering the element type by
traversing the value.
Yeah, since I didn't start implementation yet, this was a big concern from
my side regarding performance.
I agree that waiting to see how the generics design will approach this
could be better.
I would strongly prefer that this RFC wait until the reified generics RFC
has been discussed, or at minimum limit itself to syntax and Reflection
experimentation without committing to independent variance and runtime
semantics.
Yeah, I think that we have two paths from here:
- Doing only Implementation Level 1 for this RFC, it's simple enough to be
on PHP 8.7 IMO, and then deferring Level 2 and 3 to a new RFC after the
generics design. - Defer the whole RFC after the generics design and aim for Implementation
Level 2 or 3 (3 as preferred one).
I'm ok with both paths here.
Whatever you think would be better.
---
Best Regards,
Wendell Adriel.
Software Engineer & Architect
https://wendelladriel.com https://wendelladriel.com
Yeah, I think that we have two paths from here:
- Doing only Implementation Level 1 for this RFC, it's simple enough to be
on PHP 8.7 IMO, and then deferring Level 2 and 3 to a new RFC after the
generics design.- Defer the whole RFC after the generics design and aim for Implementation
Level 2 or 3 (3 as preferred one).
Your "level 1" sounds similar in spirit to the "bound-erased generics" RFC which was just declined: https://wiki.php.net/rfc/bound_erased_generic_types
Reserving the syntax without directly enforcing it was not popular, and could actually make it harder to add an enforced version later, because you'd be changing the behaviour of existing code.
I think this is one of those cases where just talking about the abstract design doesn't get us very far without a concrete plan of implementation - there's plenty of support for the feature in theory, but the reason it hasn't happened is that it's really hard to do well within PHP's runtime type model.
Regards,
Rowan Tommins
[IMSoP]
Hey Rowan,
Your "level 1" sounds similar in spirit to the "bound-erased generics" RFC
which was just declined:
https://wiki.php.net/rfc/bound_erased_generic_types
Reserving the syntax without directly enforcing it was not popular, and
could actually make it harder to add an enforced version later, because
you'd be changing the behaviour of existing code.
I think this is one of those cases where just talking about the abstract
design doesn't get us very far without a concrete plan of implementation -
there's plenty of support for the feature in theory, but the reason it
hasn't happened is that it's really hard to do well within PHP's runtime
type model.
I totally get it.
So maybe for now the best would be to defer this RFC until we get the
generics design wrapped up.
I'll be happy to help in any way I can for it, if needed.
Also, if anyone else has any other feedback, questions, or concerns about
the RFC, I'd be happy to discuss about it.
---
Best Regards,
Wendell Adriel.
Software Engineer & Architect
https://wendelladriel.com https://wendelladriel.com
Hi Wendell,
Thanks for updating the RFC.
My largest concern is that this proposal substantially overlaps with
the reified generics RFC, which is being held until after the current
code freeze in late August or early September at the earliest.In particular, both proposals need to answer many of the same questions
around parameterized types, variance, Reflection, runtime enforcement,
inference, and type identity. I think we should avoid committing PHP to
a separate set of array-specific rules before the broader generics
proposal has been discussed. Otherwise, we risk either constraining the
generics design or ending up with two parameterization models that
behave differently.My second concern is the proposed variance model. It is extremely
complex for a mutable built-in collection. Collections are normally
invariant unless their API clearly separates reading from writing. The
generics RFC follows that model: generic parameters are invariant by
default, and covariance or contravariance is permitted only where their
usage can be proven safe.Here, an
array<Dog>is treated as compatible witharray<Animal>
only at selected by-value boundaries, based on PHP's copy-on-write
separation. It then becomes invariant for writable aliases and requires
additional rules for shared references.That does not appear to establish a normal subtype relationship. It
establishes context-dependent boundary compatibility whose soundness
depends on engine-level array separation and reference behavior. I
think that will be challenging for users to understand and difficult
for the engine to enforce consistently.Making parameterized arrays invariant would be substantially simpler:
array<int, Dog> !== array<int, Animal>
If covariance is desirable, I think it should come from a separate
read-only collection or interface rather than from mutable arrays.My third concern is the one-argument syntax:
array<TValue>
This implicitly means:
array<int, TValue>
That is surprising. It is not a general array of values because string
keys are rejected, but it is not a list either because the integer keys
need not be contiguous or zero-based. I would expectarray<TValue>to
constrain only the value type, with the key type remaining
int|string. A futurelist<TValue>could express the more
restrictive numeric-keyed form.Lastly, I am concerned about repeated runtime validation. Since
ordinary arrays do not retain a trusted element-type identity, an
untyped boundary loses any information established by an earlier check:function foo(array<Bar> $arr): int
{
$acc = 0;foreach ($arr as $bar) { $acc += $bar->count; } return $acc;}
function bar(array $arr): int
{
return foo($arr);
}$arr = get_super_huge_array();
echo bar($arr);At the call to foo(), PHP must recursively validate the entire array
again, even if the same array was previously checked elsewhere. For
large or nested arrays, this makes a type declaration potentially
introduce an O(n) or recursive O(n) cost at every typed boundary.This is another reason I think typed arrays should be considered
together with the wider generics design. A broader design may be able
to provide type identity, inference, specialized collection types, or
another mechanism that avoids repeatedly rediscovering the element type
by traversing the value.I would strongly prefer that this RFC wait until the reified generics
RFC has been discussed, or at minimum limit itself to syntax and
Reflection experimentation without committing to independent variance
and runtime semantics.— Rob
I am opposed to this RFC, for all the reasons Rob mentioned. Moreover, I don't believe the issues can be resolved by just waiting for the reified generics RFC (which I really hope passes). The core issue is that PHP arrays are an over-broad data structure, and we need to have a hard, type-based (not just implied by generics) distinction between lists/sequences, sets, and dictionaries/maps, the same way most languages do.
There are a few ways that could be done: 3 generic objects (a la Kotlin, my preferred approach), 3 generic objects with extension functions (assuming we can get those), 3 new core data types with extension functions (very hard to implement as I understand it), etc.
But tacking it onto the already-dangerously-overloaded array mega-type is the wrong approach.
--Larry Garfield
Hey Larry,
I am opposed to this RFC, for all the reasons Rob mentioned. Moreover, I
don't believe the issues can be resolved by just waiting for the reified
generics RFC (which I really hope passes). The core issue is that PHP
arrays are an over-broad data structure, and we need to have a hard,
type-based (not just implied by generics) distinction between
lists/sequences, sets, and dictionaries/maps, the same way most languages
do.
There are a few ways that could be done: 3 generic objects (a la Kotlin,
my preferred approach), 3 generic objects with extension functions
(assuming we can get those), 3 new core data types with extension functions
(very hard to implement as I understand it), etc.
But tacking it onto the already-dangerously-overloaded array mega-type is
the wrong approach.
Thanks for the feedback.
I understand the concern of adding more things to the array type and I
think it's a valid concern.
Would you think that an RFC adding new types, maybe using Kotlin as a
reference (as you mentioned), for having strongly typed array/list
structures would be a better fit?
I'd be happy to propose and work on this if this would be a better
direction/approach.
---
Best Regards,
Wendell Adriel.
Software Engineer & Architect
https://wendelladriel.com https://wendelladriel.com
Hey Larry,
I am opposed to this RFC, for all the reasons Rob mentioned. Moreover, I don't believe the issues can be resolved by just waiting for the reified generics RFC (which I really hope passes). The core issue is that PHP arrays are an over-broad data structure, and we need to have a hard, type-based (not just implied by generics) distinction between lists/sequences, sets, and dictionaries/maps, the same way most languages do.
There are a few ways that could be done: 3 generic objects (a la Kotlin, my preferred approach), 3 generic objects with extension functions (assuming we can get those), 3 new core data types with extension functions (very hard to implement as I understand it), etc.
But tacking it onto the already-dangerously-overloaded array mega-type is the wrong approach.Thanks for the feedback.
I understand the concern of adding more things to the array type and I
think it's a valid concern.Would you think that an RFC adding new types, maybe using Kotlin as a
reference (as you mentioned), for having strongly typed array/list
structures would be a better fit?
I'd be happy to propose and work on this if this would be a better
direction/approach.
The challenge with new base types, AIUI, is that the way the engine works right now adding more types is really really hard. Building on top of objects is vastly easier. That's the biggest reason Enums were objects rather than their own base type. (There were other advantages to that approach, too, but implementation potential was a big one.)
Base types would likely resolve the "value object" problem for lists: They would (I think) naturally pass like arrays, not like objects. However, it's fairly clear that we want to have methods on collection objects, not just free-floating functions, so if they're implemented as their own types then we would need to have extension functions for them to support a more robust standard library for them. If implemented as objects, we could use built-in methods or extension functions, but then we have to deal with the passing semantics problem.
And in either case, we really, really want to have operator support.
I did some research on this topic a while back with Derick. My conclusion at the time was that we needed generics first, but once that was in then it was pretty straightforward, and by the end I had a clear-ish picture of the API I wanted. (Ignore the one-off syntax; we were considering that initially but decided to hold off and see if real generics were possible.)
https://github.com/Crell/php-rfcs/blob/master/collections/research-notes.md
(It's rather stream of consciousness, rather than a formal summary, so could flop around a bit.)
At this point, I think the best approach is to wait for reified generics and cross our fingers that it finally happens, then loop back and form a working group to decide on which way forward we want to take. The fate of extension functions would also impact the route taken here.
--Larry Garfield
Base types would likely resolve the "value object" problem for lists: They would (I think) naturally pass like arrays, not like objects. However, it's fairly clear that we want to have methods on collection objects, not just free-floating functions, so if they're implemented as their own types then we would need to have extension functions for them to support a more robust standard library for them. If implemented as objects, we could use built-in methods or extension functions, but then we have to deal with the passing semantics problem.
We could save a lot of conversations if everyone just agreed that my Roadmap for 9.0 with implementations for Monomorphized Generics, Structs, Modules, Extensions, and Surfaces could be considered accepted as a rough outline for the future. PHP needs meaningful growth to avoid settling back into a 5.x slumber.
For anyone who hasn’t checked it out yet, I would encourage you to do so at https://EventuallyWrong.com/php9. I update it regularly as I expand my proposals. Most recently I added Generics. Variadic Generics will probably be added today as well.
pon., 20 lip 2026, 17:22 użytkownik Larry Garfield larry@garfieldtech.com
napisał:
Hi Wendell,
Thanks for updating the RFC.
My largest concern is that this proposal substantially overlaps with
the reified generics RFC, which is being held until after the current
code freeze in late August or early September at the earliest.In particular, both proposals need to answer many of the same questions
around parameterized types, variance, Reflection, runtime enforcement,
inference, and type identity. I think we should avoid committing PHP to
a separate set of array-specific rules before the broader generics
proposal has been discussed. Otherwise, we risk either constraining the
generics design or ending up with two parameterization models that
behave differently.My second concern is the proposed variance model. It is extremely
complex for a mutable built-in collection. Collections are normally
invariant unless their API clearly separates reading from writing. The
generics RFC follows that model: generic parameters are invariant by
default, and covariance or contravariance is permitted only where their
usage can be proven safe.Here, an
array<Dog>is treated as compatible witharray<Animal>
only at selected by-value boundaries, based on PHP's copy-on-write
separation. It then becomes invariant for writable aliases and requires
additional rules for shared references.That does not appear to establish a normal subtype relationship. It
establishes context-dependent boundary compatibility whose soundness
depends on engine-level array separation and reference behavior. I
think that will be challenging for users to understand and difficult
for the engine to enforce consistently.Making parameterized arrays invariant would be substantially simpler:
array<int, Dog> !== array<int, Animal>
If covariance is desirable, I think it should come from a separate
read-only collection or interface rather than from mutable arrays.My third concern is the one-argument syntax:
array<TValue>
This implicitly means:
array<int, TValue>
That is surprising. It is not a general array of values because string
keys are rejected, but it is not a list either because the integer keys
need not be contiguous or zero-based. I would expectarray<TValue>to
constrain only the value type, with the key type remaining
int|string. A futurelist<TValue>could express the more
restrictive numeric-keyed form.Lastly, I am concerned about repeated runtime validation. Since
ordinary arrays do not retain a trusted element-type identity, an
untyped boundary loses any information established by an earlier check:function foo(array<Bar> $arr): int
{
$acc = 0;foreach ($arr as $bar) { $acc += $bar->count; } return $acc;}
function bar(array $arr): int
{
return foo($arr);
}$arr = get_super_huge_array();
echo bar($arr);At the call to foo(), PHP must recursively validate the entire array
again, even if the same array was previously checked elsewhere. For
large or nested arrays, this makes a type declaration potentially
introduce an O(n) or recursive O(n) cost at every typed boundary.This is another reason I think typed arrays should be considered
together with the wider generics design. A broader design may be able
to provide type identity, inference, specialized collection types, or
another mechanism that avoids repeatedly rediscovering the element type
by traversing the value.I would strongly prefer that this RFC wait until the reified generics
RFC has been discussed, or at minimum limit itself to syntax and
Reflection experimentation without committing to independent variance
and runtime semantics.— Rob
I am opposed to this RFC, for all the reasons Rob mentioned. Moreover, I
don't believe the issues can be resolved by just waiting for the reified
generics RFC (which I really hope passes). The core issue is that PHP
arrays are an over-broad data structure, and we need to have a hard,
type-based (not just implied by generics) distinction between
lists/sequences, sets, and dictionaries/maps, the same way most languages
do.There are a few ways that could be done: 3 generic objects (a la Kotlin,
my preferred approach), 3 generic objects with extension functions
(assuming we can get those), 3 new core data types with extension functions
(very hard to implement as I understand it), etc.
I suspect the 3rd option implementation challenge may not be as prohibitive
as it first appears. The design space seems larger than just "extend
arrays" versus "generic objects". I'm still exploring it though, so I'd
rather not jump to conclusions yet.
Cheers,
Michał Marcin Brzuchalski
My largest concern is that this proposal substantially overlaps with the reified generics RFC, which is being held until after the current code freeze in late August or early September at the earliest.
In particular, both proposals need to answer many of the same questions around parameterized types, variance, Reflection, runtime enforcement, inference, and type identity. I think we should avoid committing PHP to a separate set of array-specific rules before the broader generics proposal has been discussed. Otherwise, we risk either constraining the generics design or ending up with two parameterization models that behave differently.
plus: we may need to use turbofish syntax like array::<int, Product>
to avoid issues with the parser.
czw., 16 lip 2026 o 11:25 Wendell Adriel wendelladriel.ti@gmail.com
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
arraydeclarations remain unchanged.The RFC proposes three implementation levels that share the same syntax
and Reflection metadata, but they provide different runtime guarantees: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 throwsTypeError, 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 throwTypeErrorbefore 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:
- initialize the output parameter as an empty array,
- the empty array satisfies the declared type,
- populate the HashTable in place,
- 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
Hey Michał,
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.
This is very interesting.
I thought about it, but didn't dive too much into it.
However, from some checks I've done myself, when you say: "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 going into this direction would be very beneficial, however, I think
this would imply touching a lot of things that can have a broader impact.
If this is something that people would agree to follow, I think it would be
great.
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),
BTW, on this, is this already an RFC, or is the implementation somewhere I
can take a look at?
As it was advised before, since this RFC can be affected by the generics
design, for now I'll keep the RFC on hold, as well as because it's not a
PHP 8.6 one, and as Larry asked, since we're close to PHP 8.6, this version
should be the focus.
But I'd be happy to discuss and improve the RFC along the way.
---
Best Regards,
Wendell Adriel.
Software Engineer & Architect
https://wendelladriel.com https://wendelladriel.com
pon., 20 lip 2026 o 13:48 Wendell Adriel wendelladriel.ti@gmail.com
napisał(a):
Hey Michał,
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.This is very interesting.
I thought about it, but didn't dive too much into it.However, from some checks I've done myself, when you say: "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 going into this direction would be very beneficial, however, I think
this would imply touching a lot of things that can have a broader impact.
If this is something that people would agree to follow, I think it would
be great.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),BTW, on this, is this already an RFC, or is the implementation somewhere
I can take a look at?
I actually moved away from the direction I originally suggested (adding
element constraints to arrays). I'm now exploring a separate approach based
on dedicated immutable value types, but it's still too early to discuss as
a concrete proposal. I'd rather wait until the design has stabilized.
Cheers,
Michał Marcin Brzuchalski
Hey Michał,
I actually moved away from the direction I originally suggested (adding
element constraints to arrays). I'm now exploring a separate approach based
on dedicated immutable value types, but it's still too early to discuss as
a concrete proposal. I'd rather wait until the design has stabilized.
Interesting, I'll keep an eye out when you make this public!
---
Best Regards,
Wendell Adriel.
Software Engineer & Architect
https://wendelladriel.com https://wendelladriel.com