Besides encapsulation, another major advantage of object-oriented code is reuse. Reusing code reduces development time and the number of bugs. Object-oriented programming promotes reuse through a process known an inheritance.
With inheritance, you can modify a class by adding or rewriting its methods. This allows your new class to be more specific than the original one, while still allowing you access to all the methods of the first class. The original class is known as the parent, and the new class is called the child.
Creating a child class is also called extending a class or subclassing an object. The original class that's extended can also be called a super class or a base class.
For instance, you can extend Person to create an Employee class, where an Employee is a Person with a salary.
When extending a class, abide by the "is a" rule. You should always be able to say, "Child class is a Parent class." Following this rule leads to clean relationships between your classes. This example is okay because an Employee is a Person:
class Person { private $name; public function setName($name) { $this->name = $name; } public function getName( ) { return $this->name; } } class Employee extends Person { private $salary; public function setSalary($salary) { $this->salary = $salary; } public function getSalary( ) { return $this->salary; } } $billg = new Employee; $billg->setName('Bill Gates'); $billg->setSalary(865114); // Actual 2003 salary (excluding stock)
This code creates a new Employee class; instantiates a new instance of Employee, $billg; and sets his name and salary.
The extends keyword at the top of the class definition indicates to PHP that this class should inherit all the properties and methods of the parent class. This allows you to interact with a child object in the same way as its parent because when PHP cannot find a property or method in the child class, it searches the parent class.
Therefore, you can call setName( ) on an Employee, even though this method isn't defined in the class, because it's defined in Person and Employee extends Person.
Inheritance also lets you modify the parent class. This allows you to create two new methods, setSalary( ) and getSalary( ), and a property, $salary, to store the salary information.
When using inheritance, a third visibility level, protected, is useful. A public method is callable by anyone; a private method is callable by any method in the same class, but not by any methods in a subclass; a protected method, like a private method, restricts access to only the methods within the class, but it also allows access from children classes.
You should use protected instead of private in your classes, unless you have a particularly strong reason to deliberately prevent a subclass from accessing a method or property.
In the previous example, it did not matter that the name property was declared private, because it's altered only by setName( ) and getName( ), and these methods are in the same class as name. However, you could not add a new method to Employee that references name. This would not be the case if name were declared as protected.
Inheritance isn't limited to one level. You can further extend Employee:
class Executive extends Employee { public function swindleShareholders( ) { } }
Since an instance of the Executive class inherits everything from Employee, it also inherits everything from Person. This simple concept allows you to build up a tree of classes that start with an extremely basic class and become further specialized as they continue to extend each other. This is known as a class hierarchy.
You can branch classes in your class hierarchy. For instance, in addition to an Employee line, you can have a Student class that also extends Person. This results in the hierarchy shown in Figure 2-1.
Some languages, most notably C++, allow a class to extend multiple parents. This is known as multiple inheritance. Multiple inheritance is handy when your child needs to take on characteristics of two different sets of parents.
For instance, consider an object that is an Employee and a Parent. Many people fit both criteria, but it doesn't make sense for Parent to subclass Employee or vice versa, because it's possible to be one and not the other.
This is easy to check because it violates the "is a" rule. An Employee is not a Parent any more than a Parent is an Employee.
The "is a" relationship also applies to the instanceof operator. For example, an instance of Employee is also a Person:
$billg = new Employee; if ($billg instanceof Person) { print "Employees are people too!"; } Employees are people too!
Since Employee subclasses Person, it's also considered an instance of Person.
Type hinting does an instanceof check, so a type hint for a Person can be either an instance of the Person class or any class that extends Person, such as Employee.
PHP does not support multiple inheritance, because multiple inheritance introduces many complexities. For instance, what happens when a class inherits different methods with the same name? It's not clear which method is the "correct" one. Instead, PHP handles these situations like Java does: by using interfaces.
As you may remember, an interface is a way to require a class to support a specific set of methods. When a class is required to implement an interface, you know that you can use the class as you need without corrupting the object hierarchy. Additionally, PHP 5 allows a class to implement multiple interfaces, so you're not constrained to a single class and a single interface.
Sometimes you have a slightly different problem. You have a series of objects that are related using the "is a" relationship, so it makes logical sense to have them descend from a common parent. However, while the children are tangible, the parent is abstract.
Take, for example, the Database class used previously in this chapter. A database is a real object, so it makes sense to have a Database class. However, although Oracle, MySQL, Postgres, MSSQL, and hundreds of other databases exist, you cannot download and install a generic database. You must choose a specific database.
PHP 5 provides a way for you to create a class that cannot be instantiated. This class is known as an abstract class. Here's an updated version of the Database class:
abstract class Database { abstract public function connect( ); abstract public function query( ); abstract public function fetch( ); abstract public function close( ); }
Mark a class as abstract by placing the abstract keyword before class.
Abstract classes must contain at least one method that is also marked abstract. These methods are called abstract methods. Database contains four abstract methods: connect( ), query( ), fetch( ), and close( ). These four methods are the basic set of functionality necessary to use a database.
If a class contains an abstract method, the class must also be declared abstract. However, abstract classes can contain nonabstract methods (even though there are no regular methods in Database).
Abstract methods, like methods listed in an interface, are not implemented inside the abstract class. Instead, abstract methods are implemented in a child class that extends the abstract parent. For instance, you could use a MySQL class:
class MySQL extends Database { protected $dbh; protected $query; public function connect($server, $username, $password) { $this->dbh = mysql_connect($server, $username, $password); } public function query($sql) { $this->query = mysql_query($sql, $this->dbh); } public function fetch( ) { return mysql_fetch_row($this->query, $this->dbh); } public function close( ) { mysql_close($this->dbh); } }
If a subclass fails to implement all the abstract methods in the parent class, then it itself is abstract and another class must come along and further subclass the child. You might do this if you wanted to create two MySQL classes: one that fetched information as objects and another that returned arrays.
There are two requirements for abstract methods:
Abstract methods cannot be defined private, because they need to be inherited.
Abstract methods cannot be defined final, because they need to be overridden.
PHP 5 offers the ability to restrict child classes from overriding a method in their parent. A final method is one that cannot be overridden by a child class. This is discussed in more detail in Section 2.5.5, later in this chapter.
Inheritance is the reason PHP 5 changed constructor names from the class's name to _ _construct( ). When constructors are tied to class names, it's easy to break code without realizing it. In PHP 4, to call the parent constructor inside of your class, you must hardcode the parent class name:
// Hardcoded parent constructor name class MyDatabase extends MySQL { function MyDatabase( ) { // automatically populate connection values parent::MySQL('db.example.com', 'web', 'jsd6w@2d'); } }
This class, MyDatabase, is a wrapper for the main MySQL class. The two classes act identically, except that MyDatabase hardcodes the information necessary to connect to the database.
It uses the parent prefix to explicitly reference the constructor in the parent class. Otherwise, it may accidentally reference a method named MySQL in the current class. It is unlikely that you'd create such a method, but this eliminates any potential problems and makes parental reference clear.
However, moving this class under a different parent, or even renaming the parent, forces you to edit the constructor, replacing the call to parent::MySQL( ) with the new parent constructor's name.
The more flexible solution makes you jump through a few hoops to dynamically determine the name of the parent's class:
// Dynamic parent constructor name class MyDatabase extends MySQL { function MyDatabase( ) { // automatically populate connection values $parent = get_parent_class($this); parent::$parent('db.example.com', 'web', 'jsd6w@2d'); } }
This code is inefficient because you must call get_parent_class( ) every time you create a new instance of the class.
Using a fixed name for constructors reduces the brittleness of OO code. Calling a parent constructor is now easy and safe:
// Hardcoded parent constructor name class MyDatabase extends MySQL { function MyDatabase( ) { // automatically populate connection values parent::_ _construct('db.example.com', 'web', 'jsd6w@2d'); } }
With the new naming scheme, you can always use parent::_ _construct( ) and know that it's the right thing to do.
Inheritance is normally a good thing, but it can make sense to restrict it. The most common reason to declare a method final is that the method is "perfect." When you believe there's no way to update the method to make it better, declare it using the final keyword. This prevents subclasses from ruining it by reimplementing the method in an inferior manner.
Make a method final by placing the final keyword at the beginning of the method declaration:
final public function connect($server, $username, $password) { $this->dbh = mysql_connect($server, $username, $password); }
This prevents someone from subclassing MySQL or creating a different connect( ) method.
To prevent subclassing of an entire class, don't mark each method final. Instead, make a final class:
final class MySQL extends Database { }
A final class cannot be subclassed. This differs from a class in which every method is final because that class can be extended and provided with additional methods, even if you cannot alter any of the pre-existing methods.
PHP 5 also lets you mark properties as final; however, it does not call them final properties. Instead, it reuses its concept of constants.
class Math { const pi = 3.14159; // universal const e = 2.71828; // constants } $area = math::pi * radius * raidus;
Like static properties, you can access constants without first instantiating a new instance of your class, and they're accessed using the double colon (::) notation. Prefix the word self:: to the constant name to use it inside of a class.
Unlike properties, constants do not have a dollar sign ($) before them:
class Circle { const pi = 3.14159; protected $radius; public function _ _construct($radius) { $this->radius = $radius; } public function diameter( ) { return 2 * self::pi * $this->radius; } } $c = new circle(1); print $c->diameter( ); 6.28318
This example creates a circle with a radius of 1 and then calls the diameter method to calculate its diameter. To use the class's pi constant, refer to it as self::pi; otherwise, PHP tries to access the value of the global pi constant:
define('pi', 10); // global pi constant class Circle { const pi = 3.14159; // class pi constant protected $radius; public function _ _construct($radius) { $this->radius = $radius; } public function diameter( ) { return 2 * pi * $this->radius; } } $c = new circle(1); print $c->diameter( ); 20
Oops! PHP has used the value of 10 instead of 3.14159, so the new answer is 20 instead of 6.28318.
Although it's unlikely that you will accidentally redefine (you'll probably use the built-in M_PI constant anyway), this can still slip you up.
You cannot assign the value of an expression to a constant, nor can they use information passed into your script:
// invalid class permissions { const read = 1 << 2; const write = 1 << 1; const execute = 1 << 0; } // invalid and insecure class database { const debug = $_REQUEST['debug']; }
Neither the constants in permissions nor the debug constant in database are acceptable because they are not fixed. Even the first example, 1 << 2, where PHP does not need to read in external data, is not allowed.