phpissetarrayaccess

PHP 5.6: ArrayAccess: Function isset calls offsetGet and causes undefined index notice


I wrote simple PHP class that implements ArrayAccess Interface:

class MyArray implements ArrayAccess
{
    public $value;

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

    public function &offsetGet($offset)
    {
        var_dump(__METHOD__);

        if (!isset($this->value[$offset])) {
            throw new Exception('Undefined index: ' . $offset);
        }

        return $this->value[$offset];
    }

    public function offsetExists($offset)
    {
        var_dump(__METHOD__);

        return isset($this->value[$offset]);
    }

    public function offsetSet($offset, $value)
    {
        var_dump(__METHOD__);

        $this->value[$offset] = $value;
    }

    public function offsetUnset($offset)
    {
        var_dump(__METHOD__);

        $this->value[$offset] = null;
    }
}

It works normally in PHP 7, but the problem in PHP 5.6 and HHVM.

If I call function isset() on undefined index, the PHP will call offsetGet() instead of offsetExists() which will cause Undefined index notice.

In PHP 7, it calls offsetGet() only if offsetExists() returns true, so there is no error.

I think that this is related to PHP bug 62059.

The code is avalible at 3V4L, so you can see what is wrong. I added few more debug calls and throw exception if index is undefined because notices aren't shown in 3V4L: https://3v4l.org/7C2Fs

There shouldn't be any notice otherwise PHPUnit tests will fail. How can I fix this error?


Solution

  • It looks like this is a PHP bug in old versions of PHP and HHVM. Because PHP 5.6 is not supported anymore, this bug will not be fixed.

    Quick fix is to add additional check in method offsetGet() and return null if index is undefined:

    class MyArray implements ArrayAccess
    {
        public $value;
    
        public function __construct($value = null)
        {
            $this->value = $value;
        }
    
        public function &offsetGet($offset)
        {
            if (!isset($this->value[$offset])) {
                $this->value[$offset] = null;
            }
    
            return $this->value[$offset];
        }
    
        public function offsetExists($offset)
        {
            return isset($this->value[$offset]);
        }
    
        public function offsetSet($offset, $value)
        {
            $this->value[$offset] = $value;
        }
    
        public function offsetUnset($offset)
        {
            $this->value[$offset] = null;
        }
    }
    

    See code at 3V4L and zerkms's comments (first, second, third).