Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Private Access

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.

Start your All-Access Pass
Buy just this tutorial for $6.00

Let me show you a problem with our app, there's nothing stopping me from going in and setting the strength to something like Jar Jar Binks:

107 lines functions.php
... lines 1 - 4
function get_ships()
{
... lines 7 - 8
$ship1 = new Ship();
... lines 10 - 12
$ship1->strength = 'Jar Jar Binks';
... lines 14 - 45
}
... lines 47 - 107

Clearly this value makes absolutely no sense at all for many reasons.

Sure enough, when we refresh Jar Jar Binks prints out as the strength in the select menu. The Ship class lets us give this really bad data. If we tried to battle, this would probably break our app since you can't compare a strength of 10 to Jar Jar Binks mathematically. But if you disagree, I would love to see your math in the comments.

To fix this, I'll get to show you another strength of classes. So far everything has been public:

50 lines lib/Ship.php
... lines 1 - 2
class Ship
{
public $name;
public $weaponPower = 0;
public $jediFactor = 0;
public $strength = 0;
... lines 12 - 48
}

Public name, weapon power and so on but I haven't told you what that means.

Making a Property private

There's actually three different words that can go here: public, private and protected, but we'll only worry about the first two for now. As soon as you make a property private it can't be accessed from outside of the class. I'll show you what I mean:

50 lines lib/Ship.php
... lines 1 - 2
class Ship
{
... lines 5 - 10
private $strength = 0;
... lines 12 - 48
}

Now that it's marked as private my editor is highlighting strength saying, "No no no, you can't access strength anymore." So from outside of the class it's illegal to access a private property.

And sure enough, when I refresh it says, "Fatal error: Cannot access private property".

Adding a Setter Method

This is called a visibility modifier. Once you make something private if you want someone from the outside to be able to interact with that property you'll need to add public functions to be able to do that. In this case down here, we can create what's called a setter: public function setStrength($strength) it will take an argument called $strength, which will be a number:

60 lines lib/Ship.php
... lines 1 - 2
class Ship
{
... lines 5 - 22
public function setStrength($number)
{
$this->strength = $number;
}
... lines 27 - 58
}

And then we'll set it on that property. You see that this does not highlight red, so a private property can still be accessed from within a class using the magic $this keyword. It just can't be accessed outside of the class.

Here, instead of accessing the strength property directly, we can access the setStrength method:

107 lines functions.php
... lines 1 - 4
function get_ships()
{
... lines 7 - 8
$ship1 = new Ship();
... lines 10 - 12
$ship1->setStrength('Jar Jar Binks');
... lines 14 - 45
}
... lines 47 - 107

When we refresh, it gets further!

Adding a Getter Method

It gets past that setter and now we're down to line 71. We're still accessing the strength property:

106 lines index.php
... lines 1 - 65
<?php foreach ($ships as $ship): ?>
... lines 67 - 70
<td><?php echo $ship->strength; ?></td>
... line 72
<?php endforeach; ?>
... lines 74 - 106

So let's fix that right here. Since we can't reference that anymore we need to go in and make a public function getStrength() and it will go grab the value from that private property and return it to us:

60 lines lib/Ship.php
... lines 1 - 2
class Ship
{
... lines 5 - 27
public function getStrength()
{
return $this->strength;
}
... lines 32 - 58
}

In index we can say getStrength() and that should take care of the problem:

106 lines index.php
... lines 1 - 65
<?php foreach ($ships as $ship): ?>
... lines 67 - 70
<td><?php echo $ship->getStrength(); ?></td>
... line 72
<?php endforeach; ?>
... lines 74 - 106

Head back and refresh and it works! Alright!

Avoiding Jar Jar Binks

The reason we did this, is that when you have a public property there's no way to control who sets it from the outside. Anyone could have set the strength and they could have set it to any crazy string, negative number or bad Star Wars character, none of which make any sense. As soon as you make it private, it means that outsiders are going to have to call public methods, and this gives us a cool opportunity to run a check inside of here to say, "Hey! Is the strength a number? If not, let's throw an error."

In setStrength() we'll put in an if statement with the is_numeric() function, and if it's not numeric, then we're going to throw a new Exception() with a helpful message:

64 lines lib/Ship.php
... lines 1 - 22
public function setStrength($number)
{
if (!is_numeric($number)) {
throw new \Exception('Strength must be a number, duh!');
}
$this->strength = $number;
}
... lines 31 - 64

In case you aren't familiar with exceptions, they're a special internal object to php. It stops the flow and shows an error.

Now when we refresh we get this nice helpful error. This message is for us the developer. Instead of the application running and tripping up later when we accidentaly put in a bad strength, we are notified immediately.

It even tells us that the error happened on Ship.php line 52 and we called the method on functions.php line 13. So let's go back into functions.php line 13 and of course there it is:

107 lines functions.php
... lines 1 - 8
$ship1 = new Ship();
... lines 10 - 12
$ship1->setStrength('Jar Jar Binks');
... lines 14 - 107

We'll change that back to 30 and when we refresh life is good again:

107 lines functions.php
... lines 1 - 8
$ship1 = new Ship();
... lines 10 - 12
$ship1->setStrength(30);
... lines 14 - 107

Make all the Things Private!

This idea of making your properties private and then adding getters and setters is really common. Even if you don't need the control like this now you might in the future. If you're already forcing outsiders to call your setter methods and you realize later that you need to do some sort of check you have the opportunity to do that by modifying your method.

A really common thing to do is to always make your properties private. So I'll update jediFactor, weaponPower and name:

103 lines lib/Ship.php
... lines 1 - 2
class Ship
{
private $name;
private $weaponPower = 0;
private $jediFactor = 0;
private $strength = 0;
... lines 12 - 101
}

The downside of this is that we'll need a getName(), setName(), getWeaponPower(), setWeaponPower() getJediFactor(), and a setJediFactor().

That can be a lot of work and PHP doesn't give us a way to get around this, so we do need to write those. A lot of editors allow you to generate these, which is nice. In PHPStorm, go to code generate and then pick "Getters and Setters" and select the weaponPower and jediFactor fields. Name isn't in this list because we already have a getName(). I'll go back to code generate again and pick just setter this time and it recognizes that the name doesn't have a setter:

103 lines lib/Ship.php
... lines 1 - 2
class Ship
{
... lines 5 - 65
public function getWeaponPower()
{
return $this->weaponPower;
}
... lines 70 - 73
public function getJediFactor()
{
return $this->jediFactor;
}
... lines 78 - 81
public function setName($name)
{
$this->name = $name;
}
... lines 86 - 89
public function setWeaponPower($weaponPower)
{
$this->weaponPower = $weaponPower;
}
... lines 94 - 97
public function setJediFactor($jediFactor)
{
$this->jediFactor = $jediFactor;
}
}

Now we have getters and setters on all of these properties. And by the way the name of this doesn't matter, we could get creative and call this setWeaponPowerFooBar(), but in your project try to be clear and concise.

Now that we've made everything private and we have these getters and setters, we need to use those everywhere instead of accessing the properties directly. Let's change this to setName(), this to setWeaponPower(), and setJediFactor():

107 lines functions.php
... lines 1 - 8
$ship1 = new Ship();
$ship1->setName('Jedi Starfighter');
$ship1->setWeaponPower(5);
$ship1->setJediFactor(15);
$ship1->setStrength(30);
... lines 14 - 107

Maybe this feels like extra work right now, but if we had made it private in the beginning then we wouldn't have to go back and change them. Which is what I recommend that you do.

In index.php we have the same thing, we need to call getWeaponPower() and getJediFactor():

106 lines index.php
... lines 1 - 67
<td><?php echo $ship->getName(); ?></td>
<td><?php echo $ship->getWeaponPower(); ?></td>
<td><?php echo $ship->getJediFactor(); ?></td>
<td><?php echo $ship->getStrength(); ?></td>
... lines 72 - 106

We're already calling getStrength and down here we're calling the public function getNameAndSpecs.

106 lines index.php
... lines 1 - 84
<option value="<?php echo $key; ?>"><?php echo $ship->getNameAndSpecs(); ?></option>
... lines 86 - 106

So let's try that out and see if we missed anything. Refresh and everything looks really good and even the select menu shows up perfect. We're all set!

We now have all these wonderful hooks so that if anyone ever needs to get the weaponPoweror set the jediFactor, we can do something before returning it. For example, in getName() you can actually use a strtoupper so whenever someone calls this we'll return the uppercase version:

public function getName()
{
    return strtoupper($this->name);
}

As cool as that is, I'll just undo it for now.

Creating all the Ship Objects

With all these private properties, getters and setters our Ship class is looking fit for action.

Back in functions.php we used to have these 3 other ships. Let's make object representations of those. We'll say $ship2 = new Ship() and then we just need to set the name, weaponPower jediFactor and strength for those other three ships. I'm going to save us a little bit of time and paste this in:

99 lines battle.php
... lines 1 - 25
$ship1 = $ships[$ship1Name];
$ship2 = $ships[$ship2Name];
var_dump($ship1, $ship2);die;
... lines 30 - 99

And there we go, $ship2, 3 and 4 have their data set on the array. What we're returning from here is an array of Ship objects.

When we go back and refresh everything looks perfect. And this is starting to look pretty good.

Next, we need to fix up battle.php. If we try to start a battle, we can see that it's super broken. And that makes sense, since we moved from arrays to objects and we haven't updated that page yet. But we've learned a ton so far so I'm sure this will be easy.

Leave a comment!

5
Login or Register to join the conversation
Default user avatar
Default user avatar Oliver Davies | posted 5 years ago

There are __get() and _set() magic methods that can be used rather than creating separate ones.

Reply

Comparing numbers to "Jar Jar Binks" is easy!
We all know that "Jar Jar Binks" is a zero,
so any positive integer is bigger than "Jar Jar Binks".

Reply

Haha, you took our bad joke to a new low... I love it! <3

2 Reply
Default user avatar

Can you share your IDE and Browser settings? Error handling renders differently, ie without tables etc. for me. Otherwise I love your work!

Reply

Yo Sajjad!

IDE is PHPStorm, and I *love* it - I highly recommend.

For error handling, it's not the browser exactly - it's likely a PHP extension called Xdebug. If this is installed and enabled in your PHP (you can do a phpinfo() to find out), then literally when you var_dump();, it renders with HTML markup that makes things look awesome. Anyways, try this out and let me know if it's what you're looking for :). Additionally, Symfony has a library called VarDumper which gives you a dump() function in PHP, which dumps even *more* beautifully: http://symfony.com/doc/curr...

Cheers!

Reply
Cat in space

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

userVoice