Hi everyone,
I would like to propose a new type in PHP: Generic Collection Notation
for Iterables (array<T>).
The problem
PHP provides several ways to represent sequences of values:
-
array -
iterable -
Traversableimplementations -
generators
While these constructs are fundamental to PHP, none of them allow the
developer to express what type of values they yield or contain at
the language level.
This leads to several issues:
- Static analysis relies heavily on PHPDoc annotations.
- Runtime type hints communicate structure but not intent.
- APIs lack self descriptive type information.
- Developers must duplicate type information across signatures and
documentation.
Example
class UserService
{
/**
* @return User[]
*/
public function getAllUsers(): array
{
return [new User("Alice"), new User("Bob")];
}
/**
* @param User $user
* @param Role[] $roles
* @return void
*/
public function saveRoles($user, array $roles): void
{
// Some database call
}
}
The proposal
Introduce parameterized iterable types using generic like syntax:
array<T>
iterable<T>
Traversable<T>
Generator<T>
function processUsers(array<User> $users): void { }
function normalize(array<string> $values): array<string> { }
Optionally we could also extend the new array syntax with this
functionality.
function processUsers(User[] $users): void { }
function normalize(string[] $values): string[] { }
Key points:
- The existing
arraytype remains valid and unchanged. -
array<T>means an array whose elements are of typeT. - The syntax is currently invalid in PHP and therefore does not
introduce a backward compatibility break.
Example
class UserService
{
public function getAllUsers(): array<User>
{
return [new User("Alice"), new User("Bob")];
}
public function saveRoles(User $user, array<Role> $roles): void
{
// Some database call
}
public function streamAllUsers(): Generator<User>
{
yield new User("Alice");
yield new User("Bob");
}
public function iterateAllUsers(): ArrayIterator<User>
{
return new ArrayIterator([new User("Alice"), new User("Bob")]);
}
public function getUserProvider(): Traversable<User>
{
return new ArrayIterator([new User("Alice"), new User("Bob")]);
}
public function normalizeNames(iterable<string> $names):
iterable<string>
{
foreach ($names as $name) {
yield trim($name);
}
}
}
Runtime semantics and enforcement
The goal is that array<T> and related iterable parameterizations
behave like other PHP types: if a value is passed or returned that
violates the declared type, a TypeError is thrown.
The main question is when and how enforcement occurs for eager versus
lazy containers.
- Checked on function argument receive, property assignment, and return.
- Each element value is validated against
T.
There are two approaches, but only one gives strong runtime guarantees.
Preferred behavior:
- Validate on consumption when the iterable is iterated.
- If a yielded element violates
T, throwTypeErrorat the point
of iteration.
This mirrors the fact that iterable may be lazy and cannot be fully
validated up front.
Implementation
I have basic C knowledge but I don't have much experience with Zend
Engine / PHP Core. However I am fully committed to:
- Write and maintain the RFC document.
- Test and write tests for the implementation or draft
- Participate in discussions
- Collaborate with anyone willing to help with the implementation
Keys and additional parameters
This proposal focuses on element types. Keys are intentionally out of
scope for the initial version.
Possible follow up work could explore array<TKey, TValue> and
Generator<TKey, TValue>.
Next steps
- I would like to have hear any potential caveats. For example
regarding lazy loading. - If the RFC gains internal support, I will officially publish the
first initial draft of the RFC. - Whether supporting the shorthand
T[]should be part of the initial
scope or a follow up - Whether supporting
TKeyshould be part of the initial scope or a
follow up
Kind regards,
Jordi Kroon
Hi everyone,
I would like to propose a new type in PHP: Generic Collection Notation
for Iterables (array<T>).The problem
PHP provides several ways to represent sequences of values:
arrayiterableTraversableimplementationsgeneratorsWhile these constructs are fundamental to PHP, none of them allow the
developer to express what type of values they yield or contain at
the language level.This leads to several issues:
- Static analysis relies heavily on PHPDoc annotations.
- Runtime type hints communicate structure but not intent.
- APIs lack self descriptive type information.
- Developers must duplicate type information across signatures and
documentation.Example
class UserService { /** * @return User[] */ public function getAllUsers(): array { return [new User("Alice"), new User("Bob")]; } /** * @param User $user * @param Role[] $roles * @return void */ public function saveRoles($user, array $roles): void { // Some database call } }The proposal
Introduce parameterized iterable types using generic like syntax:
array<T>
iterable<T>
Traversable<T>
Generator<T>function processUsers(array<User> $users): void { } function normalize(array<string> $values): array<string> { }Optionally we could also extend the new array syntax with this
functionality.function processUsers(User[] $users): void { } function normalize(string[] $values): string[] { }Key points:
- The existingarraytype remains valid and unchanged.
-array<T>means an array whose elements are of typeT.
- The syntax is currently invalid in PHP and therefore does not
introduce a backward compatibility break.Example
class UserService { public function getAllUsers(): array<User> { return [new User("Alice"), new User("Bob")]; } public function saveRoles(User $user, array<Role> $roles): void { // Some database call } public function streamAllUsers(): Generator<User> { yield new User("Alice"); yield new User("Bob"); } public function iterateAllUsers(): ArrayIterator<User> { return new ArrayIterator([new User("Alice"), new User("Bob")]); } public function getUserProvider(): Traversable<User> { return new ArrayIterator([new User("Alice"), new User("Bob")]); } public function normalizeNames(iterable<string> $names): iterable<string> { foreach ($names as $name) { yield trim($name); } } }Runtime semantics and enforcement
The goal is thatarray<T>and related iterable parameterizations
behave like other PHP types: if a value is passed or returned that
violates the declared type, aTypeErroris thrown.The main question is when and how enforcement occurs for eager versus
lazy containers.
- Checked on function argument receive, property assignment, and return.
- Each element value is validated againstT.
There are two approaches, but only one gives strong runtime guarantees.Preferred behavior:
- Validate on consumption when the iterable is iterated.
- If a yielded element violatesT, throwTypeErrorat the point
of iteration.This mirrors the fact that
iterablemay be lazy and cannot be fully
validated up front.Implementation
I have basic C knowledge but I don't have much experience with Zend
Engine / PHP Core. However I am fully committed to:
- Write and maintain the RFC document.
- Test and write tests for the implementation or draft
- Participate in discussions
- Collaborate with anyone willing to help with the implementation
Keys and additional parameters
This proposal focuses on element types. Keys are intentionally out of
scope for the initial version.Possible follow up work could explore
array<TKey, TValue>and
Generator<TKey, TValue>.Next steps
- I would like to have hear any potential caveats. For example
regarding lazy loading.
- If the RFC gains internal support, I will officially publish the
first initial draft of the RFC.
- Whether supporting the shorthandT[]should be part of the initial
scope or a follow up
- Whether supportingTKeyshould be part of the initial scope or a
follow upKind regards,
Jordi Kroon
Yes, I am fully aware https://wiki.php.net/rfc/generic-arrays exists.
But since it's older date I was not sure if I had to revive the original
or start a new. That said since PHP is becoming a more strict language,
it became more relevant. And the original RFC doesn't mention anything
regarding Traversables or other iterables. Though I would like to give
my sincere for the work that has been done for the current RFC proposal.
Hi everyone,
I would like to propose a new type in PHP: Generic Collection Notation
for Iterables (array<T>).The problem
PHP provides several ways to represent sequences of values:
arrayiterableTraversableimplementationsgeneratorsWhile these constructs are fundamental to PHP, none of them allow the
developer to express what type of values they yield or contain at
the language level.This leads to several issues:
- Static analysis relies heavily on PHPDoc annotations.
- Runtime type hints communicate structure but not intent.
- APIs lack self descriptive type information.
- Developers must duplicate type information across signatures and
documentation.Example
class UserService { /** * @return User[] */ public function getAllUsers(): array { return [new User("Alice"), new User("Bob")]; } /** * @param User $user * @param Role[] $roles * @return void */ public function saveRoles($user, array $roles): void { // Some database call } }The proposal
Introduce parameterized iterable types using generic like syntax:
array<T>
iterable<T>
Traversable<T>
Generator<T>function processUsers(array<User> $users): void { } function normalize(array<string> $values): array<string> { }Optionally we could also extend the new array syntax with this
functionality.function processUsers(User[] $users): void { } function normalize(string[] $values): string[] { }Key points:
- The existing
arraytype remains valid and unchanged.array<T>means an array whose elements are of typeT.- The syntax is currently invalid in PHP and therefore does not
introduce a backward compatibility break.Example
class UserService { public function getAllUsers(): array<User> { return [new User("Alice"), new User("Bob")]; } public function saveRoles(User $user, array<Role> $roles): void { // Some database call } public function streamAllUsers(): Generator<User> { yield new User("Alice"); yield new User("Bob"); } public function iterateAllUsers(): ArrayIterator<User> { return new ArrayIterator([new User("Alice"), new User("Bob")]); } public function getUserProvider(): Traversable<User> { return new ArrayIterator([new User("Alice"), new User("Bob")]); } public function normalizeNames(iterable<string> $names): iterable<string> { foreach ($names as $name) { yield trim($name); } } }Runtime semantics and enforcement
The goal is thatarray<T>and related iterable parameterizations
behave like other PHP types: if a value is passed or returned that
violates the declared type, aTypeErroris thrown.The main question is when and how enforcement occurs for eager versus
lazy containers.
- Checked on function argument receive, property assignment, and
return.- Each element value is validated against
T.
There are two approaches, but only one gives strong runtime guarantees.Preferred behavior:
- Validate on consumption when the iterable is iterated.
- If a yielded element violates
T, throwTypeErrorat the point
of iteration.This mirrors the fact that
iterablemay be lazy and cannot be fully
validated up front.Implementation
I have basic C knowledge but I don't have much experience with Zend
Engine / PHP Core. However I am fully committed to:
- Write and maintain the RFC document.
- Test and write tests for the implementation or draft
- Participate in discussions
- Collaborate with anyone willing to help with the implementation
Keys and additional parameters
This proposal focuses on element types. Keys are intentionally out of
scope for the initial version.Possible follow up work could explore
array<TKey, TValue>and
Generator<TKey, TValue>.Next steps
- I would like to have hear any potential caveats. For example
regarding lazy loading.- If the RFC gains internal support, I will officially publish the
first initial draft of the RFC.- Whether supporting the shorthand
T[]should be part of the initial
scope or a follow up- Whether supporting
TKeyshould be part of the initial scope or a
follow upKind regards,
Jordi Kroon
Yes, I am fully aware https://wiki.php.net/rfc/generic-arrays exists.
But since it's older date I was not sure if I had to revive the original
or start a new. That said since PHP is becoming a more strict language,
it became more relevant. And the original RFC doesn't mention anything
regarding Traversables or other iterables. Though I would like to give
my sincere for the work that has been done for the current RFC proposal.
I recommend reading this blog post from August that takes an in-depth look
at the problem and potential solutions:
https://thephp.foundation/blog/2025/08/05/compile-generics/
Another relevant post is from August 2024, a year before the one linked
above:
https://thephp.foundation/blog/2024/08/19/state-of-generics-and-collections/
Cheers,
Ben
Hi,
Hi everyone,
I would like to propose a new type in PHP: Generic Collection Notation
for Iterables (array<T>).The problem
PHP provides several ways to represent sequences of values:
arrayiterableTraversableimplementationsgeneratorsWhile these constructs are fundamental to PHP, none of them allow the
developer to express what type of values they yield or contain at
the language level.This leads to several issues:
- Static analysis relies heavily on PHPDoc annotations.
- Runtime type hints communicate structure but not intent.
- APIs lack self descriptive type information.
- Developers must duplicate type information across signatures and
documentation.Example
class UserService { /** * @return User[] */ public function getAllUsers(): array { return [new User("Alice"), new User("Bob")]; } /** * @param User $user * @param Role[] $roles * @return void */ public function saveRoles($user, array $roles): void { // Some database call } }The proposal
Introduce parameterized iterable types using generic like syntax:
array<T>
iterable<T>
Traversable<T>
Generator<T>function processUsers(array<User> $users): void { } function normalize(array<string> $values): array<string> { }Optionally we could also extend the new array syntax with this
functionality.function processUsers(User[] $users): void { } function normalize(string[] $values): string[] { }Key points:
- The existing
arraytype remains valid and unchanged.array<T>means an array whose elements are of typeT.- The syntax is currently invalid in PHP and therefore does not
introduce a backward compatibility break.Example
class UserService { public function getAllUsers(): array<User> { return [new User("Alice"), new User("Bob")]; } public function saveRoles(User $user, array<Role> $roles): void { // Some database call } public function streamAllUsers(): Generator<User> { yield new User("Alice"); yield new User("Bob"); } public function iterateAllUsers(): ArrayIterator<User> { return new ArrayIterator([new User("Alice"), new User("Bob")]); } public function getUserProvider(): Traversable<User> { return new ArrayIterator([new User("Alice"), new User("Bob")]); } public function normalizeNames(iterable<string> $names): iterable<string> { foreach ($names as $name) { yield trim($name); } } }Runtime semantics and enforcement
The goal is thatarray<T>and related iterable parameterizations
behave like other PHP types: if a value is passed or returned that
violates the declared type, aTypeErroris thrown.The main question is when and how enforcement occurs for eager versus
lazy containers.
- Checked on function argument receive, property assignment, and return.
- Each element value is validated against
T.
There are two approaches, but only one gives strong runtime guarantees.Preferred behavior:
- Validate on consumption when the iterable is iterated.
- If a yielded element violates
T, throwTypeErrorat the point
of iteration.This mirrors the fact that
iterablemay be lazy and cannot be fully
validated up front.Implementation
I have basic C knowledge but I don't have much experience with Zend
Engine / PHP Core. However I am fully committed to:
- Write and maintain the RFC document.
- Test and write tests for the implementation or draft
- Participate in discussions
- Collaborate with anyone willing to help with the implementation
Keys and additional parameters
This proposal focuses on element types. Keys are intentionally out of
scope for the initial version.Possible follow up work could explore
array<TKey, TValue>and
Generator<TKey, TValue>.Next steps
- I would like to have hear any potential caveats. For example
regarding lazy loading.- If the RFC gains internal support, I will officially publish the
first initial draft of the RFC.- Whether supporting the shorthand
T[]should be part of the initial
scope or a follow up- Whether supporting
TKeyshould be part of the initial scope or a
follow up
This has been discussed in past and we actually talked about it internally
quite recently. IMHO only T[] should be supported to not give partial
generics and disallow nested arrays.
Arnaud described best the possible issues in
https://externals.io/message/125049#125069 .
There is really not much point to do RFC without implementation so you will
probably need to find someone who is willing and capable to implement it.
Kind regards,
Jakub