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( );

}

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.