Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Sharpening the Battle Result with a Class

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 $6.00

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:

... 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:

... 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:

... 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:

... 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:

... 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:

... 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:

... 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:

... 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()?

... 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:

... 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!

8
Login or Register to join the conversation
Farshad Avatar
Farshad Avatar Farshad | posted 2 years ago

At 7:20
$battleResult->getWinningShip()->getName();
How is getName() connected with getWinningShip()?
getWinningShip is in the BattleResult Class and getName is in the Ship Class.
Does the Class not need to be extended orso? I am confused.

Reply

Hey @Farry7!

No worries! This stuff is hard! Let me see if I can help :).

Let's look at each part:

1) You're correct that $battleResult/code> is an "instance" of the BattleResult class, which means that when we call getWinningShip() we are calling the getWinningShip())` inside the BattleResult class.

2) Now, when we call $battleResult->getWinningShip(), what value does that return? It returns a Ship object.

3) So, when we call ->getName(), that is actually being called on the Ship object, not on BattleResult.

That still... may not be a good explanation, so let's walk through the whole flow :).

A) The battle() method in BattleManager is what's responsible for instantiating the BattleResult object - https://symfonycasts.com/screencast/oo-ep2/battle-result-model#codeblock-ea7db2497c - when it does this, it passes $winningShip as the 2nd argument, which is a Ship object.

B) Then, in Ship's __construct() method, that 2nd argument (which, remember, is a Ship object) is set onto the $winningShip property of BattleResult: https://symfonycasts.com/screencast/oo-ep2/battle-result-model#codeblock-ea7db2497c

C) Finally, when we use the BattleResult object: $battleResult->getWinningShip()->getName(). To be more clear, we could write this one line onto multiple lines:


// the getWinningShip() method returns BattleResult's $winningShip property
// this means that we know that $winningShip is a Ship object
$winningShip = $battleResult->getWinningShip();

// we call the getName() method on the Ship object
$winningShip->getName();

Really, $battleResult->getWinningShip()->getName() is just a longer version of what I wrote above.

Let me know if that helps!

Cheers!

1 Reply
Hicham A. Avatar
Hicham A. Avatar Hicham A. | posted 3 years ago

I am new to OOP, I was lost in chapter 1 especially with mile long Class names lol.

Reply

Hey Travel,

We're sorry about that, and thank you for your feedback! I see you're on a 3rd chapter now, so may I think that you finally nailed the 1st chapter? What exactly was confusing in 1st chapter? Could you tell us a bit more? And also, do you need any help/clarification on some things you didn't get in the 1st chapter?

Cheers!

Reply
Hicham A. Avatar

I am learning a ton from these videos, but I am a novice to programming. To answer your question, I do follow all steps, but sometimes I move on to next chapter even though I did not get everything on the previous chapter. As I mentioned I am new to programming lol. Thank you

Reply

Hey Travel,

Ah, ok, I see. Well, it's totally OK if you don't get *everything* from the video, especially when you're a newbie ;) Sometimes next chapters may answer most of your questions, or make things more obvious for you.

But don't hesitate to ask questions in comments below videos if you didn't get something from the video - we will try to explain it to you from a different side :)

Cheers!

Reply

"And gone are the days of needing to use weird associative arrays: BattleManager::battle() returns a nice BattleResult object." Unless I missed something, you can't call `battle()` as a static function, because it doesn't have a `static` keyword.

Reply

Hey boykodev

That's just a way to reference to a method of a class, like in some sort of documentation file but that is not actual code.

Cheers!

1 Reply
Cat in space

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

userVoice