O'Reilly logo

Web Database Applications with PHP and MySQL, 2nd Edition by David Lane, Hugh E. Williams

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 4. Introduction to Object-Oriented Programming with PHP 5

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.

Classes and Objects

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

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

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";

Using include Files for Class Definitions

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.

Constructors

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

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 Members Variables

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

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

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

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.

Cloning Objects

Objects can optionally be cloned in PHP5, and are always cloned in PHP4. We explain how this works in this section.

Cloning in PHP5

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.

Cloning in PHP4

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.

Inheritance

Inheritance is available in PHP4 and PHP5.

One of the powerful concepts in object-oriented programming is inheritance. Inheritance allows a new class to be defined by extending the capabilities of an existing base class or parent class. PHP allows a new class to be created by extending an existing class with the extends keyword.

Example 4-7 shows how the UnitCounter class from Example 4-4 is extended to create the new class CaseCounter. The aim of the extended class is to track the number of cases or boxes that are needed to hold the units accumulated by the counter. For example, if bottles of wines are the units, then a case might hold 12 bottles.

Example 4-7. Defining the CaseCounter class by extending UnitCounter

<?php

// Access to the UnitCounter class definition
require "example.4-1.php";

class CaseCounter extends UnitCounter
{
    var $unitsPerCase;

    function addCase( )
    {
        $this->add($this->unitsPerCase);
    }

    function caseCount( )
    {
        return ceil($this->units/$this->unitsPerCase);
    }

    function CaseCounter($caseCapacity)
    {
        $this->unitsPerCase = $caseCapacity;
    }
}

?>

Before we discuss the implementation of the CaseCounter, we should examine the relationship with the UnitCounter class. Figure 4-1 illustrates this relationship in a simple class diagram . There are several different notations for representing class diagrams; we show the inheritance relationship by joining two classes with an annotated line with a solid arrowhead.

Class diagram showing UnitCounter and CaseCounter

Figure 4-1. Class diagram showing UnitCounter and CaseCounter

The new CaseCounter class provides features related to counting cases worth of units—for example, bottles of wine—while the UnitCounter base class provides the counting and total weight capabilities. To create a CaseCounter object, the number of units that are stored in a case needs to be specified. This value is passed to the constructor when new CaseCounter object is created,

// Create a CaseCounter that holds 12 bottles in a case
$order = new CaseCounter(12);

the value is then recorded in the member variable $unitsPerCase.

The addCase( ) member function uses the $unitsPerCase member variable to add a case of units to the counter:

function addCase( )
{
  // The add( ) function is defined in the 
  // base class UnitCounter
  $this->add($this->unitsPerCase);
}

The units are added by calling the base UnitCounter member function add( ). Unless they are declared as private, member variables and functions defined in the base class can be called in derived classes using the -> operator and the special placeholder variable $this.

The caseCount( ) member function calculates the number of cases needed to contain the total number of units. For example, if there are 50 bottles of wine, and a case can hold 12 bottles, then 5 cases are needed to hold the wine. The number of cases is therefore calculated by dividing the total number of units—stored in the member variable $unit defined in the UnitCounter class—by the member variable $unitsPerCase. The result of the division is rounded up to the next whole case with the ceil( ) function. The ceil( ) function is described in Chapter 3.

When a new CaseCounter object is created and used, all of the publicly accessible member variables and functions of the base class are also available. This means that you can use a CaseCounter object as if it were a UnitCounter but it also has the extra features of the CaseCounter class. Consider an example:

// Create a CaseCounter that holds 12 bottles in a case
$order = new CaseCounter(12);

// Add seven bottles using the UnitCounter defined function
$order->add(7);

// Add a case using the CaseCounter defined function
$order->addCase( );

// Print the total number of Units : 19
print $order->units;

// Print the number of cases: 2
print $order->caseCount( );

Unlike some other object-oriented languages, PHP only allows a single base class to be specified when defining new classes. Allowing inheritance from multiple base classes can lead to unnecessarily complex code and, in practice, isn't very useful. In Chapter 14, we explore advanced techniques that eliminate the need for multiple inheritance.

Calling Parent Constructors

The ability to call parent constructors is available in PHP5.

CaseCounter objects use three member variables: two are defined in the UnitCounter class, and the third is defined in CaseCounter. When a CaseCounter object is created, PHP calls the _ _construct( ) function defined in CaseCounter and sets the value of the member variable $unitsPerCase with the value passed as a parameter. In the following fragment, the value passed to the _ _construct( ) function is 12:

// Create a CaseCounter that holds 12 bottles in a case
$order = new CaseCounter(12);

PHP only calls the _ _construct( ) function defined in CaseCounter; the constructor of the parent class UnitCounter is not automatically called. Therefore, objects created from the CaseCounter class defined in Example 4-7 always have the weight defined as 1 kg, the value that's set in the member variable of the parent class. The CaseCounter class shown in Example 4-8 solves this problem by defining a _ _construct( ) function that calls the UnitCounter _ _construct( ) function using the parent:: reference.

Example 4-8. Calling parent constructor function

<?php

// Access to the UnitCounter class definition
include "example.4-4.php";

class CaseCounter extends UnitCounter
{
    private $unitsPerCase;

    function addCase( )
    {
        $this->add($this->unitsPerCase);
    }

    function caseCount( )
    {
        return ceil($this->numberOfUnits( )/$this->unitsPerCase);
    }

    function _  _construct($caseCapacity, $unitWeight)
    {
        parent::_  _construct($unitWeight);
        $this->unitsPerCase = $caseCapacity;
    }
}

?>

As Example 4-8 is written to use features provided by PHP5, we extend the more sophisticated UnitCounter class defined in Example 4-4. Also, the member variable $unitsPerCase is now defined to be private and we use the PHP5 _ _construct( ) function. The constructor function of the improved CaseCounter shown in Example 4-8 takes a second parameter, $unitWeight which is passed to the _ _construct( ) function defined in the UnitCounter class.

Redefined Functions

Both PHP4 and PHP5 allow functions to be redefined, and the parent:: and class reference operators are available in PHP5.

Functions defined in a base class can be redefined in a descendant class. When objects of the descendant class are created, the redefined functions take precedence over those defined in the base class. We have already seen the _ _construct( ) function of the base UnitCounter class redefined in the CaseCounter class in Example 4-8.

Consider the Shape and Polygon classes defined in the following code fragment:

class Shape
{
    function info( ) 
    { 
        return "Shape."; 
    }
}

class Polygon extends Shape
{
    function info( ) 
    { 
        return "Polygon."; 
    }
}

The class Shape is the base class to Polygon, making Polygon a descendant of Shape. Both classes define the function info( ). So, following the rule of redefined functions, when an object of class Polygon is created, the info( ) function defined in the Polygon class takes precedence. This is shown in the following example:

$a = new Shape;
$b = new Polygon;

// prints "Shape."
print $a->info( );

// prints "Polygon."
print $b->info( );

With PHP 5, we can use the parent:: reference to access the info( ) function from the parent class. For example, we can modify the Polygon class definition of info( ) as follows:

class Polygon extends Shape
{
    function info( ) 
    {
        return parent::info( ) . "Polygon."; 
    }
}

$b = new Polygon;

// prints "Shape.Polygon."
print $b->info( );

This approach can be used in descendant classes, proving a way of accumulating the result of ancestor functionality. Consider a Triangle class that extends the Polygon class:

class Triangle extends Polygon
{
    function info( )
    {
        return parent::info( ) . "Triangle.";
    }
}

$t = new Triangle;

// prints "Shape.Polygon.Triangle."
print $t->info( );

The parent:: reference operator only allows access to the immediate parent class. PHP allows access to any known ancestor class using a class reference operator—we introduced the class reference earlier in our discussion of static member variables and functions in Section 4.1. We can rewrite the Triangle class to call the ancestor version of the info( ) functions directly:

class Triangle extends Polygon
{
    function info( )
    {
        return Shape::info( ) . Polygon::info( ) . "Triangle.";
    }
}

$t = new Triangle;

// prints "Shape.Polygon.Triangle."
print $t->info( );

Using the class access operators makes code less portable. For example, you would need to modify the implementation of the Triangle class if you decided that Triangle would extend Shape directly. Using the parent:: reference operator allows you to re-arrange class hierarchies more easily.

Protected Member Variables and Functions

Protected members are available in PHP5.

Member variables and functions can be defined using the protected keyword. This offers a compromise between being public and private: it allows access to member variables and functions defined in a class from within descendant classes, but it prevents access to the member variables and functions from code outside of the class hierarchy. So, for example, a child class can access a parent class's protected functions, but the parent class protected functions can't be accessed from an unrelated class or from within a script that uses the class.

In Example 4-5, we introduced the FreightCalculator class to work out freight costs based on the number of cases and the total weight of a shipment. The FreightCalculator class defined in Example 4-5 calculates the per case and per kilogram costs using the two private functions perCaseTotal( ) and perKgTotal( ).

In Example 4-9, we rewrite the FreightCalculator class to define these functions as protected. This allows a new class AirFreightCalculator to extend FreightCalculator and redefine the functions to apply different rates per kilogram and case count.

Example 4-9. An air freight calculator

class FreightCalculator
{

    protected $numberOfCases;
    protected $totalWeight;

    function totalFreight( )
    {
        return $this->perCaseTotal( ) + $this->perKgTotal( );
    }

    protected function perCaseTotal( )
    {
        return $this->numberOfCases * 1.00;
    }

    protected function perKgTotal( )
    {
        return $this->totalWeight * 0.10;
    }

    function _  _construct($numberOfCases, $totalWeight)
    {
        $this->numberOfCases = $numberOfCases;
        $this->totalWeight = $totalWeight;
    }
}


class AirFreightCalculator extends FreightCalculator
{

    protected function perCaseTotal( )
    {
        // $15 + $1 per case
        return 15 + $this->numberOfCases * 1.00;
    }

    protected function perKgTotal( )
    {
        // $0.40 per kilogram
        return $this->totalWeight * 0.40;
    }
}

Because the AirFreightCalculator implementation of perCaseTotal( ) and perKgTotal( ) requires access to the FreightCalculator member variables $totalWeight and $numberOfCases, these have also been declared as protected.

Final Functions

Declaring final functions is available in PHP5.

The AirFreightCalculator class defined in Example 4-9 doesn't redefine the totalFreight( ) member function because the definition in FreightCalculator correctly calculates the total. Descendant classes can be prevented from redefining member functions in base classes by declaring them as final. Declaring the totalFreight( ) member function with the final keyword prevents accidental redefinition in a descendant class:

final function totalFreight( )
{
    return $this->perCaseTotal( ) + $this->perKgTotal( );

}

Throwing and Catching Exceptions

PHP 5 has introduced an exception model that allows objects to be thrown and caught using the throw and try...catch statements.

The throw and try...catch statements provide a way of jumping to error handling code in exceptional circumstances: rather than terminating a script with a fatal error, exceptions are thrown, and can be caught and processed. The throw statement is always used in conjunction with the try...catch statement, and the following fragment shows the basic structure:

$total = 100;
$n = 5;

$result;

try
{
    // Check the value of $n before we use it
    if ($n == 0)
        throw new Exception("Can't set n to zero.");

    // Calculate an average
    $result = $total / $n;
}
catch (Exception $x)
{
    print "There was an error: {$x->getMessage( )};
}

The block of statements contained in the braces that follow the try keyword are executed normally as part of the script; the braces are required, even for a single statement. If a throw statement is called in the try block, then the statements contained in the braces that follow the catch keyword are executed. The throw statement throws an object and the catch block of code catches the thrown object, assigning it to the variable specified.

The catch statement specifies the type of object that is caught by placing the class name before the variable: the following fragment catches Exception objects and assigns them to the variable $x:

catch (Exception $x)
{
    print "There was an error: {$x->getMessage( )};
}

Specifying the type of object that is caught in the catch block is an example of a class type hint . We discuss class type hints in Chapter 14.

The Exception Class

While objects of any class can be thrown, PHP5 predefines the Exception class that has useful features suitable for exception reporting.

Exception objects are constructed with a message and an optional integer error code. The message and error code are retrieved using the getMessage( ) and getCode( ) member functions. The line number and filename of the script that creates an Exception object is also recorded and retrieved with the getLine( ) and getFile( ) member functions. These functions are used in Example 4-10 to define the formatException( ) function that returns a simple error message for a given Exception object $e.

Example 4-10. Simple try-catch

<?php

function formatException(Exception $e)
{
    return "Error {$e->getCode( )}: {$e->getMessage( )}
        (line: {$e->getline( )} of {$e->getfile( )})";
}


function average($total, $n)
{
    if ($n == 0)
        throw new Exception("Number of items = 0", 1001);

    return $total / $n;
}


// Script that uses the average( ) function
try
{
    $a = average(100, 0);
    print "Average = {$a}";
}
catch (Exception $error)
{
    print formatException($error);
}

?>

Example 4-10 shows how a try...catch statement is used to catch exceptions thrown by the function average( ). The Exception object is created—with a message and error code—and thrown from the average( ) function if the value of $n is zero. Example 4-10 calls the average( ) function inside a try block. If average( ) throws an exception, it is caught by the catch block and the formatException( ) function is called to format the caught Exception object $error. When Example 4-10 is run the call to average( ) causes an Exception object to be thrown, and the following is output:

Error 1001: Number of items = 0
        (line: 13 of c:\htdocs\book\example.4-10.php)

If you called the average( ) as shown in Example 4-10 without a try...catch statement, any exceptions thrown wouldn't be caught and PHP 5 terminates the script with a fatal "Uncaught exception" error.

The throw and try...catch statements provide an alterative to calling the PHP exit( ) or die( ) functions that terminate a script. Using throw and try...catch statements allow you to develop applications that can handle exceptional circumstances in a controlled manner. However, exceptions are quite different from the errors and warnings that PHP generates when things go wrong. Unfortunately, a try...catch statement can't be used to catch fatal errors such as divide by zero. (You can suppress errors with the @ operator; we explain how in Chapter 6.) In Example 4-10, the code that implements the average( ) function tests the value of $n before using it in a division to avoid the fatal "Divide by Zero" error.

We discuss the management of PHP errors and warnings in Chapter 12.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required