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
$data = [ new Item(), null, 'foo', self::Item, ];
When looping over the data variable and ->someMethod()
PHP Fatal error: Uncaught Error: Call to a member function someMethod() on null
Because we’re trying to someMethod()
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
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
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.