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.
Let's do something else that's not possible. BattleResult
is an object:
// ... 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()
:
// ... 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 inbattle.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.
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?