PHP 7.4: The Strange Case of ArrayObject

When upgrading a website from one version of PHP to the next, there is one phrase that inevitably strikes fear into the heart of even the most intrepid PHP developer: Backwards Compatible Breaks. There … I’ve said it.  Might as well be screaming Voldemort, Voldemort, Voldemort at the top of my lungs in Hogwarts, right?  (Harry Potter fans take note!) OK … but what does this have to do with The Strange Case of ArrayObject?  To explain, let’s first have a look at ArrayObject and get_object_vars().

 

Enter get_object_vars()

Although hotly disputed by many reputable developers, personally I really like get_object_vars().  There are caveats to its use, of course.  For example, if used from the “outside,” it can only read public properties.  Otherwise, it’s extremely simple to use, and is an excellent tool for converting object properties into an associative array.

In the simple code block shown here, an ArrayObject instance is created and initialized with an array.  Running getArrayCopy()  and get_object_vars() should return the same results … right?

$obj = new ArrayObject(['A' => 1,'B' => 2,'C' => 3]);
var_dump($obj->getArrayCopy());
var_dump(get_object_vars($obj));

To test this absurdly simple example, I spun up two Linux for PHP docker containers, one running PHP 7.3, and the other running PHP 7.4.  As expected, when running this example under PHP 7.3, identical results were obtained:

php 7.3 get_object_vars()

Whistling happily to myself, I then ran the exact same code on the PHP 7.4 docker container.  I then had to rub my eyes, clean my glasses (not done often enough, at least according to my wife!), and look again.  It seems there’s something missing …  Have a look for yourselves!

php 7.4 get_object_vars()

At this point, as you may have suspected, I stopped whistling.  Uh … hmmm …  NOTE TO SELF: after upgrading to PHP 7.4, make sure you use getArrayCopy() and not get_object_vars() when working with ArrayObject!

 

It Gets Worse!

BC breaks … BC breaks … I kept mumbling to myself, they’re normal, right?  even expected, right?.  Taking a deep breath, I barreled ahead with my test.  Thinking back to the countless times I’ve used get_object_vars(), I created another simple example.  How many of you have done something like the getVars() method shown here?  (No, don’t answer that question: I don’t want to be the only one in the room to raise my hand!)

class Test extends ArrayObject
{
    public $id = 12345;
    public $name = 'Cal Evans';
    public function getVars()
    {
        return get_object_vars($this);
    }
}

Now, granted, when extending ArrayObject, even running versions of PHP earlier than 7.4, you can expect the unexpected.  Putting the above class to use, here is an example code block:

$test = new Test(['A' => 1,'B' => 2,'C' => 3]);
var_dump($test->getVars());
var_dump($test->getArrayCopy());
var_dump($test);

So, again, a class that extends ArrayObject is initialized with an array.  Now here’s where it gets even more strange.  What you would expect is that get_object_vars() would return both $id as well as $name, in addition to whatever was created upon instantiation, right?  Wrong!  Here is the output from PHP 7.3:

get_object_vars() as method

 

And the output from PHP 7.4:

get_object_vars() as method

On second glance, the output from PHP 7.4 seems to make more sense.  If we run get_object_vars() internally, we would expect it to pick up the internally defined properties.  On the other hand, getArrayCopy() returns the value of the array created upon initialization.  Thus, to get both, the getVars() method would just need to combine the two as shown in this example:

public function getVars()
{
    return array_merge(
        get_object_vars($this),
        $this->getArrayCopy()
    );
}

 

Got It … Anything Else?

Well … now that you ask … yes, there is something else, and please stop moaning and pounding your desk won’t you?  The changes internal to PHP 7.4 that resulted in the behavior demonstrated above also affects array navigation functions such as reset(), current() and next().  In case you haven’t used these functions extensively, they’re “old school” ways of navigating arrays.  reset() moves the array pointer to the “top” of the array (i.e. first element), current() returns the current value, and next() moves the array pointer to the next element.

As before, we start with a simple ArrayObject instance, initialized with an associative array:

$obj = new ArrayObject(['A','B','C']);

Here is the “traditional” way of navigating arrays, using reset(), current() and next():

reset($obj);
while ($item = current($obj)) {
    echo $item . ' ';
    next($obj);
}

And here is the same thing, using iteration methods instead:

$it = $obj->getIterator();
while ($item = $it->current()) {
    echo $item . ' ';
    $it->next();
}

As you probably expected, given that you’ve read this article up to this point, the results in PHP 7.3 are identical:

ArrayObject Iteration

And, as you might also have expected, the traditional array navigation functions don’t work against a PHP 7.4 ArrayObject instance:

ArrayObject Iteration

And that’s all she wrote!

 

Conclusion

Recognition of potential code breaks is half the battle.  As long as you are aware of potential breaks, you know what to look for potential breaks in your application post-upgrade.  As you have from the test code discussed in this post, using the internal methods will return results consist with earlier versions of PHP 7.4.  You can still use get_object_vars(), however you need to be aware that the internally generated storage array is treated differently in PHP 7.4.  This means that it may be necessary to mix the results of get_object_vars() with getArrayCopy() to arrive at the desired results.

All in all, it’s extremely important to bear in mind that a PHP 7.4 upgrade is definitely a good thing.  Performance has been improved, and there are many many enhancements that give you greater control over your code.  To name just one: PHP 7.4 introduces property level types.  These and other improvements will be addressed here in future posts.

SHAMELESS PLUG: we offer an online PHP 7.4 update course that covers, in approximately 2.5 hours, not only backwards compatibility breaks, but also infrastructural changes, language enhancements, and the ability to make direct “C” language calls, and other really cool stuff.

Thanks for reading and Happy Coding!