Chapter 5
IN THIS CHAPTER
Defining object-oriented programming
Creating objects
Using objects
Customizing objects
So far, all the PHP scripts presented in this minibook have followed the procedural style of programming. With procedural programming, you create variables and functions within your code to perform certain procedures, such as storing values in variables, and then checking them with conditional statements. The data you use and the functions you create are completely separate entities, with no specific relationship to one another. With object-oriented programming, on the other hand, variables and functions are grouped into common objects that you can use in any program. In this chapter, you learn what object-oriented programming is and how to use it in your web applications.
Before you can start working on object-oriented programming (OOP), you need to know how it works. OOP uses a completely different paradigm from coding than what I cover earlier in this minibook. OOP requires that you think differently about how your programs work and how you code them.
With OOP, everything is related to objects. (I guess that’s why they call it object-oriented programming!) Objects are the data you use in your applications, grouped together into a single entity.
For example, if you’re writing a program that uses cars, you can create a Car
object that contains information on the car's weight, size, color, engine, and number of doors. If you’re writing a program that tracks people, you might create a person
object that contains information about each person’s name, date of birth, height, weight, and gender.
OOP uses classes to define objects. A class is the written definition in the program code that contains all the characteristics of the object, using variables and functions. The benefit of OOP is that after you create a class for an object, you can use that same class in any other application. Just plug in the class definition code and put it to use!
An OOP class contains members. There are two types of members:
Defining a class in PHP isn’t too different from defining a function. To define a new class, you use the class
keyword, along with the name of the class, followed by any statements contained in the class.
Here's an example of a simple class definition:
class Product {
public $description;
public $price;
public $inventory;
public $onsale;
public function buyProduct($amount) {
$this->inventory -= $amount;
}
}
This example defines four property members and one method member. Each member is defined using one of three visibility classifications. The visibility of the member determines where you can use or reference that member. There are three visibility keywords used in PHP:
public
: The member can be accessed from outside the class code.private
: The member can only be accessed from inside the class code.protected
: The member can only be accessed from a child class. (I talk about that a little later in the “Extending Classes” section.)The Product
class example declares all the members to be public, so you can reference them anywhere in your PHP code.
The buyProduct()
method uses an odd variable name in the function:
$this->inventory
The $this
variable is a special identifier that references the current object of the class. In this example, it points to the $inventory
property of the object. Notice the removal of the dollar sign from the inventory
property when referencing it this way. This helps PHP know that you're referencing the $inventory
property from within the class object and not the class itself.
This code defines the makeup of the class, but it doesn’t actually do anything with it. The next section shows you how to actually use your class template to create objects.
To use a class, you have to instantiate it. When you instantiate a class, you create what’s called an instance of the class in your program. Each instance represents one occurrence of the object within the program. To instantiate an object in PHP code, you use the following format:
$prod1 = new Product();
This creates the object called $prod1
using the Product
class. When you instantiate an object, you can access the public members of that class directly from your program code:
$prod1->description = "carrot";
$prod1->price = 1.50;
$prod1->inventory = 10;
$prod1->onsale = false;
This code sets values for each of the properties for the object. Notice the ->
symbol in use again. It tells PHP that you're referencing the properties and methods specifically for the $prod1
object.
The $prod1
variable now contains these values set for the object properties, and you can use it anywhere in your PHP code to reference the properties. The same applies when you need to use a public method of an object:
$prod1->buyProduct(4);
This calls the buyProduct()
method for the class object, passing the value of 4
. Because the buyProduct()
method alters the $inventory
property of the object, the next time you reference the $prod1->inventory
property in your code, it'll have the value of 6.
You can instantiate as many instances of a class as you need within your program. Just make sure that each instance uses a different variable name:
$prod2 = new Product();
$prod2->description = "eggplant";
$prod2->price = 2.00;
$prod2->inventory = 5;
$prod2->onsale = true;
PHP will keep the two instances of the Product
class completely separate, maintaining the property values for each one.
Follow these steps to test out creating and using classes in PHP:
<!DOCTYPE html>
<html>
<head>
<title>PHP OOP Test</title>
</head>
<body>
<h1>Testing PHP OOP code</h1>
<?php
class Product {
public $description;
public $price;
public $inventory;
public $onsale;
public function buyProduct($amount) {
$this->inventory -= $amount;
}
}
$prod1 = new Product();
$prod1->description = "Carrots";
$prod1->price = 1.50;
$prod1->inventory = 10;
$prod1->onsale = false;
echo "<p>Just added $prod1->description<p>\n";
$prod2 = new Product();
$prod2->description = "Eggplants";
$prod2->price = 2.00;
$prod2->inventory = 5;
$prod2->onsale = true;
echo "<p>Just added $prod2->description<p>\n";
echo "<p>Now buying 4 carrots…<p>\n";
$prod1->buyProduct(4);
echo "<p>Inventory of $prod1->description is now
$prod1->inventory</p>\n";
echo "<p>Inventory of $prod2->description is still
$prod2->inventory</p>\n";
?>
</body>
</html>
Save the file as ooptest1.php
in the DocumentRoot
folder for your web server.
For XAMPP on Windows, that's c:\xampp\htdocs
; for XAMPP on macOS, that’s /Applications/XAMPP/htdocs
.
http://localhost:8080/ooptest1.php
You may need to change the TCP port to match your web server.
When you run the ooptest1.php
file, you should see the output shown in Figure 5-1.
FIGURE 5-1: The output from the ooptest1.php
program.
The example code defines the Product
class, which contains the four properties and one method that has already been discussed. After the Product
class definition, the code creates two instances of the Product
class: $prod1
and $prod2
. When using classes, you need to define the class first in the code before you create an instance of it.
After creating the two instances, the code uses the buyProduct()
method for the $prod1
instance to reduce the inventory by 4. Then it uses two echo
statements to display the inventory properties for the two instances. Notice that the buyProduct()
method reduced the inventory of the $prod1
instance, but not the $prod2
instance, showing that the two instances are, indeed, separate objects in the program.
No, you won't be learning any new tricks involving smoke and mirrors. Magic class methods are built-in method names in PHP that apply to all class objects. You can redefine them in your code to provide additional functionality to your PHP classes. This process is called overloading or overriding. In overloading, you define a method in your class code with the same name as an existing method. PHP uses the newly defined method when you call it from your program code in the class object.
Magic class methods are most often used to help provide common functionality for classes, such as creating a new class object, copying an existing class object, or displaying class objects as text. The PHP developers identify magic class methods by using a double underscore at the start of the method name.
The following sections walk through how to use some of the more common magic class methods in your own classes.
Mutator magic methods are methods that change the value of a property that you set with the private
visibility. These are also commonly called setters.
The class example in the previous section used the public
visibility feature for the class properties, but that's not always a good thing to do. That means that any application can directly access the properties and change them to whatever values it wants. That could be dangerous, and it’s somewhat frowned upon in OOP circles.
The preferred way to handle class properties is to make them private so external programs can’t change them directly. Instead, to manipulate the data, external programs are forced to use mutator magic class methods that interface with the properties.
The mutator magic method in PHP is __set()
(note the leading double underscores). You use the mutator magic method to set all the values of the properties in the class with a single method definition:
public function __set($name, $value) {
$this->$name = $value;
}
The mutator uses two parameters: the name of the property to set and the value to assign to the property. Where the magic comes into play is with how PHP uses the mutator. In your PHP application code, you don’t actually have to call the __set()
mutator method. You can define the $description
property just by using a simple assignment statement:
$prod1->description = "Carrots";
PHP automatically knows to look for the __set()
mutator method defined for the class and runs it, passing the appropriate property name and value.
Even though the $description
property is set to the private
visibility, by defining the mutator magic method you can allow external programs to assign a value to the property. The benefit of using mutators, though, is that you can control how external programs use the properties you define for the class.
With the mutator definition, you can place any code you need to control property features, such as ranges of values allowed or the allowed settings applied to the property. For example, you could so something like this:
public function __set($name,$value) {
if ($name == "price" && $value < 0) {
$this->price = 0;
} else {
$this->$name = $value;
}
}
This example checks if the property being set is the $price
property. If it is, it checks if the value is less than 0. If the value is less than 0, the price is set to 0 instead of the supplied price value. This gives you a way to control the value that is set for the price from external programs that use the class object.
Accessor magic methods are methods you use to access the private property values you define in the class. Creating special methods to retrieve the current property values helps create a standard for how other programs use your class objects. These methods are often called getters because they retrieve (get) the value of the property.
You define the accessor using the special __get()
method:
public function __get($name) {
return $this->$name;
}
That's all there is to it! Accessor methods aren’t overly complicated; they just return the current value of the property. To use them you just reference the property name as normal:
echo "<p>Product: $prod1->description</p>\n";
PHP automatically looks for the accessor method to retrieve the property value. Follow these steps to try creating and using a class definition with mutators and accessors:
<!DOCTYPE html>
<html>
<head>
<title>PHP OOP Test</title>
</head>
<body>
<h1>Testing PHP OOP setters and getters</h1>
<?php
class Product {
private $description;
private $price;
private $inventory;
private $onsale;
public function __set($name, $value) {
if ($name == "price" && $value < 0) {
echo "<p>Invalid price set<p>\n";
$this->price = 0;
} elseif ($name == "inventory" && $value < 0) {
echo "<p>Invalid inventory set: $value</p>\n";
} else {
$this->$name = $value;
}
}
public function __get($name) {
return $this->$name;
}
public function buyProduct($amount) {
if ($this->inventory >= $amount) {
$this->inventory -= $amount;
} else {
echo "<p>Sorry, invalid inventory requested:
$amount</p>\n";
echo "<p>There are only $this->inventory
left</p>\n";
}
}
}
$prod1 = new Product();
$prod1->description = "Carrots";
$prod1->price = 1.50;
$prod1->inventory = 5;
$prod1->onsale = false;
echo "<p>Just added $prod1->inventory $prod1->description</p>\n";
echo "<p>Now buying 4 carrots…<p>\n";
$prod1->buyProduct(4);
echo "<p>Inventory of $prod1->description is now $prod1->inventory</p>\n";
echo "<p>Trying to set carrot inventory to -1:</p>\n";
$prod1->inventory = -1;
echo "<p>Now trying to buy 10 carrots…</p>\n";
$prod1->buyProduct(10);
echo "<p>Inventory of $prod1->description is now $prod1->inventory</p>\n";
?>
</body>
</html>
ooptest2.php
in the DocumentRoot
folder for your web server.http://localhost:8080/ooptest2.php
Figure 5-2 shows the output that you should see when you run the program in your browser.
FIGURE 5-2: The output from the ooptest2.php
program.
There’s a lot going on in this example, so hang in there with me! First, the PHP code defines the Product
class, using the four properties, but this time it defines them with private visibility. Following that, the mutator and accessor magic methods are defined. The mutator checks to ensure the price and inventory properties can't be set to a negative value.
After the class definition, the code creates an instance of the Product
class, and experiments with the inventory values. First, it uses the buyProduct()
method to purchase four carrots. That works just fine.
Next, it uses the mutator to set the inventory
property for the carrot object to a negative value. The mutator code intercepts that request and prevents the inventory from being set, instead producing an error message.
Finally, the code tries to use the buyProduct()
method to purchase more carrots than what's set in inventory. The added code in the buyProduct()
method prevents that from happening.
Now the class definition is starting to do some useful functions for the application. But wait, there are more magic methods available for you to use!
Having to set property values using the mutator methods each time you instantiate a new object can get old, especially if you have lots of properties in the class. The constructor magic class method makes that job a lot easier.
The constructor magic method allows you to define values for the properties when you create the new object instance. You can define as many or as few of the properties as you like within the class constructor definition. You do that with the __construct()
magic method:
public function __construct($name, $cost, $quantity) {
$this->description = $name;
if ($price > 0) {
$this->price = $cost;
} else {
$this->price = 0;
}
$this->inventory = $quantity;
$this->onsale = false;
}
This constructor for the Product
class uses three parameters to assign values to three of the class properties when you instantiate the class. It also automatically sets the $onsale
property to a false
value for each new class instance. To use the constructor, you just provide the three property values as parameters to the class:
$prod1 = new Product("Carrot", 1.50, 10);
Handling memory management in PHP programs is normally a lot easier than with some other programming languages. By default, PHP recognizes when a class instance is no longer in use and automatically removes it from memory. However, sometimes a program might need to do some type of “cleanup” work for the class object before PHP removes it from memory.
You can specify a magic class method that PHP automatically attempts to run just before it removes the instance from memory. These methods are called destructors.
Destructors come in handy with a class that works with files or databases to ensure that the files or database connections are properly closed before the system removes the class instance. This helps prevent any corruption in the data from an improperly closed session being stopped.
You use the __destruct()
magic class method to define any final statements to process before PHP removes the class instance from memory:
public function __destruct() {
statements
}
The __destruct()
method doesn't allow you to pass any parameters into the method. All the statements you specify in the method need to be self-contained and must not rely on any data from the main program. They can, however, rely on properties within the class, because those should be available when the class object is removed.
You can also manually remove an instance of an object from memory using the unset()
function:
unset($prod1);
When you run this command, PHP will process the destructor for the Product
class for the instance.
You can copy objects within PHP, but not using the standard assignment statement. Instead you need to use the clone
keyword:
$prod1 = new Product("Carrot", 1.50, 100);
$prod2 = clone $prod1;
Now the $prod2
variable contains a second object instance of the Product
class, with the same property values as the $prod1
instance.
You may however have a situation where you don't want the copy of the object to have all the same property values as the original. To do that, you can override the __clone()
magic method in your class code:
public function __clone() {
$this->price = 0;
$this->inventory = 0;
$this->onsale = false;
}
With this code, when you clone an object only the description
property will copy over; all the other property values will be reset.
Most likely, at some point in your application, you'll want to display the properties of your objects in the web page. However, if you try to use the echo
statement to display the object instance, you’ll get a somewhat ugly error message from PHP:
Recoverable fatal error: Object of class Product could not be converted to string
You can solve that problem by defining the __toString()
magic class method in the class definition.
The __toString()
magic method defines how you want PHP to handle the properties when you try to use the object as a string value, such as in the echo
statement. You just build the string value from the properties and store the output in a variable. Then use the return
statement to return the output variable back to the main program. That code looks like this:
public function __toString() {
$output = "<p>Product: " . $this->description . "<br>\n";
$output .= "Price: $" . number_format($this->price,2) . "<br>\n";
$output .= "Inventory: " . $this->inventory . "<br>\n";
$output .= "On sale: ";
if ($this->onsale) {
$output .= "Yes</p>\n";
} else {
$output .= "No</p>\n";
}
return $output;
}
With the __toString()
magic method defined, you can now use an instance of the Product
class in an echo
statement just like any variable:
echo $prod1;
And you'll get the following output in your web page:
Product: Carrots
Price: 1.50
Inventory: 10
On sale: No
With the __tostring()
magic method, displaying your class objects in the web page is as easy as any other type of variable value!
At the beginning of this chapter, I mention that OOP helps make it easy to reuse program code in multiple applications. After you create the Product
class for one application, you can use the same code to use the Product
class in any other application that uses products.
However, having to retype the entire Product
class code definition in each application that uses it can be somewhat tedious, especially for complicated classes. To solve that problem you can use our friend the include()
function.
Just save your class definitions in separate PHP code files; then use the include()
function to include the files in any code that uses the class definitions. This enables you to include only the files for the classes the application uses, without having to retype the entire class code definition! That's good, but there may still be a downside to that.
Complex applications may use dozens or possibly even hundreds of separate class objects to manage and manipulate data in the application. Having to list each of the class include files can still be somewhat tedious, as well as be prone to typing mistakes that will cause errors. To solve that problem, the PHP developers created the autoload feature, which determines when a class is being instantiated in the program and then tries to load the appropriate include file that defines that class. You implement that using the spl_autoload_register()
function.
With the spl_autoload_register()
function, you define the location for all of the class include files based on the class name. With a little bit of programming magic, you can make that task a breeze:
spl_autoload_register(function($class) {
include $class . ".inc.php";
});
The anonymous function provided to the spl_autoload_register()
function defines the include file to load whenever a class is instantiated in the PHP code. The anonymous function attempts to load the include file with the same name as the class name, with an .inc.php
file extension. Using this method, you must be careful to save the class definition files using the class name as the filename, plus the .inc.php
file extension.
Follow these steps to try out using the autoload feature in PHP:
<?php
class Product {
private $description;
private $price;
private $inventory;
private $onsale;
public function __construct($name, $cost, $quantity, $sale) {
$this->description = $name;
$this->onsale = $sale;
if ($cost < 0) {
$this->price = 0;
} else {
$this->price = $cost;
}
if ($quantity < 0) {
$this->inventory = 0;
} else {
$this->inventory = $quantity;
}
}
public function __set($name, $value) {
if ($name == "price" && $value < 0) {
echo "<p>Invalid price set<p>\n";
$this->price = 0;
} elseif ($name == "inventory" && $value < 0) {
echo "<p>Invalid inventory set: $value</p>\n";
} else {
$this->$name = $value;
}
}
public function __get($name) {
return $this->$name;
}
public function __clone() {
$this->price = 0;
$this->inventory = 0;
$this->onsale = false;
}
public function __toString() {
$output = "<p>Product: " . $this->description . "<br>\n";
$output .= "Price: $" . number_format($this->price,2) . "<br>\n";
$output .= "Inventory: " . $this->inventory . "<br>\n";
$output .= "On sale: ";
if ($this->onsale) {
$output .= "Yes</p>\n";
} else {
$output .= "No</p>\n";
}
return $output;
}
public function buyProduct($amount) {
if ($this->inventory >= $amount) {
$this->inventory -= $amount;
} else {
echo "<p>Sorry, invalid inventory requested:
$amount</p>\n";
echo "<p>There are only $this->inventory
left</p>\n";
}
}
public function putonsale() {
$this->onsale = true;
}
public function takeoffsale() {
$this->onsale = false;
}
}
?>
Product.inc.php
(note the capitalization) in the DocumentRoot
folder for your web server.<!DOCTYPE html>
<html>
<head>
<title>PHP Total OOP Test</title>
</head>
<body>
<h1>Testing the PHP class</h1>
<?php
spl_autoload_register(function($class) {
include $class . ".inc.php";
});
$prod1 = new Product("Carrots", 4.00, 10, false);
echo "<p>Creating one product:</p>\n";
echo $prod1;
$prod2 = new Product("Eggplant", 2.00, 5, true);
echo "<p>Creating one product:</p>\n";
echo $prod2;
echo "<p>Putting $prod1->description on sale:</p>\n";
$prod1->price = 3.00;
$prod1->putonsale();
echo "<p>New product status:</p>\n";
echo $prod1;
?>
</body>
</html>
ooptest3.php
in the DocumentRoot
folder for your web server.http://localhost:8080/ooptest3.php
When you run the ooptest3.php
file, you should see the output shown in Figure 5-3.
FIGURE 5-3: The output from the ooptest3.php
program.
The code saves the Product
class definition code in the Product.inc.php
file and then uses the autoloader feature to load the Product
class include file when needed. It instantiates two Product
class objects using the constructor and displays them on the web page.
Following that, the code changes the price for the $prod1
object using the class mutator and uses the putonsale()
method to place the product on sale. The code finishes with an echo
statement so you can see the changes made to the class object. Now things are really starting to get fancy!
No, I’m not talking about making you stay after school! OOP provides a way to extend an existing class by adding additional members to an existing class. That’s the whole beauty of OOP: You can take classes and use them as is, or you can modify just the pieces you need to fit your particular application.
Defining a new class that’s an extension of another class is called inheritance. The new class (called the child) inherits all the public or protected members from the original class (called the parent). You can then add new members to the child class and even override members of the parent class. If you use the overridden members, the child members take precedence over the parent members.
To create a child class, you use the normal class definition format, along with the extends
keyword and the name of the class you're extending:
class Soda extends Product {
For the new class, Soda
, to inherit the Product
class properties, you need to change the visibility of the properties to protected
:
class Product {
protected $description;
protected $price;
protected $inventory;
protected $onsale;
…
With the properties set to protected
visibility, the Soda
child class will automatically inherit the description
, price
, inventory
, and onsale
properties from the Product
class, along with all the public class methods.
In the child class definition, you can add additional properties and methods that are unique to the child class:
private ounces;
public function restock($amount) {
$this->inventory += $amount;
}
Notice that the restock()
method uses the inventory
property that was inherited from the Product
parent class. When you define a method in a child class, it's only available for objects that are instantiated from the child class. Objects instantiated from the parent class won’t see that method.
Because the Soda
child class contains an additional property, you need to override the __construct()
method from the parent class to add the new property. That code looks like this:
public function __construct($name, $value, $amount, $sale,
$size) {
parent::__construct($name, $value, $amount,
$sale);
$this->ounces = $size;
}
The new constructor for the Soda
child class requires five parameters. Note the first line in the constructor code:
parent::__construct($name, $value, $amount, $sale);
The parent::
keyword tells PHP to run the constructor from the parent object. This assigns values to those properties inherited from the parent. The property unique to the child class is assigned a separate value from the parameters.
To instantiate a new child object you'd then just use the following:
$rootbeer = new Soda("Root Beer", 1.50, 10, false, 18);
Inside the child class definition, you can override any or all of the parent methods. Any methods that don’t override the child class objects can use the parent methods.
Follow these steps to test out using inheritance in your OOP PHP code.
Product.inc.php
file in your editor.Change the private
visibility keyword to protected
.
Look for these four lines:
private $description;
private $price;
private $inventory;
private $onsale;
And change them to the following:
protected $description;
protected $price;
protected $inventory;
protected $onsale;
Product.inc.php
in the DocumentRoot
folder for your web server.<?php
include("Product.inc.php");
class Soda extends Product {
private $ounces;
public function __construct($name, $value, $amount, $sale, $size) {
parent::__construct($name, $value, $amount, $sale);
$this->ounces = $size;
}
public function __toString() {
$output = "<p>Product: " . $this->description . "<br>\n";
$output .= "Price: $" . number_format($this->price,2) . "<br>\n";
$output .= "Inventory: " . $this->inventory . "<br>\n";
$output .= "On sale: ";
if ($this->onsale) {
$output .= "Yes<br>\n";
} else {
$output .= "No<br>\n";
}
$output .= "Ounces: " . $this->ounces . "</p>\n";
return $output;
}
public function restock($amount) {
$this->inventory += $amount;
}
}
?>
Soda.inc.php
in the DocumentRoot
folder for your web server.<!DOCTYPE html>
<html>
<head>
<title>Testing PHP Inheritance</title>
</head>
<body>
<h1>Testing inheritance in PHP OOP</h1>
<?php
spl_autoload_register(function($class) {
include $class . ".inc.php";
});
$prod1 = new Soda("Root Beer", 1.25, 10, false, 18);
echo $prod1;
echo "<p>Buying 6 bottles:</p>\n";
$prod1->buyProduct(6);
echo $prod1;
echo "<p>Restocking 4 bottles:</p>\n";
$prod1->restock(4);
echo $prod1;
?>
</body>
</html>
ooptest4.php
in the DocumentRoot
folder for your web server.http://localhost:8080/ooptest4.php
When you run the ooptest4.php
code, you should see the output as shown in Figure 5-4.
FIGURE 5-4: The output from the ooptest4.php
program.
The Soda
class code overrides both the constructor and the __toString()
methods of the Product
parent class to accommodate the additional $ounces
property. The ooptest4.php
code creates an instance of the Soda
class, uses the buyProduct()
method from the parent class to buy bottles, and then uses the restock()
method from the child class to restock them. Notice that the child class object has access to the public buyProduct()
method from the parent class.