gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
Yay! We got rid of a flat function. Woh - not so fast: inside battle()
,
we're calling a flat function: didJediDestroyShipUsingTheForce()
:
... lines 1 - 2 | |
class BattleManager | |
{ | |
... lines 5 - 9 | |
public function battle(Ship $ship1, $ship1Quantity, Ship $ship2, $ship2Quantity) | |
{ | |
... lines 12 - 18 | |
if (didJediDestroyShipUsingTheForce($ship1)) { | |
... lines 20 - 23 | |
} | |
... lines 25 - 56 | |
} | |
} |
No bueno!
This lives at the bottom of functions.php
. In our app, this is only called
from inside battle()
, and since it obviously relates to battles, let's
move it into BattleManager
. Make it a private function
:
... lines 1 - 2 | |
class BattleManager | |
{ | |
... lines 5 - 58 | |
private function didJediDestroyShipUsingTheForce(Ship $ship) | |
{ | |
$jediHeroProbability = $ship->getJediFactor() / 100; | |
return mt_rand(1, 100) <= ($jediHeroProbability*100); | |
} | |
} |
Why did I make it private? Well, do we need use this function from outside
of this class? No - the only code using it is up in battle()
, so this is
a perfect candidate to be private
.
Above in battle()
, update the calls to be $this->didJediDestroyShipUsingTheForce()
.
The "force" of our app is happy again:
... lines 1 - 2 | |
class BattleManager | |
{ | |
... lines 5 - 9 | |
public function battle(Ship $ship1, $ship1Quantity, Ship $ship2, $ship2Quantity) | |
{ | |
... lines 12 - 18 | |
if ($this->didJediDestroyShipUsingTheForce($ship1)) { | |
... lines 20 - 23 | |
} | |
if ($this->didJediDestroyShipUsingTheForce($ship2)) { | |
... lines 26 - 29 | |
} | |
... lines 31 - 56 | |
} | |
... lines 58 - 64 | |
} |
Now, if someday we did want to use this function from outside of BattleManager
,
then we could change it to public
. Ok, so why not just make everything
public
- isn't that more flexible? Yes, but making this private
is nice: it means that if I want to change this function - add arguments
or even change what it returns - I know that the only code that will be
affected will be right inside this class. If it's public, who knows what
code I might break in my app?
Start with private
, make it public
only if you need. The same rule goes
for protected
- something we'll talk about later with inheritance.
Let's make sure we didn't bust things. Refresh!
Yes!
In functions.php
, only the flat get_ships()
function remains. You guys
know what do to: move it into a class!
Should we move it into BattleManager
? No - it doesn't relate to battles.
Instead, create a new class for this - how about ShipLoader
:
... lines 1 - 2 | |
class ShipLoader | |
{ | |
... lines 5 - 36 | |
} |
Let's work our magic: go grab get_ships()
and move it into ShipLoader
.
Remove the old commented code and make the function public
. Also, rename
it from get_ships()
to getShips()
- that's a more common naming standard
for methods in a class:
... lines 1 - 2 | |
class ShipLoader | |
{ | |
public function getShips() | |
{ | |
$ships = array(); | |
$ship = new Ship('Jedi Starfighter'); | |
... lines 10 - 33 | |
return $ships; | |
} | |
} |
Yep, that's great! Now we need to update the code that calls this function.
But first, open functions.php
and require
the new ShipLoader.php
:
... lines 1 - 4 | |
require_once __DIR__.'/lib/ShipLoader.php'; |
getShips()
is used in battle.php
and index.php
- start there. To
call the method, create a $shipLoader
variable and create a new ShipLoader()
object. Now, just $shipLoader->getShips()
:
require __DIR__.'/functions.php'; | |
$shipLoader = new ShipLoader(); | |
$ships = $shipLoader->getShips(); | |
... lines 6 - 119 |
Do the same thing in battle.php
:
require __DIR__.'/functions.php'; | |
$shipLoader = new ShipLoader(); | |
$ships = $shipLoader->getShips(); | |
... lines 6 - 99 |
I think it's time to try it. Click to create a new battle. Looks pretty good.
Setup a new battle and, Engage. Ok! battle.php
works too!
AND, all the flat functions are gone! Object-orient all the things! So if
you look in functions.php
, well, there aren't any functions here: just
require
statements, and even those we'll get rid of eventually. To celebrate,
give this a more appropriate name: bootstrap.php
. Update this in battle.php
:
require __DIR__.'/bootstrap.php'; | |
... lines 3 - 99 |
and index.php
:
require __DIR__.'/bootstrap.php'; | |
... lines 3 - 119 |
Refresh once more! Let's keep going.
Yo Tariq I.!
You're definitely thinking the correct way. The key is that index.php
is the file that's loaded first and it has require __DIR__.'/functions.php';
on the first line. This means that all of those functions are available to any other files for the rest of this request.
But, you do touch on an interesting problem with require
. In this case, the didJediDestroyShipUsingTheForce()
function is available inside BattleManager.php
because an earlier file index.php
required it. But... when you look at BattleManager.php
, you can't see this - you don't know that this is happening unless you look at the whole request flow. And what if we create another file - otherPage.php
- where we don't require functions.php
and then we try to use BattleManager
? In that case, the function would suddenly not exist inside of BattleManager
.
This is a LONG way of saying that require statements can be a bit confusing (do I need a require here or not?) and error-prone. In OO episode 4, we talk about the solution to this: an "autoloader": https://symfonycasts.com/screencast/oo-ep4/autoloading-awesomeness - this is where you basically teach PHP to do the require statements automatically, so you don't need to worry about them.
I hope that helps!
Cheers!
Hi, You used two instances of the ShipLoader, so you might be battling ships that are on service. Right?
Hey Simón B.!
You used two instances of the ShipLoader
Do you mean that on the first request (index.php) there is one ShipLoader instance and when you submit the form (battle.php) that creates a second ShipLoader instance? if so, you are 100% correct! We are working with different ShipLoader instances on each request - that's just a property of how php works. So, within a single request, you typically only need one instance of a service (ShipLoader)... because (if you needed to) you could call $shipLoader->getShips()
multiple times. But yes, on the next request, all your objects (including your services) will be instantiated fresh. Ideally, your service objects will behave the same each time they are instantiated - i.e. $shipLoader->getShips()
will return the same array of Ship objects.
Does that help? Or... did I misunderstand your question entirely? :p
Cheers!
But doesn't that change the underRepair property of the ship objects, as this property exploits the mt_rand() function ?
Yo Tariq I.!
Nice avatar, btw :). You're totally right - the mt_rand()
makes this all a bit less realistic, and means that the individual Ship
objects on one request won't actually perfectly match the objects on the next request. In a real app, where we would probably load all this data from the database, the Ship
objects would have identical data between the requests (even though they are technically different instances in memory) because they would be loading data from the same place (and no randomness).
So, yea, you're definitely thinking correctly!
Cheers!
Good tip! I didn't show that here because I want them to understand what changes and why. But in real life, I absolutely use this :).
Without including require_once __DIR__.'functions.php'; on top of lib/BattleManager.php file, how come the line 19 of lib/BattleManager.php file succeed to be executed ? As the didJediDestroyShipUsingTheForce() function was defined in functions.php ( before moving it to the BattleManager class ), wasn't it supposed to be needed to add require_once __DIR__.'functions.php'; on top of lib/BattleManager.php file ?