4.2 InheritanceInheritance 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
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
Figure 4-1. Class diagram showing UnitCounter and CaseCounter
The new
CaseCounter
class provides features
// 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
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
When a new
CaseCounter
object is created and used, all of the
// 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. 4.2.1 Calling Parent ConstructorsThe 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
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. 4.2.2 Redefined FunctionsBoth 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
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
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. 4.2.3 Protected Member Variables and Functions
Protected
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
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( )
{
// + per case
return 15 + $this->numberOfCases * 1.00;
}
protected function perKgTotal( )
{
//
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. 4.2.4 Final FunctionsDeclaring 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( );
}
|