Buy

Let's keep making our Rebel ships work a bit differently than the Empire's. In this dropdown you can see a short summary of each ship that is currently functional. It shows their name, weapon power, jedi power and strength which all comes from the getNameAndSpecs function. But I would like a way to tell which ships in this list align with the rebels, so let's add that word in parenthesis at the end.

As usual to do that, we'll override this in RebelShip. Copy the getNameAndSpecs function and paste it over here. And then just add '(rebel)' at the end:

44 lines lib/Model/RebelShip.php
... lines 1 - 2
class RebelShip extends Ship
{
... lines 5 - 22
public function getNameAndSpecs($useShortFormat = false)
{
if ($useShortFormat) {
return sprintf(
'%s: %s/%s/%s (Rebel)',
$this->name,
$this->weaponPower,
$this->jediFactor,
$this->strength
);
} else {
return sprintf(
'%s: w:%s, j:%s, s:%s (Rebel)',
$this->name,
$this->weaponPower,
$this->jediFactor,
$this->strength
);
}
}
}

Now you may be thinking "guys, that's some serious code duplication...". Well you're absolutely right, and we'll get to fixing that!

For now what we've got is pretty straightforward, so let's refresh and... oh, check out our dropdown. We've got an Undefinded property RebelShip::$name error.

You can't access private things in sub-classes

Back in PhpStorm, you can see $this->name is highlighted with an error message of 'Member has private access'. Interesting. So far, I've told you that since RebelShip extends Ship it has access to everything inside of it like the properties and methods as if they also exist inside of RebelShip. However, this error really does seem to be saying something different than that.

We can see that in Ship there is a name property so why isn't this working? The answer has to do with this word private in front of $name:

140 lines lib/Model/Ship.php
... lines 1 - 2
class Ship
{
... lines 5 - 6
private $name;
... lines 8 - 138
}

All functions and properties so far are either private or public. If a function or a property is private it means you can only access it from within the ship class:

140 lines lib/Model/Ship.php
... lines 1 - 2
class Ship
{
... lines 5 - 16
public function __construct($name)
{
$this->name = $name;
... lines 20 - 21
}
... lines 23 - 138
}

Like here where we say $this->name. As we can see here, private functions and properties can't be accessed inside of subclasses. So only things inside of the Ship class can access this private $name; property.

I always recommend that you make things private until you need to access them from outside of the class you're working in.

Introducing: protected

Now, there is another designation between private and public which is called protected. Protected works exactly like private except that subclasses can access it, so when we change it here the error goes away:

140 lines lib/Model/Ship.php
... lines 1 - 2
class Ship
{
... lines 5 - 6
protected $name;
... lines 8 - 138
}

Cool! Let's do a temporary fix for the error we're getting by making all of these things protected:

140 lines lib/Model/Ship.php
... lines 1 - 2
class Ship
{
... lines 5 - 6
protected $name;
protected $weaponPower = 0;
protected $jediFactor = 0;
protected $strength = 0;
... lines 14 - 138
}

Everything in our RebelShip file looks happy again so let's refresh. Ah ha! Our dropdown is back in business and showing the rebel designation.

I just mentioned that our fix was 'temporary' because I don't actually want to make these protected I really prefer to keep things private whenever possible. So even though these properties are private we have public functions that access them like getName, getStrength, getWeaponPower:

140 lines lib/Model/Ship.php
... lines 1 - 2
class Ship
{
... lines 5 - 6
private $name;
private $weaponPower = 0;
private $jediFactor = 0;
private $strength = 0;
... lines 15 - 33
public function getName()
{
return $this->name;
}
... lines 39 - 47
public function getStrength()
{
return $this->strength;
}
... lines 53 - 81
public function getWeaponPower()
{
return $this->weaponPower;
}
... lines 87 - 89
public function getJediFactor()
{
return $this->jediFactor;
}
... lines 95 - 138
}

Which means that in the subclass we can just use these instead of the properties. Let's go ahead and just change those in RebelShip. And to save me some effort I'll copy and paste these from the if to the else:

44 lines lib/Model/RebelShip.php
... lines 1 - 2
class RebelShip extends Ship
{
... lines 5 - 22
public function getNameAndSpecs($useShortFormat = false)
{
if ($useShortFormat) {
return sprintf(
'%s: %s/%s/%s (Rebel)',
$this->getName(),
$this->getWeaponPower(),
$this->getJediFactor(),
$this->getStrength()
);
} else {
return sprintf(
'%s: w:%s, j:%s, s:%s (Rebel)',
$this->getName(),
$this->getWeaponPower(),
$this->getJediFactor(),
$this->getStrength()
);
}
}
}

I like this, I mean I already have these public functions so why not use them? It allows me to keep these properties private which is looking ahead a little bit, but the more things you have marked as private the easier it's going to be to maintain and update your code later.

Back to the browser and refresh, and things still work!

private and protected Methods

Let's temporarily add a new private function to Ship called getSecretDoorCodeToTheDeathstar(). Since only Empire ships should have access to this you can see why setting it as private makes sense. And let's return the secret code 'Ra1nb0ws':

145 lines lib/Model/Ship.php
... lines 1 - 2
class Ship
{
... lines 5 - 6
private $name;
private $weaponPower = 0;
private $jediFactor = 0;
... lines 13 - 139
private function getSecretDoorCodeToTheDeathstar()
{
return 'Ra1nb0ws';
}
}

Over in RebelShip I should not be able to access this new function since we set it to private:

46 lines lib/Model/RebelShip.php
... lines 1 - 2
class RebelShip extends Ship
{
... lines 5 - 22
public function getNameAndSpecs($useShortFormat = false)
{
return $this->getSecretDoorCodeToTheDeathstar();
... lines 26 - 43
}
}

We see the 'Member has private access' error so when we refresh we can check the dropdown to confirm that things aren't working. Fatal error: Call to private method Ship::getSecretDoorCodeToTheDeathstar() and we need to view the source to see the full error message.

But, if we go back and change that function to protected, our error is gone, the rebels have access to the secret door code and life is good:

145 lines lib/Model/Ship.php
... lines 1 - 2
class Ship
{
... lines 5 - 139
protected function getSecretDoorCodeToTheDeathstar()
{
return 'Ra1nb0ws';
}
}

Remove all that nonsense. The moral of the story is this, make things private at first, proctected once you need to access them in a subclass. And finally public when you need to use it outside of its class and subclass.

Leave a comment!

  • 2015-10-12 weaverryan

    Hey Syed!

    You're 100% right - nice catch! I've fixed it (https://github.com/knpuniversi... and am deploying the change right now. Thanks for asking if this was wrong so we could fix it :).

    Cheers!

  • 2015-10-11 Syed

    In the code challenge #2 shouldn't the JediShip class extend the Ship class? Currently JediShip isn't a sub class of Ship and therefore making both (A) and (C) correct answers.