PHP: making arrays more robust

If PHP had array constraints/generics it would be so much easier to be sure the array items are all of the same type, public array $users. So what are the options? The array object way abstract class TypedCollection extends ArrayObject { abstract protected function getAllowedType(): string; public static function fromArray(array $array): mixed { $class = static::class; $instance = new $class(); foreach ($array as $item) { $instance[] = $item; } return $instance; } public function __construct(object|array $array = [], int $flags = 0, string $iteratorClass = "ArrayIterator") { parent::__construct($array, $flags, $iteratorClass); if(is_array($array)) { foreach ($array as $item) { $this->validate($item); } } } public function offsetSet($key, $value): void { $this->validate($value); if (is_null($key)) { $this->getIterator()->append($value); } else { $this->getIterator()->offsetSet($key, $value); } } public function append(TypedCollection $value): self { foreach ($value as $item) { $this[] = $item; } return $this; } public function toArray(): array { return $this->getArrayCopy(); } private function validate($value) { $allowedType = $this->getAllowedType(); if (!($value instanceof $allowedType)) { throw new InvalidArgumentException( "Value must be an instance of {$allowedType}" ); } } } The property hooks way abstract class TypedCollection { public array $arr = [] { get => $this->arr; set { $this->validate($value); $this->arr = $value; } } abstract protected function getAllowedType(): string; private function validate($value) { $allowedType = $this->getAllowedType(); if (!($value instanceof $allowedType)) { throw new InvalidArgumentException( "Value must be an instance of {$allowedType}" ); } } } Pros and cons Both will need more code if scalar need to be checked. The array object way pro can add a single item to an array. The array object way con need for toArray method to use array functions. The property hooks way pro no need for an extra method to use array functions. The property hooks way con not possible to add a single item to the array. Conclusion PhpStan can check code using generics. But this doesn't work at runtime. Whatever option you choose it will be better than the array type hinting. I find that the collection instantiation in the code makes it more readable than the anonymus [].

Mar 16, 2025 - 17:25
 0
PHP: making arrays more robust

If PHP had array constraints/generics it would be so much easier to be sure the array items are all of the same type, public array $users.

So what are the options?

The array object way

abstract class TypedCollection extends ArrayObject {

    abstract protected function getAllowedType(): string;

    public static function fromArray(array $array): mixed
    {
        $class = static::class;
        $instance = new $class();

        foreach ($array as $item) {
            $instance[] = $item;
        }

        return $instance;
    }

    public function __construct(object|array $array = [], int $flags = 0, string $iteratorClass = "ArrayIterator")
    {
        parent::__construct($array, $flags, $iteratorClass);

        if(is_array($array)) {
            foreach ($array as $item) {
                $this->validate($item);
            }
        }
    }

    public function offsetSet($key, $value): void
    {
        $this->validate($value);

        if (is_null($key)) {
            $this->getIterator()->append($value);
        } else {
            $this->getIterator()->offsetSet($key, $value);
        }
    }

    public function append(TypedCollection $value): self
    {
        foreach ($value as $item) {
            $this[] = $item;
        }

        return $this;
    }

    public function toArray(): array
    {
        return $this->getArrayCopy();
    }

    private function validate($value) {
        $allowedType = $this->getAllowedType();

        if (!($value instanceof $allowedType)) {
            throw new InvalidArgumentException(
                "Value must be an instance of {$allowedType}"
            );
        }
    }
}

The property hooks way

abstract class TypedCollection {
    public array $arr = [] {
        get => $this->arr;

        set {
            $this->validate($value);

            $this->arr = $value;
        }

    }

       abstract protected function getAllowedType(): string;

    private function validate($value) {
           $allowedType = $this->getAllowedType();

           if (!($value instanceof $allowedType)) {
            throw new InvalidArgumentException(
                "Value must be an instance of {$allowedType}"
            );
           }
    }
}

Pros and cons

Both will need more code if scalar need to be checked.

The array object way pro

  • can add a single item to an array.

The array object way con

  • need for toArray method to use array functions.

The property hooks way pro

  • no need for an extra method to use array functions.

The property hooks way con

  • not possible to add a single item to the array.

Conclusion

PhpStan can check code using generics. But this doesn't work at runtime.

Whatever option you choose it will be better than the array type hinting.
I find that the collection instantiation in the code makes it more readable than the anonymus [].