Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

ArrayAccess: Treat your Object like an Array

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $10.00

Let's do something else that's not possible. BattleResult is an object:

112 lines battle.php
... lines 1 - 39
<html>
... lines 41 - 59
<body>
<div class="container">
... lines 62 - 73
<div class="result-box center-block">
<h3 class="text-center audiowide">
Winner:
<?php if ($battleResult->isThereAWinner()): ?>
<?php echo $battleResult->getWinningShip()->getName(); ?>
<?php else: ?>
Nobody
<?php endif; ?>
</h3>
... lines 83 - 101
</div>
... lines 103 - 108
</div>
</body>
</html>

But, use your imagination: its only real job is to hold these three properties, plus it does have one extra method: isThereAWinner():

... lines 1 - 4
class BattleResult
{
private $usedJediPowers;
private $winningShip;
private $losingShip;
... lines 10 - 51
public function isThereAWinner()
{
return $this->getWinningShip() !== null;
}
}

But for the most part, it's kind of a glorified associative array.

Let's get crazy and treat the object like an array: say $battleResults['winningShip']->getName():

112 lines battle.php
... lines 1 - 39
<html>
... lines 41 - 59
<body>
<div class="container">
... lines 62 - 73
<div class="result-box center-block">
<h3 class="text-center audiowide">
Winner:
<?php if ($battleResult->isThereAWinner()): ?>
<?php echo $battleResult['winningShip']->getName(); ?>
... lines 79 - 80
<?php endif; ?>
</h3>
... lines 83 - 101
</div>
... lines 103 - 108
</div>
</body>
</html>

That shouldn't work, but let's refresh and try it. Ah yes:

Cannot use object of type Model\BattleResult as array in battle.php.

It's right - we're breaking the rules.

The ArrayAccess Interface

After the last chapter, you might expect me to go into BattleResults and add some new magic method down at the bottom that would make this legal. But nope!

There is actually a second way to add special behavior to a class, and this method involves interfaces. Basically, PHP has a group of built-in interfaces and each gives your class a different super-power if you implement it.

The most famous is probably \ArrayAccess.

Of course as soon as you implement any interface, it will require you to add some methods. In this case, PhpStorm is telling me that I needed offsetGet(), offsetUnset(), offsetExist() and offsetSet():

... lines 1 - 4
class BattleResult implements \ArrayAccess
{
... lines 7 - 75
}

Ok, let's do that, but with a little help from my editor. In PhpStorm, I can go to the "Code"->"Generate" menu and select "Implement Methods". Select these 4:

... lines 1 - 4
class BattleResult implements \ArrayAccess
{
... lines 7 - 56
public function offsetExists($offset)
{
... line 59
}
public function offsetGet($offset)
{
... line 64
}
public function offsetSet($offset, $value)
{
... line 69
}
public function offsetUnset($offset)
{
... line 74
}
}

Cool!

And just by doing this, it's legal to treat our object like an array. And when someone tries to access some array key - like winningShip - we'll just return that property instead.

So, for offsetExists(), use a function called property_exists() and pass it $this and $offset: that will be whatever key the user is trying to access:

... lines 1 - 4
class BattleResult implements \ArrayAccess
{
... lines 7 - 56
public function offsetExists($offset)
{
return property_exists($this, $offset);
}
... lines 61 - 75
}

For offsetGet(), return $this->$offset and in offsetSet(), say $this->$offset = $value:

... lines 1 - 4
class BattleResult implements \ArrayAccess
{
... lines 7 - 61
public function offsetGet($offset)
{
return $this->$offset;
}
public function offsetSet($offset, $value)
{
$this->$offset = $value;
}
... lines 71 - 75
}

And finally - even though it would be weird from someone to unset one of our keys, let's make that legal by removing the property: unset($this->$offset):

... lines 1 - 4
class BattleResult implements \ArrayAccess
{
... lines 7 - 71
public function offsetUnset($offset)
{
unset($this->$offset);
}
}

Ok, this is a little weird, but it works. Now, just like with magic methods, don't run and use this everywhere for no reason. But occasionally, it might come in handy. And more importantly, you will see this sometimes in outside libraries. This means that even though something looks like an array, it might actually be an object.

Leave a comment!

2
Login or Register to join the conversation
Arno Avatar

I don't quite get why an Interface provides 'superpowers' to a class.The
interface itself doesn't implement the methods, and the methods we add
don't seem to do much, they are not called by battle.php line 77?

Reply

Hey Arno,

Because interfaces are the lowest level thing in OOP, *they* work behind the scene. It's not enough to just add those method. If you add all that implementation we added here - you won't be able to treat objects of the class like arrays. Interface is required, and so exactly interface tell the PHP how it can work with those objects. Yes, implementation also improtant, but without interfaces that implementation is just a piece of code that will never be called by PHP.

On line 77, we don't... but on 78 we do :) There's where we treat our $battleResult object as an array, i.e. call "$battleResult['winningShip']".

I hope this clarify things for you :)

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

userVoice