Type Hinting?!
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.
When we submit the form it goes to battle.php
and we see this nasty error:
Argument 1 passed to battle() must be of the type array, object given
It comes from functions.php
on line 74 and called on battle.php
on line
- Let's start with
battle.php
, sure enough we can see the problem is with the battle function, let me show you why. But first, let's dump$ship1
and$ship2
:
// ... lines 1 - 25 | |
$ship1 = $ships[$ship1Name]; | |
$ship2 = $ships[$ship2Name]; | |
var_dump($ship1, $ship2);die; | |
// ... lines 30 - 99 |
Let's see here, up top we call getShips()
which returns an array of objects.
Then we read the $_POST
data to figure out which two ships are fighting.
Then, down here, we get the ship objects off this array:
// ... lines 1 - 3 | |
$ships = get_ships(); | |
// ... line 5 | |
$ship1Name = isset($_POST['ship1_name']) ? $_POST['ship1_name'] : null; | |
$ship1Quantity = isset($_POST['ship1_quantity']) ? $_POST['ship1_quantity'] : 1; | |
$ship2Name = isset($_POST['ship2_name']) ? $_POST['ship2_name'] : null; | |
$ship2Quantity = isset($_POST['ship2_quantity']) ? $_POST['ship2_quantity'] : 1; | |
// ... lines 10 - 25 | |
$ship1 = $ships[$ship1Name]; | |
$ship2 = $ships[$ship2Name]; | |
// ... lines 28 - 99 |
So this should dump two objects. And it does: we have the Jedi Starfighter and the Super Star Destroyer.
What is Type-Hinting?
Next, let's look in the battle()
function which lives in functions.php
:
// ... lines 1 - 73 | |
function battle(array $ship1, $ship1Quantity, array $ship2, $ship2Quantity) | |
{ | |
// ... lines 76 - 120 | |
} | |
// ... lines 122 - 128 |
Here's the issue the $ship1
and $ship2
arguments have "array" in front
of them. This tells PHP that this argument must be an array and if someone
passes something other than an array, I want you to throw a huge error. So
let's see that error again, it says:
Argument 1 passed to battle() must be of the type array, object given
This is called a type hint and the only purpose of a type hint in PHP is to get better errors: it doesn't change the behavior. We can just take the type hint off like this and that will fix the error:
// ... lines 1 - 73 | |
function battle($ship1, $ship1Quantity, $ship2, $ship2Quantity) | |
{ | |
// ... lines 76 - 120 | |
} | |
// ... lines 122 - 128 |
And down here, knowing that $ship1
is actually an object, instead of using
the array syntax we can call the getStrength()
method. Let's go ahead and
dump $ship1Health
to make sure it's working:
// ... lines 1 - 73 | |
function battle($ship1, $ship1Quantity, $ship2, $ship2Quantity) | |
{ | |
$ship1Health = $ship1->getStrength() * $ship1Quantity; | |
var_dump($ship1Health);die; | |
// ... lines 78 - 121 | |
} | |
// ... lines 123 - 129 |
Just by removing the type hint it tells PHP to stop making sure it's an array, just let anything in and be ok with it. Refresh! This time it's printing out 60 which means it's printing out the ship's mighty strength correctly.
Type-Hinting Saves your Butt
The type hint is a useful thing, not from a functionality standpoint, but
for knowing when you're doing something wrong. Let's go back to battle.php
and pretend that something went wrong here by changing our object $ship1
to the string foo
:
// ... lines 1 - 28 | |
$outcome = battle('foo', $ship1Quantity, $ship2, $ship2Quantity); | |
// ... lines 30 - 97 |
When we refresh this time, we get this really weird error:
Call to a member function getStrength() on a non-object
You're going to see this error a lot and it's coming from line 76:
// ... lines 1 - 73 | |
function battle($ship1, $ship1Quantity, $ship2, $ship2Quantity) | |
{ | |
$ship1Health = $ship1->getStrength() * $ship1Quantity; | |
var_dump($ship1Health);die; | |
// ... lines 78 - 121 | |
} | |
// ... lines 123 - 129 |
It happens whenever you use the arrow syntax on something that isn't an object.
It's a fatal error and PHP just dies immediately. We know because I just
passed foo
, that $ship1
is no longer an object, it's just a string. And
when we call this on it, everything dies. The issue is that from the error
message, it isn't exactly clear where the mistake is. It's telling us that
the problem is on line 76 in functions.php
. And sure, that is where the
error occurred. But the real problem is in battle.php
where we are passing
in a bad value to the battle()
function.
Type-Hinting with a Class
So in addition to type-hinting with the array, when we use objects we can
type hint with the class name. Which means we can actually type Ship
here
and we can do that here as well:
// ... lines 1 - 73 | |
function battle(Ship $ship1, $ship1Quantity, Ship $ship2, $ship2Quantity) | |
// ... lines 75 - 129 |
That is the exact same thing. It says, "Hey, PHP, if something is passed
to this argument that's not a Ship
object, I want you to throw a very clear
error." So let's go see this new error! Refresh and there it is:
Argument 1 passed to battle() must be an instance of Ship, string given
on line 29 `battle.php`
This time it's very clear: it says it should have been a Ship
object, but
you're passing a string and it points us to the exact right spot. So type-hinting
is optional, but it's a really good idea because it will make your code easier
to debug later. It also has a second benefit: as soon as I type hinted this
$ship1
variable here, all of a sudden my editor knew what type of object
$ship1
was and offered me autocomplete. So it knows about getStrength()
and all the other methods on that object.
Now that we know that these are objects, let's fix this method for all the array syntaxes. Let's see here we have a few more:
// ... lines 1 - 73 | |
function battle(Ship $ship1, $ship1Quantity, Ship $ship2, $ship2Quantity) | |
{ | |
$ship1Health = $ship1->getStrength() * $ship1Quantity; | |
$ship2Health = $ship2->getStrength() * $ship2Quantity; | |
// ... lines 78 - 96 | |
$ship1Health = $ship1Health - ($ship2->getWeaponPower() * $ship2Quantity); | |
$ship2Health = $ship2Health - ($ship1->getWeaponPower() * $ship1Quantity); | |
// ... lines 99 - 120 | |
} | |
// ... lines 122 - 129 |
And then down here, which is called from above we have one more:
// ... lines 1 - 123 | |
function didJediDestroyShipUsingTheForce(array $ship) | |
{ | |
$jediHeroProbability = $ship['jedi_factor'] / 100; | |
return mt_rand(1, 100) <= ($jediHeroProbability*100); | |
} |
And notice that this one is not giving me autocomplete because it's being
type hinted as an array. This function is called all the way up here, it's
passing a $ship1
and $ship2
, so it's passing a ship object:
// ... lines 1 - 73 | |
function battle(Ship $ship1, $ship1Quantity, Ship $ship2, $ship2Quantity) | |
{ | |
// ... lines 76 - 82 | |
if (didJediDestroyShipUsingTheForce($ship1)) { | |
// ... lines 84 - 87 | |
} | |
if (didJediDestroyShipUsingTheForce($ship2)) { | |
// ... lines 90 - 93 | |
} | |
// ... lines 95 - 120 | |
} | |
// ... lines 122 - 129 |
Let's change that type hint to be a Ship
instead of an array
:
// ... lines 1 - 122 | |
function didJediDestroyShipUsingTheForce(Ship $ship) | |
{ | |
$jediHeroProbability = $ship->getJediFactor() / 100; | |
return mt_rand(1, 100) <= ($jediHeroProbability*100); | |
} |
And now we will get that nice autocompletion which will make sure the object is being passed there. Awesome, this function looks good!
Let's go back and refresh. And of course I get that same error because I forgot
to go back and put $ship1
here:
// ... lines 1 - 28 | |
$outcome = battle($ship1, $ship1Quantity, $ship2, $ship2Quantity); | |
// ... lines 30 - 97 |
Fixing the Objects inside $outcome
Let's try that again. We still get an error, but if you look closely you'll see that it is happening farther down the page. The battle function is being called and it's all working. This new error is from line 61, at this point you can probably even spot what that is:
Cannot use object of type Ship as an array
That's another syntax thing that we need to change.
So, let's go down to line 61 and sure enough there it is:
// ... lines 1 - 60 | |
<?php echo $ship1Quantity; ?> <?php echo $ship1['name']; ?><?php echo $ship1Quantity > 1 ? 's': ''; ?> | |
// ... lines 62 - 97 |
We'll call getName()
on our $ship1
and $ship2
objects:
// ... lines 1 - 60 | |
<?php echo $ship1Quantity; ?> <?php echo $ship1->getName(); ?><?php echo $ship1Quantity > 1 ? 's': ''; ?> | |
VS. | |
<?php echo $ship2Quantity; ?> <?php echo $ship2->getName(); ?><?php echo $ship2Quantity > 1 ? 's': ''; ?> | |
// ... lines 64 - 98 |
Now, real quick back up on battle()
, what it returns is this $outcome
variable, and I'm going to show you what that actually is. Down here, let's
do a var_dump()
on $outcome
, put a die
statement and refresh:
// ... lines 1 - 65 | |
<?php var_dump($outcome); ?> | |
// ... lines 67 - 98 |
So the battle()
function returns an array with three different keys on
it: winning_ship
which is a Ship
object, losing_ship
which is a Ship
object and whether or not Jedi powers were used to have a really awesome
comeback win (used_jedi_powers
).
The important part is that winning_ship
and losing_ship
are Ship
objects.
Let's just remove this var_dump
real quick. Down here, when we reference
$outcome['winning_ship']
we know that this is an object:
// ... lines 1 - 70 | |
<?php echo $outcome['winning_ship']['name']; ?> | |
// ... lines 72 - 98 |
And we want to call getName()
on it. The same thing here. And then we'll
do the same thing here as well:
// ... lines 1 - 69 | |
<?php echo $outcome['winning_ship']->getName(); ?> | |
// ... lines 71 - 78 | |
The <?php echo $outcome['winning_ship']->getName(); ?> | |
// ... lines 80 - 82 | |
overpowered and destroyed the <?php echo $outcome['losing_ship']->getName() ?>s | |
// ... lines 84 - 97 |
We're converting from that array syntax to the object syntax.
Moment of truth, do we have a working battle page? SUCCESS! Super Star Destoyer won. Let's try it again. We'll throw 10 Jedi Star Ships at our one Super Star Destroyer and it wins again. Come one Jedi's get it together! If you try enough times the Jedis do come out with a victory.
The key take away here is because we have a Ship
class, when we have a
Ship
object, we know exactly what we can do with it. This is cool because
whenever we pass around a Ship
, object we can type hint it with Ship
and our editor instantly knows what that is and what methods we can call
on it. We're giving definition to our data instead of passing around arrays
which have unknown and probably inconsistent keys.
Hey!
At 6:11 you remove the array key and replace it with the getter-function. PHPStorm not seems to recognize it as it's yellow afterwards and does not suppose autocompletion. Is there a possibility to fix that?