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.
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.
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.
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 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.
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.