Buy

Sharpening the Battle Result with a Class

The most obvious time you should create a class is when you are passing around an associative array of data. Check out the battle() function: it returns an associatve array - with winning_ship, losing_ship and used_jedi_powers keys:

66 lines lib/BattleManager.php
... lines 1 - 2
class BattleManager
{
... lines 5 - 9
public function battle(Ship $ship1, $ship1Quantity, Ship $ship2, $ship2Quantity)
{
... lines 12 - 51
return array(
'winning_ship' => $winningShip,
'losing_ship' => $losingShip,
'used_jedi_powers' => $usedJediPowers,
);
}
... lines 58 - 64
}

We use this in battle.php, set it to an $outcome variable, then reference all those keys to print stuff further down:s

99 lines battle.php
... lines 1 - 30
$outcome = $battleManager->battle($ship1, $ship1Quantity, $ship2, $ship2Quantity);
... lines 32 - 77
<?php if ($outcome['winning_ship'] == null): ?>
Both ships destroyed each other in an epic battle to the end.
<?php else: ?>
The <?php echo $outcome['winning_ship']->getName(); ?>
<?php if ($outcome['used_jedi_powers']): ?>
used its Jedi Powers for a stunning victory!
<?php else: ?>
overpowered and destroyed the <?php echo $outcome['losing_ship']->getName() ?>s
<?php endif; ?>
<?php endif; ?>
... lines 88 - 99

Ah man, I hate this kind of stuff. It's not obvious at all what's inside this $outcome variable or whether the keys it has now might be missing or different in the future. When you see questionable code like this, you need to be thinking: this is perfect for a class.

Creating the BattleResult Model Class

Let's create one! Now, what to call this new class. Well, this information summarizes a battle result - let's use that - a new class called BattleResult:

16 lines lib/BattleResult.php
... lines 1 - 2
class BattleResult
{
... lines 5 - 14
}

Ok, let's think about this: it'll need to hold data for the winning ship, the losing ship and whether jedi powers were used. So, let's create 3 private properties called $usedJediPowers, $winningShip and $losingShip:

16 lines lib/BattleResult.php
... lines 1 - 2
class BattleResult
{
private $usedJediPowers;
private $winningShip;
private $losingShip;
... lines 8 - 14
}

Look at Ship: our other model-type class that holds data. There are two ways we can set the data. One way is by making a __construct() function. Here, we're saying: "Hey, when you create a new Ship object, you need to pass in the name as an argument":

117 lines lib/Ship.php
... lines 1 - 2
class Ship
{
private $name;
... lines 6 - 14
public function __construct($name)
{
$this->name = $name;
... lines 18 - 19
}
... lines 21 - 115
}

For the other properties, we created public functions - like setStrength(), setWeaponPower() and getJediFactor():

117 lines lib/Ship.php
... lines 1 - 2
class Ship
{
... lines 5 - 36
public function setStrength($number)
{
if (!is_numeric($number)) {
throw new \Exception('Strength must be a number, duh!');
}
$this->strength = $number;
}
... lines 45 - 100
/**
* @param int $weaponPower
*/
public function setWeaponPower($weaponPower)
{
$this->weaponPower = $weaponPower;
}
/**
* @param int $jediFactor
*/
public function setJediFactor($jediFactor)
{
$this->jediFactor = $jediFactor;
}
... lines 116 - 117

Both ways are fine - but I like to use the `__construct()` strategy for any properties that are required. You must give your ship a name - it doesn't make sense to have a nameless Ship fighting battles. How will they know who to write songs about?

A BattleResult only makes sense with all of this information - that's perfect for setting via the constructor! Create a new public function __construct() with $usedJediPowers, $winningShip and $losingShip. These argument names don't need to match the properties, it's just nice. Now, assign each property to that variable: $this->usedJediPowers = $usedJediPowers, $this->winningShip = $winningShip and $this->losingShip = $losingShip:

16 lines lib/BattleResult.php
... lines 1 - 2
class BattleResult
{
private $usedJediPowers;
private $winningShip;
private $losingShip;
public function __construct($usedJediPowers, $winningShip, $losingShip)
{
$this->usedJediPowers = $usedJediPowers;
$this->winningShip = $winningShip;
$this->losingShip = $losingShip;
}
}

Ok, this little data wrapper is done.

Passing BattleResult around

So let's use it inside battle(): instead of returning that array, return a new BattleResult and pass it $usedJediPowers, $winningShip and $losingShip:

62 lines lib/BattleManager.php
... lines 1 - 2
class BattleManager
{
... lines 5 - 9
public function battle(Ship $ship1, $ship1Quantity, Ship $ship2, $ship2Quantity)
{
... lines 12 - 51
return new BattleResult($usedJediPowers, $winningShip, $losingShip);
}
... lines 54 - 60
}

But hey, we're referencing a class, so make sure you require it in bootstrap.php:

7 lines bootstrap.php
... lines 1 - 5
require_once __DIR__.'/lib/BattleResult.php';

So where is battle() being called? It's at the top of battle.php - and this $outcome variable used to be that associative array - now it's a fancy BattleResult object:

99 lines battle.php
... lines 1 - 30
$outcome = $battleManager->battle($ship1, $ship1Quantity, $ship2, $ship2Quantity);
... lines 32 - 99

This means that our code below - the stuff that treats $outcome like an array - should blow up.:

99 lines battle.php
... lines 1 - 70
<?php if ($outcome['winning_ship']): ?>
<?php echo $outcome['winning_ship']->getName(); ?>
<?php else: ?>
Nobody
<?php endif; ?>
... lines 76 - 99

Let's see some fireworks! Boom error!

Cannot use object of type BattleResult as array on line 71.

But we do need to get the winning ship from the BattleResult object. Is that possible right now? No - the $winningShip property is private. If we want to access it from outside the class, we need a public function that returns it for us. We did this same thing in Ship with methods like getName().

Type-Hinting Arguments

But before we add some methods - think about the 3 arguments. What are they? Well, $usedJediPowers is a boolean and the other two are Ship objects. And whenever you have an argument that is an object, you can choose to type-hint it by putting the name of the class in front of it:

45 lines lib/BattleResult.php
... lines 1 - 2
class BattleResult
{
... lines 5 - 13
public function __construct($usedJediPowers, Ship $winningShip, Ship $losingShip)
{
$this->usedJediPowers = $usedJediPowers;
$this->winningShip = $winningShip;
$this->losingShip = $losingShip;
}
... lines 20 - 43
}

But this doesn't change any behavior - it just means that if you pass something that's not a Ship object on accident, you'll get a really nice error. And there's one other benefit - auto-completion in your editor! PhpStorm now knows what these variables are.

Adding Getter Methods

Ok, back to what we were doing. We need to access the private properties from outside this class. To do that, we'll create some public functions. Start with public function getWinningShip(). This will just return $this->winningship:

45 lines lib/BattleResult.php
... lines 1 - 2
class BattleResult
{
... lines 5 - 31
public function getWinningShip()
{
return $this->winningShip;
}
... lines 36 - 43
}

We'll do this for each property. But actually, I can make PhpStorm write these methods for me! Suckers! Delete getWinningShip(), then right-click, go to "Generate" and select "Getters". Select all 3 properties, say abracadabra, and let it work its magic.

It even added some PHPDoc above each with an @return mixed - which basically is PhpStorms' way of saying "I don't know what this method returns". So let's help it - the first returns a boolean and the other two return a Ship object:

45 lines lib/BattleResult.php
... lines 1 - 2
class BattleResult
{
... lines 5 - 20
/**
* @return boolean
*/
public function isUsedJediPowers()
{
return $this->usedJediPowers;
}
/**
* @return Ship
*/
public function getWinningShip()
{
return $this->winningShip;
}
/**
* @return Ship
*/
public function getLosingShip()
{
return $this->losingShip;
}
}

This comment stuff is optional - but it helps other developers read our code and gives us auto-completion when we call these methods.

Name the Methods Awesomely

Check out the first method - getUsedJediPowers(). Is it clear what the method returns? It's kind of bad English, and that's a shame. This method will return whether or not Jedi powers were used to win this battle. Let's give it a name that says that - how about wereJediPowersUsed()?

45 lines lib/BattleResult.php
... lines 1 - 2
class BattleResult
{
... lines 5 - 20
/**
* @return boolean
*/
public function wereJediPowersUsed()
{
return $this->usedJediPowers;
}
... lines 28 - 43
}

Using get and then the method name is a good standard, but you can name these methods however you want.

Using BattleResult for Battle #Wins

Now we can finally go back to battle.php and start using these public methods. Start by renaming $outcome to $battleResult - it's more clear this is a BattleResult object:

99 lines battle.php
... lines 1 - 30
$battleResult = $battleManager->battle($ship1, $ship1Quantity, $ship2, $ship2Quantity);
... lines 32 - 99

Below, use $battleResult->getWinningShip():

99 lines battle.php
... lines 1 - 30
$battleResult = $battleManager->battle($ship1, $ship1Quantity, $ship2, $ship2Quantity);
... lines 32 - 70
<?php if ($battleResult->getWinningShip()): ?>
... lines 72 - 99

Except, where's my auto-completion on that method? This will work, but PhpStorm is highlighting the method like it's wrong. It doesn't know that $battleResult is a BattleResult object.

Why? Look at battle(). We are returning a BattleResult, but oh no, the @return above this method still advertises that this method returns an array. Fix that with @return BattleResult:

62 lines lib/BattleManager.php
... lines 1 - 2
class BattleManager
{
/**
* Our complex fighting algorithm!
*
* @return BattleResult
*/
public function battle(Ship $ship1, $ship1Quantity, Ship $ship2, $ship2Quantity)
{
... lines 12 - 52
}
... lines 54 - 60
}

Ok, now PhpStorm is acting friendly - the angry highightling on the method is gone. Now update the other spots: $battleResult->getWinningShip()->getName(): thank you auto-complete. Use that same method once more, and in the if statement, use that nice wereJediPowersUsed() method. Finish with $battleResult->getLosingShip():

99 lines battle.php
... lines 1 - 70
<?php if ($battleResult->getWinningShip()): ?>
<?php echo $battleResult->getWinningShip()->getName(); ?>
<?php else: ?>
Nobody
<?php endif; ?>
... lines 76 - 77
<?php if ($battleResult->getWinningShip() == null): ?>
Both ships destroyed each other in an epic battle to the end.
<?php else: ?>
The <?php echo $battleResult->getWinningShip()->getName(); ?>
<?php if ($battleResult->wereJediPowersUsed()): ?>
used its Jedi Powers for a stunning victory!
<?php else: ?>
overpowered and destroyed the <?php echo $battleResult->getLosingShip()->getName() ?>s
<?php endif; ?>
<?php endif; ?>
... lines 88 - 99

I think we're done. Refresh to try it! Ship it!

And gone are the days of needing to use weird associative arrays: BattleManager::battle() returns a nice BattleResult object. And we're in full control of what public methods we put on that.

Leave a comment!