Home » Php » php – Function return type hinting for an array of objects in PHP7

php – Function return type hinting for an array of objects in PHP7

Posted by: admin April 23, 2020 Leave a comment

Questions:

I am very happy with the new features in PHP 7. But I am confused on how to return an array of objects in PHP 7.

For example, we have a class Item, and we want to return an array of objects of this class from our function:

function getItems() : Item[] {
}

But it does not work this way.

How to&Answers:

I actually understand what you mean, but the answer unfortunately is that you can’t do that. PHP7 lacks that kind of expressivity, so you can either declare your function to return “array” (a generic array) or you have to create a new class ItemArray which is an array of Item (but that meaning you will have to code it yourself).

There is currently no way to express “I want an array of Item” instances.

EDIT: As an added reference, here the “array of” RFC of what you wanted to do, it has been declined due to various reasons.

Answer:

This is called Generics, unfortunately we won’t see this feature any time soon. You can type hint this way though using docblocks.

PHP editor (IDE) like PhpStorm supports this very well and will properly resolve the class when iterating over such array.

/**
 * @return YourClass[]
 */
public function getObjects(): iterable

PHPStorm also supports nested arrays:

/**
 * @return YourClass[][]
 */
public function getObjects(): iterable

Answer:

The current version of PHP doesn’t support a built-in type hinting for an array of objects, as there is no such data type as “an array of objects”. A class name can be interpreted as a type in certain contexts, as well as array, but not both at a time.

Actually you can implement such kind of strict type hinting by creating a class based on the ArrayAccess interface, e.g.:

class Item
{
    protected $value;

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

class ItemsArray implements ArrayAccess
{
    private $container = [];

    public function offsetSet($offset, $value)
    {
        if (!$value instanceof Item) {
            throw new Exception('value must be an instance of Item');
        }

        if (is_null($offset)) {
            $this->container[] = $value;
        } else {
            $this->container[$offset] = $value;
        }
    }

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

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

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


function getItems() : ItemsArray
{
    $items = new ItemsArray();
    $items[0] = new Item(0);
    $items[1] = new Item(2);
    return $items;
}

var_dump((array)getItems());

Output

array(2) {
  ["ItemsArrayitems"]=>
  array(0) {
  }
  ["container"]=>
  array(2) {
    [0]=>
    object(Item)#2 (1) {
      ["value":protected]=>
      int(0)
    }
    [1]=>
    object(Item)#3 (1) {
      ["value":protected]=>
      int(2)
    }
  }
}

Answer:

You can achieve your intended behavior with a custom array class


function getItems() : ItemArray {
  $items = new ItemArray();
  $items[] = new Item();
  return $items;
}

class ItemArray extends \ArrayObject {
    public function offsetSet($key, $val) {
        if ($val instanceof Item) {
            return parent::offsetSet($key, $val);
        }
        throw new \InvalidArgumentException('Value must be an Item');
    }
}

Thanks to bishop’s answer here