Object-oriented programming is a several-decades-old concept that has spread to almost every aspect of modern programming languages and practices. The reason is clear as soon as you start to use a convenience such as the powerful PHP-related packages. We introduce PEAR packages in Chapter 7; many operate by defining objects, providing a wealth of useful features in a simple form. You should understand the basics of object-oriented programming in order to make use of packages and an error-recovery feature called exceptions. You may also find object-oriented programming a useful practice in your own code. We'll give you an introduction in this chapter, and present some advanced features in Chapter 14.
While many of the concepts and techniques presented in this chapter work in PHP 4, support is greatly enhanced in PHP 5. In this chapter we describe what you can and can't do in each version of PHP.
The basic idea of object-oriented programming is to bind data and
functions in convenient containers called objects . For instance, in Chapter 7 we'll show you how to
standardize the look of your own web pages through an object called a
template. Your PHP code can refer to this object through a variable;
we'll assume here you've decided to call the variable $template
. All the complex implementation of
templates is hidden: you just load in the proper package and issue a PHP
statement such as:
$template = new HTML_Template_IT("./templates");
As the statement suggests, you've just created a new object. The
object is called $template
and is
built by the HTML_Template_IT
package—a package whose code you don't need to know anything about. Once
you have a template object, you can access the functionality provided by
the HTML_Template_IT
package.
After various manipulations of the $template
object, you can insert the results
into your web page through the PHP statement:
$template->show( );
The syntax of this statement is worth examining. As the
parentheses indicate, show( ) is a
function. However the ->
operator
associates show( ) with the object
variable $template
. When the function
show( ) is called, it uses the data
that is held by the $template
object
to calculate a result: put another way, show(
) is called on the $template
object.
The functions that you can call depend on the support provided by
the package—the show( ) function is
provided by the HTML_Template_IT
package and can be called on HTML_Template_IT objects such as $template
. In traditional object-oriented
parlance, show( ) is called a
method or member function
of the HTML_Template_IT object.
HTML_Template_IT is called a
class because you can use it to create many similar template
objects. Each time you issue a new
statement you are said to create an instance of the class. Thus, the $template
object is an instance of the
HTML_Template_IT class.
We've shown how to use objects created by other packages. However, to understand objects better, it's time to define a class of our own. Example 4-1 shows a simple class invented for the purposes of this chapter that's called UnitCounter. The UnitCounter class provides two trivial features: we can use a UnitCounter object to keep a count of things, and to calculate the total weight of the things we have counted. Later in this chapter, and in Chapter 14 we use the UnitCounter class, together with other classes, to develop a simple freight-cost calculator.
Example 4-1 shows how
the class UnitCounter is defined
using the class
keyword. The
UnitCounter class defines two
member variables $units
and $weightPerUnit
, and two functions add( ) and totalWeight( ). Collectively, the variables
and the functions are members of
the class UnitCounter.
Example 4-1. Definition of the user-defined class UnitCounter
<?php // Definition of the class UnitCounter // class UnitCounter { // Member variables var $units = 0; var $weightPerUnit = 1.0; // Add $n to the total number of units, default $n to 1 function add($n = 1) { $this->units = $this->units + $n; } // Member function that calculates the total weight function totalWeight( ) { return $this->units * $this->weightPerUnit; } } ?>
The class definition defines how data and functionality are actually bound together—member variables and functions take their meaning from the class of which they're a part. The class definition shown in Example 4-1 does not actually run any code or produce any output. Instead a class definition creates a new data type that can be used in a PHP script. In practice, you might save the class definition in an include file, and include that file into any script that makes use of the class.
To use the member variables and functions defined in a class, an
instance of the class or object needs to be
created. Like other data types such as integers, strings, or arrays,
objects can be assigned to variables. However, unlike other types,
objects are created using the new
operator. An object of class UnitCounter can be
created and assigned to a variable as follows:
// Create a new UnitCounter object $bottles = new UnitCounter;
Unlike variable names, class names in PHP are not case sensitive. While we start all our class names with an uppercase letter, UnitCounter, unitcounter, and UNITCOUNTER all refer to the same class.
Once a new UnitCounter object
is created and assigned to the $bottles
variable, the member variables and
functions can be used. Members of the object, both variables and
functions, are accessed using the ->
operator. The $units
member variable can be accessed as
$bottles->units
and used like any
other variable:
// set the counter to 2 dozen bottles $bottles->units = 24; // prints "There are 24 units" print "There are {$bottles->units} units";
To include the value of an object's member variables in a double-quoted string literal, the braces syntax is used. String literals and the braces syntax are discussed in Chapter 2.
The add( ) member function can be called to
operate on the $bottles
variable by
calling $bottles->add( ). The following fragment
increases the value of $bottles->units
by 3:
// Add three bottles $bottles->add(3); // prints "There are 27 units" print "There are {$bottles->units} units";
Many objects of the same class can be created. For example, you can use the following fragment to create two UnitCounter objects and assign them to two variables:
// Create two UnitCounter objects $books = new UnitCounter; $cds = new UnitCounter; // Add some units $books->add(7); $cds->add(10); // prints "7 books and 10 CDs" print "{$books->units} books and {$cds->units} CDs";
Both the $books
and $cd
variables reference
UnitCounter objects, but each object is independent
of the other.
Member variables are available in PHP4 and PHP5.
Member variables are declared as part of a class definition
using the var
keyword. Member
variables can also be defined with the private
and protected
keywords as we describe later in
the chapter. Member variables hold the data that is stored in an
object.
The initial value assigned to a member variable can be defined in the class definition. The UnitCounter class defined in Example 4-1 sets initial values for both member variables:
var $units = 0; var $weightPerUnit = 1.0;
The var
keyword is required
to indicate that $units
and
$weightPerUnit
are class member
variables. When a new UnitCounter
object is created, the initial values of $units
and $weightPerUnit
are set to 0 and 1.0
respectively. If a default value is not provided in the class
definition, then the member variable is not set to any value.
You don't have to explicitly declare member variables as we have in Example 4-1. However, we recommend that you always declare them and set an initial value because it makes the initial state of the variables obvious to users of your code.
Member functions are available in PHP4 and PHP5.
Member functions are defined as part of the class definition—the
UnitCounter class defined in
Example 4-1 includes two
member functions add( ) and
totalWeight( ). Both these
functions access the member variables of the object with the special
variable $this
. The variable
$this
is special because PHP uses
it as a placeholder until a real object is created. When a member
function is run, the value of $this
is substituted with the actual object that the function is called on.
Consider the implementation of the add( ) member
function of UnitCounter from Example 4-1:
// Add $n to the total number of units, default $n to 1 if // no parameters are passed to add( ) function add($n = 1) { $this->units = $this->units + $n; }
The function adds the value of the parameter $n
to the member variable $this->units
. If no parameter is passed,
$n
defaults to 1. When the
add( ) function is called on the $bottles
object in the following
example,
// Create a new UnitCounter object $bottles = new UnitCounter; // Call the add( ) function $bottles->add(3);
the placeholder $this
in the
add( ) function acts as the object $bottles
.
The totalWeight( ) member function also
accesses member variables with the $this
placeholder: the function returns the
total weight by multiplying the value of the member variables $this->units
and $this->weightPerUnit
.
// Create a new UnitCounter object $bricks = new UnitCounter; $bricks->add(15); // Prints 15 - 15 units at 1 Kg each print $bricks->totalWeight( );
PHP5 allows the result of a member function to be included into a string literal using the braces syntax. The following fragment shows how, and shows an alternative that can be used with PHP4:
// This line only works for PHP5 print "total weight = {$bottles->totalWeight( )} kg"; // This works for both PHP4 and PHP5 print "total weight = " . $bottles->totalWeight( ) . " kg";
By placing the definition in Example 4-1 into a file—for
example UnitCounter.inc—you can include or
require the UnitCounter class in
other scripts. Example
4-2 uses the require
directive to include the UnitCounter class definition.
Example 4-2. Using the UnitCounter class
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html401/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <title>Using UnitCounter</title> </head> <body> <?php require "UnitCounter.inc"; // Create a new UnitCounter object $bottles = new UnitCounter; // set the counter to 2 dozen bottles $bottles->units = 24; // Add a single bottle $bottles->add( ); // Add three more $bottles->add(3); // Show the total units and weight print "There are {$bottles->units} units, "; print "total weight = " . $bottles->totalWeight( ) . " kg"; // Change the default weight per unit and show the new total weight $bottles-> weightPerUnit = 1.2; print "<br>Correct total weight = " . $bottles->totalWeight( ) . " kg"; ?> </body> </html>
We introduce the include and require directives in Chapter 2, and further examples are given in Chapter 6 and Chapter 16 where we develop practical libraries for our case study, Hugh and Dave's Online Wines.
Two different methods of defining constructors are available in PHP5, and one method is available in PHP4.
As discussed previously, when an object is created from the
UnitCounter class defined in
Example 4-1, PHP will
initialize the member variables $units
and $weightPerUnit
to 0 and 1.0 respectively. If
you needed to set the weight per unit to another value, you can set
the value directly after creating the object. For example:
// Create a new UnitCounter object $bottles = new UnitCounter; // Set the true weight of a bottle $bottles->weightPerUnit = 1.2;
However, a better solution is to define a constructor function that correctly sets up the initial state of a new object before it is used. If a constructor is defined, you don't have to do anything in your code because PHP automatically calls it when a new object is created.
PHP5 allows you to declare a constructor method by including the member function _ _construct( ) in the class definition—the function name _ _construct( ) is reserved for this purpose (the characters preceding the word construct are two consecutive underscores). Example 4-3 shows a modified UnitCounter class with a constructor that automatically sets the weight per unit.
Example 4-3. Defining a constructor for the class UnitCounter
<?php class UnitCounter { var $units; var $weightPerUnit; function add($n = 1) { $this->units = $this->units + $n; } function totalWeight( ) { return $this->units * $this->weightPerUnit; } // Constructor function that initializes the member variables function _ _construct($unitWeight = 1.0) { $this->weightPerUnit = $unitWeight; $this->units = 0; } } ?>
The class definition works the same as the definition shown in
Example 4-1. However, the
initial values for $units
and
$weightPerUnit
are no longer
defined with the variable declaration instead they are set in the
_ _construct( ) member function.
A new UnitCounter object that
uses the class defined in Example 4-3 is created as
follows:
// Create a UnitCounter where each unit is 1.2 kg -- the // weight of a full wine bottle. $bottles = new UnitCounter(1.2);
When the object is created, PHP automatically calls the
_ _construct( ) with the
parameters supplied after the class name. So, in this example, 1.2 is
passed as a value to the _ _construct(
) method and the $bottles->weightPerUnit
variable is set
to 1.2. UnitCounter objects can
still be created without passing a value to the constructor as the
parameter variable $unitWeight
defaults to 1.0.
You can also define a constructor method by including a function with the same name as the class. This is the only way constructors can be defined in PHP 4, but it can also be used as an alternative in PHP5. For example, using this technique, the _ _construct( ) function in Example 4-3 could be replaced with:
function UnitCounter($weightPerUnit = 1) { $this->weightPerUnit = $weightPerUnit; $this->units = 0; }
Using the _ _construct( ) function makes managing large projects easier, because it allows classes to be moved, renamed, and reused in a class hierarchy without changing the internals of the class definition. We discuss class hierarchies in Chapter 14.
Destructors are available in PHP5.
If it exists, a constructor function is called when an object is created. Similarly, if it exists, a destructor function is called when an object is destroyed. Like other PHP variables, objects are destroyed when they go out of scope or when explicitly destroyed with a call to the unset( ) function. We discuss variable scope in Chapter 2.
A destructor function is defined by including the function _ _destruct( ) in the class definition (again, the prefix before the keyword destruct is two consecutive underscore characters, and _ _destruct( ) is a reserved function name). _ _destruct( ) can't be defined to take any parameters (unlike the _ _construct( ) function). However, the _ _destruct( ) function does have access to the member variables of the object that is being destroyed—PHP calls _ _destruct( ) just before the member variables are destroyed.
Destructor functions are useful when you want to perform some housekeeping tasks when a process has ended. For example, you might want to gracefully close down a connection to a DBMS or save user preferences to a file. Destructors can also be used as a debugging tool when developing object-oriented applications. For example, by adding the following _ _destruct( ) function to the UnitCounter defined in Example 4-3, you can track when objects are destroyed:
// Destructor function called just before a UnitCounter object // is destroyed function _ _destruct( ) { print "UnitCounter out of scope. Units: {$this->units}"; }
We give another example of _ _destruct( ) later in the chapter in Section 4.1.8.
Private member variables are available in PHP5.
When using the UnitCounter
class defined previously in Example 4-3, a script can use
the member variables $units
and
$weightPerUnit
directly, the
UnitCounter class doesn't
implement any safeguards that prevent inconsistent values being
assigned. For example, consider the following fragment that
erroneously sets the number of units to a fractional value and the
weight per unit to a negative number:
// Construct a new UnitCounter object $b = new UnitCounter; // Set some values $b->units = 7.3; $b->weightPerUnit = -5.5; $b->add(10); // Show the total units and weight print "There are {$b->units} units, "; print "total weight = {$b->totalWeight( )} kg";
This prints:
There are 7.3 units, total weight = -40.15 kg
In PHP5, a better solution is to define member variables as
private and provide member
functions that control how the variables are used. Example 4-4 shows both the
$units
and $weightPerUnit
member variables defined as
private.
Example 4-4. Private member variables
<?php class UnitCounter { private $units = 0; private $weightPerUnit = 1.0; function numberOfUnits( ) { return $this->units; } function add($n = 1) { if (is_int($n) && $n > 0) $this->units = $this->units + $n; } function totalWeight( ) { return $this->units * $this->weightPerUnit; } function _ _construct($unitWeight) { $this->weightPerUnit = abs((float)$unitWeight); $this->units = 0; } } ?>
When a UnitCounter object
is created using the class defined in Example 4-4, the $units
and $weightPerUnit
member variables can only be
accessed by code defined in the class. Attempts to access the private
member variables cause an error:
// Construct a UnitCounter object as defined inExample 4-4 $b = new UnitCounter(1.1); // These lines cause an error $b->units = 7.3; $b->weightPerUnit = -5.5;
The member function numberOfUnits(
) provides access to the value of $units
, and the member function add( ) has been improved so only positive
integers can be added to the count value. We have also improved the
_ _construct( ) function to
ensure that $weightPerUnit
is only
set with a positive value.
Providing member functions that control how member variables are used is good object-oriented practice. However, without making member variables private, there is little point in providing such safeguards, because users can directly access and modify the member variable values.
Private member functions are available in PHP5.
Member functions can also be defined as private to hide the implementation of a class. This allows the implementation of a class to be modified, or replaced without any effect on the scripts that use the class. Example 4-5 demonstrates how the class FreightCalculator hides the internal methods used by the publicly-accessible member function totalFreight( ). The method calculates a freight cost using two private functions perCaseTotal( ) and perKgTotal( ).
Example 4-5. Private member functions
class FreightCalculator { private $numberOfCases; private $totalWeight; function totalFreight( ) { return $this->perCaseTotal( ) + $this->perKgTotal( ); } private function perCaseTotal( ) { return $this->numberOfCases * 1.00; } private function perKgTotal( ) { return $this->totalWeight * 0.10; } function _ _construct($numberOfCases, $totalWeight) { $this->numberOfCases = $numberOfCases; $this->totalWeight = $totalWeight; } }
Like private member variables, private functions can only be accessed from within the class that defines them. The following example causes an error:
// Construct a FreightCalculator object as defined inExample 4-5 $f = new FreightCalculator(10, 150); // These lines cause an error print $f->perCaseTotal( ); print $f->perKgTotal( ); // This is OK -- prints "25" print $f->totalFreight( );
Static member variables are available in PHP5.
PHP allows member variables and functions to be declared as
static using the static
keyword. As we have shown in our
examples so far, normal member variables are independent from object
to object. In contrast, static member variables are shared across all
instances of a class. This allows you to share values between several
instances of a class without declaring a global variable that's
accessible throughout your application.
Example 4-6 defines
the class Donation that records a
donor name and donation amount in the private member variables
$name
and $amount
. The class keeps track of the total
amount donated, and the total number of donations using two static
variables $totalDonated
and
$numberOfDonors
. The values of
these two variables are accessible to all instances of the class, and
each instance can update and read the values. Static member variables
are accessed using a class
reference rather than the ->
operator. In Example 4-6, the static
variables $totalDonated
and
$numberOfDonors
are prefixed by the
class reference Donation:
: when
they are used.
Example 4-6. Static member variables
<?php class Donation { private $name; private $amount; static $totalDonated = 0; static $numberOfDonors = 0; function info( ) { $share = 100 * $this->amount / Donation::$totalDonated; return "{$this->name} donated {$this->amount} ({$share}%)"; } function _ _construct($nameOfDonor, $donation) { $this->name = $nameOfDonor; $this->amount = $donation; Donation::$totalDonated = Donation::$totalDonated + $donation; Donation::$numberOfDonors++; } function _ _destruct( ) { Donation::$totalDonated = Donation::$totalDonated - $donation; Donation::$numberOfDonors--; } } ?> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html401/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <title>Using Donation</title> </head> <body> <pre> <?php $donors = array( new Donation("Nicholas", 85.00), new Donation("Matt", 50.00), new Donation("Emily", 90.00), new Donation("Sally", 65.00)); foreach ($donors as $donor) print $donor->info( ) . "\n"; $total = Donation::$totalDonated; $count = Donation::$numberOfDonors; print "Total Donations = {$total}\n"; print "Number of Donors = {$count}\n"; ?> </pre> </body> </html>
The static variables $totalDonated
and $numberOfDonors
are updated in the _ _construct( ) function: the $donation
amount is added to the value of
$totalDonated
, and $numberOfDonors
is incremented. We have also
provided a _ _destruct( )
function that decreases the value of $totalDonated
and $numberOfDonors
when a Donation object is destroyed.
After the class Donation is defined, Example 4-6 creates an array of donation objects, then prints the total donated and the total number of donations:
$total = Donation::$totalDonated; $count = Donation::$numberOfDonors; print "Total Donations = {$total}\n"; print "Number of Donors = {$count}\n";
The previous fragment demonstrates that static variables can be
accessed from outside the class definition with the Donation:
: class reference prefix. You don't
access static member variable with the ->
operator (which is used with instances
of a class) because they are not associated with any particular
object.
A foreach
loop is used to
print information about each donation by calling the member function
info( ) for each Donation object. The info( ) member function returns a string
that contains the donor name, amount, and the percentage of the total
that the donor has contributed. The percentage is calculated by
dividing the value stored for the instance in $this->amount
by the static total value
Donation::$totalDonated
.
The output of Example 4-6 is as follows:
Nicholas donated 85 (29.3103448276%) Matt donated 50 (17.2413793103%) Emily donated 90 (31.0344827586%) Sally donated 65 (22.4137931034%) Total Donations = 290 Number of Donors = 4
Unlike other member variables, you don't need to create an object to use static member variables. As long as the script has access to the class definition, static variables are available using the class reference as shown in the following fragment:
// provide access to the Donation class definition require "example.4-6.php"; // Now set the static total Donation::$totalDonated = 124; Donation::$numberOfDonors = 5;
Static member functions are available in PHP5.
Static member functions are declared using the static
keyword, and like static member
variables, aren't accessed via objects but operate for the whole class
and are accessed using a class reference. We can modify Example 4-6 to provide access
to the static member variables using static member functions:
private static $totalDonated = 0; private static $numberOfDonors = 0; static function total( ) { return Donation::$totalDonated; } static function numberOfDonors( ) { return Donation::$numberOfDonors; }
Code that uses the modified Donation class can then access the $totalDonated
and $numberOfDonors
values by calling the static
functions Donation::total( ) and
Donation::numberOfDonors( )
respectively.
Static functions can only operate on static member variables and
can't operate on objects, and therefore the function body can't refer
to the placeholder variable $this
.
Like static member variables, you can access static functions without actually creating an object instance. Indeed we could have implemented the static member variables defined in Example 4-6, and the static member functions total( ) and numberOfDonors( ) described earlier using global variables and normal user-defined functions. Defining member variables and functions as static provides a way of grouping related functionality together in class definitions, promoting a modular approach to code development.
Objects can optionally be cloned in PHP5, and are always cloned in PHP4. We explain how this works in this section.
When a new object is created, PHP5 returns a reference to the object rather than the object itself. A variable assigned with an object is actually a reference to the object. This is a significant change from PHP4 where objects are assigned directly to variables. Copying an object variable in PHP5 simply creates a second reference to the same object. This behavior can be seen in the following fragment of code that creates a new UnitCounter object, as defined earlier in Example 4-1:
// Create a UnitCounter object $a = new UnitCounter( ); $a->add(5); $b = $a; $b->add(5); // prints "Number of units = 10"; print "Number of units = {$a->units}";
The _ _clone( ) method is available if you want to create an independent copy of an object. PHP5 provides a default _ _clone( ) function that creates a new, identical object by copying each member variable. Consider the following fragment:
// Create a UnitCounter object $a = new UnitCounter( ); $a->add(5); $b = $a->_ _clone( ); $b->add(5); // prints "Number of units = 5" print "Number of units = {$a->units}"; // prints "Number of units = 10" print "Number of units = {$b->units}";
The code creates an object $a
, and adds five units to it using
$a->add(5)
to give a total of
5 units in object $a
. Then,
$a
is cloned and the result is
assigned to a new object $b
. Five
units are then added to the new object $b
, to give a total of 10 units in
$b
. Printing out the number of
units for the original object $a
outputs 5, and printing the number of units for $b
outputs 10.
You can control how an object is copied by including a custom
_ _clone( ) function in a class
definition. If you wanted cloned UnitCounter objects to maintain the
$weightPerUnit
value, but to
reset the $units
value to zero,
you can include the following function in the class
definition:
function _ _clone( ) { $this->weightPerUnit = $that->weightPerUnit; $this->units = 0; }
The original, source object is referred to in the _ _clone( ) function using the special
place-holder variable $that
, and
the variable $this
is used to
reference the new, cloned object.
Rather than use references by default, new objects created with PHP4 can be assigned directly to variables. When an object variable is copied, PHP4 automatically clones the object. For example, consider the following PHP4 fragment:
// Create a UnitCounter object $a = new UnitCounter( ); $a->add(5); $b = $a; $b->add(5); // prints "Number of units = 5" print "Number of units = {$a->units}"; // prints "Number of units = 10" print "Number of units = {$b->units}";
The variable $b
is a clone
or copy of $a
, and so modifying
$b
does not affect $a
.
If you don't want to clone an object, use the reference
assignment =&
to copy a
reference. The following shows how $b
is assigned as a reference to UnitCounter object assigned to $a
:
// Create a UnitCounter object $a = new UnitCounter( ); $a->add(5); $b =& $a; $b->add(5); // prints "Number of units = 10" print "Number of units = {$a->units}"; // prints "Number of units = 10" print "Number of units = {$b->units}";
We discuss variable references and the reference assignment
operator =&
in Chapter 2.
Get Web Database Applications with PHP and MySQL, 2nd Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.