Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Service Classes

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

Well hey! Welcome back! It's time to put our new object-oriented skills into practice. We're working on the same out of this world project: it has ships, you choose them, then they engage in epic battle!

In an editor, far far away, you'll see a simple application that runs this: index.php is the homepage and battle.php does the magic and shows the results. Last time, we created a single class called Ship, which describes all its properties - it's like a container for one ship's details:

117 lines lib/Ship.php
... lines 1 - 2
class Ship
{
private $name;
private $weaponPower = 0;
private $jediFactor = 0;
private $strength = 0;
private $underRepair;
... lines 14 - 115
}

We used this to replace these big associative arrays. Now we deal with cute Ship objects:

126 lines functions.php
... lines 1 - 4
function get_ships()
{
$ships = array();
$ship = new Ship('Jedi Starfighter');
//$ship->setName('Jedi Starfighter');
$ship->setWeaponPower(5);
$ship->setJediFactor(15);
$ship->setStrength(30);
$ships['starfighter'] = $ship;
... lines 15 - 33
return $ships;
... lines 35 - 126

Remove all the Flat Functions!

Having a huge list of flat functions in functions.php is not a good recipe for staying organized. But in just a few minutes, we'll use some new classes to give our app a whole new level of sophistication. We'll get rid of battle() first.

Look at Ship: this is a class that basically just holds data - some people call that "state", but I'll say "data" - and I'm talking about the values on a Ship object's properties. So a Ship object holds data, but it doesn't really do any work. Sure, it has some methods on it, but these just return that data, after doing some small logic at best.

Reason #1 for creating a class is this: we need some organized unit to hold data.

But there's a second big reason to create a class: because you need to do some work. For example, in functions.php, the battle() function does work: we give it 2 Ships, it does some calculations, executes logic to see how different strengths affect each other and ultimately returns the result of that work.

And we're all familiar with creating functions like this. And here's the secret for OO: whenever you get the urge to create a flat function like battle(), don't. Instead, create a class and with a method inside of it.

Create the BattleManager Service Class

Let's do this! Since this function is all about battling, let's create a new class called BattleManager:

<?php
class BattleManager
{
... lines 5 - 57
}

Be as creative as you want with naming: I want to describe that methods in this class will do things related to battling.

Go copy and remove the flat battle() function: paste it into BattleManager. Put public in front of function. Remember, public means that code outside of this class will be able to call this:

... lines 1 - 2
class BattleManager
{
/**
* Our complex fighting algorithm!
*
* @return array With keys winning_ship, losing_ship & used_jedi_powers
*/
public function battle(Ship $ship1, $ship1Quantity, Ship $ship2, $ship2Quantity)
{
... lines 12 - 51
return array(
'winning_ship' => $winningShip,
'losing_ship' => $losingShip,
'used_jedi_powers' => $usedJediPowers,
);
}
}

And yes, you don't have to add public: functions default to public if you say nothing, but let's keep things clear!

That's all you need to change: functions work the same inside or outside of a class: they have arguments, they return stuff.

But we do need to change code where we call this function - in battle.php. So how can we call this? Well, when we want to call a method on Ship, we need to have a Ship object first. The same is true here: we need a BattleManager object first. Start with a new variable called $battleManager and create a new BattleManager object:

98 lines battle.php
... lines 1 - 28
$battleManager = new BattleManager();
... lines 30 - 98

And now say $battleManager, the arrow, then battle():

98 lines battle.php
... lines 1 - 28
$battleManager = new BattleManager();
$outcome = $battleManager->battle($ship1, $ship1Quantity, $ship2, $ship2Quantity);
... lines 31 - 98

Let's give this a shot! Refresh battle.php. Oh no! Class BattleManager not found! Epic fail!

Not really - at the top of functions.php, we have access to the Ship class because we're requiring it. Do the same for BattleManager:

73 lines functions.php
<?php
require_once __DIR__.'/lib/Ship.php';
require_once __DIR__.'/lib/BattleManager.php';
... lines 5 - 73

There is a way where you can reference classes like BattleManager without needing to worry about the require statements. It's called autoloading, it's really common, and you'll learn how to master it in a future episode. But until then: if you have a class, require it.

Go back and refresh!

Cool - totally working.

Now we have 2 reasons to create a class. First, if you have some data - like properties that describe a ship, creating a class for that is nice. You'll create a Ship object whenever you have a set of that data. In get_ships(), we create 4 Ship objects. These types of classes are sometimes called models, because they model something, like a ship.

Second, if you need to make a function that does some work: create a class and put a method in it, like BattleManager. Or, you may put multiple methods inside one class - as long as they are all thematically similar.

You'll create one of these objects - like BattleManager - just one time, before you need to call a method on it. These are sometimes called service classes, because they perform work or service. Organizing your code to use service classes can be tricky, but we'll learn all about that.

Leave a comment!

2
Login or Register to join the conversation

Why would you create a service class for housing a method like 'battle', rather than just putting it on the ship class itself, so that the code reads more like ship1->battle(ship2):

`class Ship
{

[...]

	public method battle($opponent)
	{
		[...]
		return $outcome;
	}

}

$outcome = $ship1->battle($ship2);`

Reply

Hey brendanlawton

That's a good question :)
In this case the battle logic is so simple than it makes sense to put it on the Ship class but it's not a good design because you are adding more responsibilities to that class. What would happen if in the future the battle process requires other dependencies, like an API client? You won't be able to inject that dependency into the Ship class

In short, it's better to have many small classes than one big and ugly class. Cheers!

Reply
Cat in space

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

userVoice