Buy Access to Course
10.

Type Hinting?!

Share this awesome video!

|

Keep on Learning!

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

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

99 lines | battle.php
// ... 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:

99 lines | battle.php
// ... 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:

128 lines | 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:

128 lines | functions.php
// ... 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:

129 lines | functions.php
// ... 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:

97 lines | battle.php
// ... 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:

129 lines | functions.php
// ... 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:

129 lines | functions.php
// ... 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:

129 lines | functions.php
// ... 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:

129 lines | functions.php
// ... 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:

129 lines | functions.php
// ... 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:

129 lines | functions.php
// ... 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:

97 lines | battle.php
// ... 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:

97 lines | battle.php
// ... 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:

98 lines | battle.php
// ... 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:

98 lines | battle.php
// ... 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:

98 lines | battle.php
// ... 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:

97 lines | battle.php
// ... 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.