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**
```php
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>`

```php
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.

```php
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**
```php
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

Reply via email to