PHP 7 Type Safe Arrays

Here is an example of how to create type safe lists in PHP 7. Influenced by Java Generics, this approach lets your code guarantee the type of data you can store and retrieve from arrays.

To change the type of objects stored in the list, update the second line of code. To change it to a scalar, replace MyType with a scalar type of choice:


<?php

namespace TypeSafeList;

// Replace \stdClass with your class type. If you wish to only store scalars, replace MyType throughout the code with
// your data type. Almost emulates Java generics.
use \stdClass as MyType;

use ArrayAccess, Iterator, Countable;

// A type safe numeric indexed array implementation.
class TypeSafeList implements Iterator, ArrayAccess, Countable
{
private $container = array();
private $position = 0;

// Optionally, allow for this to be initialised from an existing array.
public function __construct(array $items = array())
{
$this->position = 0;
// If this list is initialised with an array, add those items using offsetSet to ensure type safety.
foreach ($items as $value) {
$this->offsetSet($this->position, $value);
$this->next();
}
$this->rewind();
}

/** Provides count functionality. */
public function count()
{
return count($this->container);
}

/** Iterator logic, to enable foreach looping. */
// Here we ensure we always return the type we expect.
public function current() : MyType
{
// No checks for an existing key, let it fail as an array would.
return $this->container[$this->position];
}

// We only want to allow numeric keys since this is a list.
public function key() : int
{
return $this->position;
}

// Increment pointer.
public function next()
{
++$this->position;
}

// Reset pointer.
public function rewind()
{
$this->position = 0;
}

// Verify if a next value would be available.
public function valid() : bool
{
return array_key_exists($this->position, $this->container);
}

/** Array access logic, to enable accessing properties with regular array syntax. */
// Here we enforce the type of the key to be an int, since this is a list.
// Wrapped by the function below.
private function existsOffset(int $offset) : bool
{
return array_key_exists($this->container, $offset);
}

// Wraps the type safe function above.
public function offsetExists($offset) : bool
{
return $this->existsOffset($offset);
}

// Same but for getting the value at a given offset.
private function getOffset(int $offset) : MyType
{
return $this->container[$offset];
}

// Here, we set the scalar or class we want to return.
public function offsetGet($offset) : MyType
{
// No checks if this offset exists - we want it to fail as it would when accessing an array.
return $this->getOffset($offset);
}

// Here, we enforce the type of class or scalar we want to set.
// This function is wrapped by the type unsafe method offsetSet.
private function setOffset(int $offset, MyType $value)
{
$this->container[$offset] = $value;
}

private function pushOffset(MyType $value)
{
$this->container[] = $value; // To allow for $array[] shorthand.
}

// Wrapper for type safe setOffset.
public function offsetSet($offset, $value)
{
if (!is_null($offset)) {
$this->setOffset($offset, $value);
} else {
$this->pushOffset($value);
}
}

private function unsetOffset(int $offset)
{
unset($this->container[$offset]);
// Reset keys.
$this->container = array_values($this->container);
}

public function offsetUnset($offset)
{
$this->unsetOffset($offset);
}
}

Examples:


$example = new TypeSafeList(array(new class extends MyType {
public $value = "First class.";
}));

$example[] = new class extends MyType {
public $value = "Second class.";
};

for ($i = 0; $i < count($example); $i++) { echo $example[$i]->value . "\n";
}

try {
$example[] = new class {
public $value = "Fourth class.";
};
} catch (\TypeError $ex) {
echo "Invalid data type: \n";
die($ex->getMessage() . "\n");
}

Leave a Reply

Your email address will not be published. Required fields are marked *