[PHP-DEV] [RFC Idea] Generic Collection Notation for Iterables (array<T>)

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`
- `Traversable` implementations
- `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 `array` type remains valid and unchanged.
  - `array<T>` means an array whose elements are of type `T`.
  - 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`, throw `TypeError` at 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 `TKey` should be part of the initial scope or a follow up

Kind regards,
Jordi Kroon

On 27/12/2025 4:26 pm, Jordi Kroon wrote:

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`
- `Traversable` implementations
- `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 `array` type remains valid and unchanged.
- `array<T>` means an array whose elements are of type `T`.
- 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`, throw `TypeError` at 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 `TKey` should be part of the initial scope or a follow up

Kind regards,
Jordi Kroon

Yes, I am fully aware PHP: 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.

On Sat, Dec 27, 2025 at 09:49 Jordi Kroon <jordikroon@me.com> wrote:

On 27/12/2025 4:26 pm, Jordi Kroon wrote:

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
  • Traversable implementations
  • 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 array type remains valid and unchanged.
  • array<T> means an array whose elements are of type T.
  • 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, throw TypeError at 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 TKey should be part of the initial scope or a
    follow up

Kind 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,

On Sat 27. 12. 2025 at 16:28, Jordi Kroon <jordikroon@me.com> wrote:

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
  • Traversable implementations
  • 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 array type remains valid and unchanged.
  • array<T> means an array whose elements are of type T.
  • 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, throw TypeError at 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 TKey should 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