Edit

Implementing ArrayAccess for Domain Objects

Option 1

In this implementation we will make use of PHPs highly dynamic nature to dynamically access properties of a subtype in a supertype at runtime. Note that this implementation has 2 main caveats:

  • It will not work with private fields
  • It will not go through any getters/setters
<?php

abstract class DomainObject implements ArrayAccess
{
    public function offsetExists($offset): bool
    {
        return isset($this->$offset);
    }

    public function offsetSet($offset, $value): void
    {
        $this->$offset = $value;
    }

    public function offsetGet($offset)
    {
        return $this->$offset;
    }

    public function offsetUnset($offset): void
    {
        $this->$offset = null;
    }
}

Option 2

In this implementation we will dynamically invoke getters/setters. Again we use PHPs dynamic nature to invoke methods on a subtype from a supertype at runtime. This implementation has the following caveats:

  • It relies on a naming convention
  • The semantics of offsetExists can differ
  • offsetUnset will not work with typehinted setters
<?php

abstract class DomainObject implements ArrayAccess
{
    public function offsetExists($offset): bool
    {
        // In this example we say that exists means it is not null
        $value = $this->{"get$offset"}();
        return $value !== null;
    }

    public function offsetSet($offset, $value): void
    {
        $this->{"set$offset"}($value);
    }

    public function offsetGet($offset)
    {
        return $this->{"get$offset"}();
    }

    public function offsetUnset($offset): void
    {
        $this->{"set$offset"}(null);
    }
}

Read-only

You can slightly tweak option 1 or option 2 in order to make array access read-only. This will also circumvent some of the caveats of each option. Simply make offsetSet and offsetUnset throw an exception (i.e. BadMethodCallException).

<?php

abstract class DomainObject implements ArrayAccess
{
    public function offsetExists($offset): bool
    {
        // option 1 or option 2
    }

    public function offsetSet($offset, $value): void
    {
        throw new BadMethodCallException("Array access of class " . get_class($this) . " is read-only!");
    }

    public function offsetGet($offset)
    {
        // option 1 or option 2
    }

    public function offsetUnset($offset): void
    {
        throw new BadMethodCallException("Array access of class " . get_class($this) . " is read-only!");
    }
}