A Deal with an Array

In our everyday programming life, we come across arrays literally every day. An array in the programming languages is a powerful and versatile data structure. But with versatility comes complexity. Developers often make use of arrays for storing and transferring data from one function or class to another.

Generally speaking, using arrays to store and transfer data is fine and there is nothing wrong in it but the complexity arises when the function expects the value of just one type from the array in a non-statically typed programming language. Consider an example given below,

$data = [
    new Item(),
    null,
    'foo',
    self::Item,
];

When looping over the data variable and calling ->someMethod() on the value from the array, the loop will work fine for the first iteration and it will result in a fatal error on the next.

PHP Fatal error: Uncaught Error: Call to a member function someMethod() on null

Because we’re trying to execute someMethod() call on the null value. This type of problem sometimes goes unnoticed by developers that results in a runtime error. For this issue, some programming languages have developed a concept of Generics. Generics helps in dealing with the types and provide compile-time safety.

Dealing with Arrays without Generics

Though Generics is a great add-on and it is the ideal solution to the above problem. But for a non-generic supported programming language we can still solve this problem.

If we think about it we can actually solve this problem in many ways, one of them could be to check for the type of value on each iteration.

foreach ($collection as $item) {
    if (!$item instanceof Item) {
        continue;
    }
    
    // Do something
}

The code above is good and helps to fix this issue. But at the same time, it also leads us to another, i.e. low performance because we’re checking for the type of value in each iteration. It’s fine to use the above code when the data is less. In that case, you’d not notice this performance issue. But what if we’re dealing with maybe thousands or millions of records?

Enters Iterator Interface and ArrayAccess Interface

The next possible solution for this problem could be to use the Iterator interface and the ArrayAccess interface. From the documentation of Iterator interface and ArrayAccess interface, we can understand that these are for looping the items inside and storing and retrieving items from the array respectively. An example of the implementation of the interfaces is given below,

class Collection implements Iterator, ArrayAccess
{
    private $position;

    private $array = [];

    public function __construct() {
        $this->position = 0;
    }

    public function current() {
        return $this->array[$this->position];
    }

    public function next() {
        ++$this->position;
    }

    public function key() {
        return $this->position;
    }

    public function valid() {
        return isset($this->array[$this->position]);
    }

    public function rewind() {
        $this->position = 0;
    }

    public function offsetExists($offset) {
        return isset($this->array[$offset]);
    }

    public function offsetGet($offset) {
        return isset($this->array[$offset]) ? $this->array[$offset] : null;
    }

    public function offsetSet($offset, $value) {
        if (is_null($offset)) {
            $this->array[] = $value;
        } else {
            $this->array[$offset] = $value;
        }
    }

    public function offsetUnset($offset) {
        unset($this->array[$offset]);
    }
}

Next, making use of the above collection and we can extend the above collection class to restrict the value from being stored in the collection if it doesn’t matches our pattern.

class ItemCollection extends Collection
{
    public function offsetSet($offset, $value) {
        if (!$value instanceof Item) {
            throw new InvalidArgumentException("value must be instance of Item.");
        }

        parent::offsetSet($offset, $value);
    }
}

Once done, we’re checking for the value only once and avoiding the check on every iteration, i.e. better performance.

$collection = new ItemCollection();
$collection[] = new Item(1);

// This would throw the InvalidArgumentException.
$collection[] = 'abc';

foreach ($collection as $item) {
    echo "{$item->someMethod()}\n";
}

But, a con of the above collection class is that you will have to create a new class for every collection type. Both of the examples above have their own pros and cons and it totally depends on your use case.

That’s it, if you’re new to programming then you might want to read You don’t understand floating points or if you’re interested in databases then How to List the Empty Databases in MySQL is just for you. Thanks for reading. Feel free to correct me or share your reading experience with me in the comments below.