Lesson Weekend

Lesson goals:

  • Learn about encapsulation with getters and setters
  • Learn about using the keywords public and private to control object visibility

Let's say you're 6 months into your career as the owner of this CD store and surprisingly, no one is buying the Pork Lion CD (you were so sure of it!). You decide to throw in a quick line of code to set its price to $1. But, it's been 6 months. What if you accidentally wrote it like this?

Cd.php
$fourth_cd->price = "$1.00";

You forgot that the price property was supposed to be a number! Instead you gave it a string so that it would display properly, or so you think. Now it will display with two dollar signs if you reload the page.

This is not the end of the world, but it is an example of a kind of error that can be very difficult to track down in larger apps. The accepted solution to this is to use special methods frequently referred to as getters and setters. Their job is to either get or set the values of an object's properties, while including some error checking to make sure it is done correctly. Let's add one for the $price property. Put this inside of your class declaration.

Cd.php
function setPrice($new_price)
{
    $this->price = (float) $new_price;
}

This method takes one argument, which is the new value to set the $price property to. By writing the keyword float in parentheses before it, we are telling the PHP interpreter to try to convert the value into a float (a float is a data type which means it is a number with a decimal point).

Next, instead of directly setting $fourth_cd->price to a value, we are going to call the setPrice method on $fourth_cd, and use the desired value as our argument.

Cd.php
$fourth_cd->setPrice("$1.00");

If we reload the browser, we see that now the Pork Lion CD costs $0. This is because when the PHP interpreter tried to convert the string $1.00 into a float, it had no idea what to do with the dollar sign because it's not a numerical digit. So instead, the PHP interpreter basically shrugged and gave you a 0. But, we can fix this by adding an if statement to our method.

Cd.php
function setPrice($new_price)
{
    $float_price = (float) $new_price;
    if ($float_price != 0) {
        $this->price = $float_price;
    }
}

Now our method takes $new_price and tries to convert it to a float, and assigns the result to the variable $float_price. If the conversion fails due to an invalid input, like a dollar sign, then 0 is assigned to $float_price. So in our if statement, we say "if the float price is not equal to 0, assign it to the object's $price property." If we reload the browser again, we can see that the Pork Lion CD is back to $49.99 (which is better than 0! You don't want to be giving things away).

Now, say we had a shipping calculator in another part of our program, and it says that the price of shipping a $1.00 Pork Lion CD is 39.25 cents, making the total $1.3925. But, say there's a mistake in the shipping calculator and it also outputs strings instead of numbers. So it says the total price for the CD is "1.3925" instead of 1.3925. Let's put this argument into our method and see what happens when we reload the browser.

Cd.php
$fourth_cd->setPrice("1.3925");

When we reload the browser, we can see that it did successfully convert the string into a float. But this could be even better. A quick google search for "display float with 2 decimal places php" takes us to the PHP manual page at (php.net)[http://php.net/number_format] for a function called number_format . This built-in function does just what we need: it takes two arguments: the first one is the float to modify, and the second one is the number of decimal places that you want to show. Since we want to display money, we will only want 2 decimal places. Let's add this to our setPrice method:

Cd.php
function setPrice($new_price)
{
    $float_price = (float) $new_price;
    if ($float_price != 0) {
        $formatted_price = number_format($float_price, 2);
        $this->price = $formatted_price;
    }
}

Now, if the conversion is successful, we set it to have only 2 decimal points before setting the property. If we reload the browser, it will show $1.39 as we intended.

There's still one way we could mess this up. We could still directly set the object's property to anything, by running something like $fourth_cd->price = "zebra", and we might not have any way to know about it until someone else saw it on the website. In a large project this could be a problem.

So now we are going to fix this by finally learning what the word public means - by replacing it with the word private. Change the public keyword next for the price property to private. Your class should now look like this:

Cd.php
class CD
{
    public $title;
    public $artist;
    public $cover_art;
    private $price;

    function __construct($album_name, $band_name, $image_path, $album_price = 10.99)
    {
        $this->title = $album_name;
        $this->artist = $band_name;
        $this->cover_art = $image_path;
        $this->price = $album_price;
    }

    function setPrice($new_price)
    {
        $float_price = (float) $new_price;
        if ($float_price != 0) {
            $formatted_price = number_format($float_price, 2);
            $this->price = $formatted_price;
        }
    }
}

Now, replace the line of code calling the setPrice method on $fourth_cd with this line.

Cd.php
$fourth_cd->price = "zebra";

If we reload the browser, we now get this scary looking error if we try to set the property like that.

Fatal error: Cannot access private property CD::$price in /Users/dianedouglas/Desktop/cd_store.php on line 34

This forces us to use the safer setPrice method. When a property is set to private, it is inaccessible for reading or writing from anywhere in the code, except for within the object.

When a property is private, we also must write a method to get its value. Conventionally, as we named the setter method setPrice, the other method should be called getPrice. They are always named setPropertyName and getPropertyName. This one will be much simpler, since all it has to do is read a property - no need for error checking. Add this to your class declaration.

Cd.php
function getPrice()
{
    return $this->price;
}

Now, we just use this method in our loop used to display the HTML.

Cd.php
foreach ($cds as $album) {
    $cd_price = $album->getPrice();
    echo "<div class='row'>
        <div class='col-md-6'>
            <img src='$album->cover_art'>
        </div>
        <div class='col-md-6'>
            <p>$album->title</p>
            <p>By $album->artist</p>
            <p>$$cd_price</p>
        </div>
    </div>
    ";
}

And don't forget to replace $fourth_cd->price = 1.00; with this line $fourth_cd->setPrice("1.3925"); again.

Now if we reload the browser, our store will work again!

We call getPrice on the current $album at the top of the loop and store the value in a new variable called $cd_price. Then we display it using echo. We do the method call at the top before we use echo because you can't run any kind of function from inside of a string. You can print variables, but nothing executable.

It is usually a good idea to use getters and setters with private properties in your objects, so start getting in the habit of it early. This state of being public or private is known as a property's visibility. The practice of removing the ability to manipulate properties from outside an instance of our class is called encapsulation, and it is a big part of object oriented programming.

These last few terms refer to complex concepts which require extensive definitions. If you're interested in more information, feel free to challenge yourself by looking at these outside resources:

Getters and setters are methods which either get or set the values of an object's properties, while including some error checking to make sure it is done correctly.

class CD
{
    private $price;

    function setPrice($new_price)
    {
        $float_price = (float) $new_price;
        if ($float_price != 0) {
            $formatted_price = number_format($float_price, 2);
            $this->price = $formatted_price;
        }
    }

    function getPrice()
    {
        return $this->price;
    }
}

When a property's visibility is set to private instead of public, it is inaccessible for reading or writing from anywhere in the code, except for within the object.

encapsulation: Keeping all the data manipulation localized inside of the object.

object oriented programming: A programming style centered around the idea of organizing your code into objects.

These terms refer to complex concepts that require extensive definitions. If you're interested in more information, feel free to challenge yourself by looking at these outside resources: