DEV Community

david duymelinck
david duymelinck

Posted on

PHP: making arrays more robust

update: I added more example code and more clarification.

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<User> $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}"
            );
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

The property hooks way

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

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

            $this->arr = $value;
        }

    }

       public function __construct(array $arr)
       {
          $this->arr = $arr;
       }

       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}"
            );
           }
    }
}
Enter fullscreen mode Exit fullscreen mode

Usage

class UserCollection extends Typedcollection {
    protected function getAllowedType(): string
    {
        return User::class;
    }
}
Enter fullscreen mode Exit fullscreen mode

array object

$userCollection = new UserCollection([new User('me')]);

$userCollection[] = new User('you');

$userCollection = $userCollection->append(new UserCollection([new User('me2'), new User('you2')]);

$userArray = $userCollection->toArray(); 
Enter fullscreen mode Exit fullscreen mode

property hooks

$userCollection = new UserCollection([new User('me')]);

$userCollection->arr = array_merge($userCollection->arr, [new User('you')]);

// no need for the two next lines of the array object code.
Enter fullscreen mode Exit fullscreen mode

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 (single element validation)

The array object way con

  • need for toArray method to use array functions (extra function call)

The property hooks way pro

  • no need for an extra method to use array functions (no function call)

The property hooks way con

  • not possible to add a single item to the array (multi element validation)

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 [].

Top comments (0)

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay