One of the aspects of objects that make them so helpful for organizing your code is the notion of subclassing, which lets you reuse a class while adding some custom functionality. A subclass (sometimes called a child class) starts with all the methods and properties of an existing class (the parent class), but then can change them or add its own.
For example, consider an entree that is not just a single dish but a combination of a few, such as a bowl of soup and a sandwich together. Our existing Entree class would be forced to model this either by treating “soup” and “sandwich” as ingredients or by enumerating all of the soup ingredients and sandwich ingredients as ingredients of this combo. Neither solution is ideal: soup and sandwich themselves are not ingredients, and reenumerating all the ingredients would mean we would need to update multiple places when any ingredient changed.
We can solve the problem more cleanly by making a subclass of Entree that expects to be given Entree object instances as ingredients and then modifying the subclass’s
hasIngredient() method to inspect those object instances for ingredients. The code for this ComboMeal class is shown in Example 6-10.
Example 6-10. Extending the Entree class
class ComboMeal extends Entree {
public function hasIngredient($ingredient) { foreach ($this->ingredients as $entree) {
if ($entree->hasIngredient($ingredient)) { return true;
}
}
return false;
}
}
In Example 6-10, the class name, ComboMeal, is followed by extends Entree. This tells the PHP engine that the ComboMeal class should inherit all of the methods and properties of the Entree class. To the PHP engine, it’s as if you retyped the definition of Entree inside the definition of ComboMeal, but you get that without actually having to do all that tedious typing. Then, the only things that need to be inside the curly braces of ComboMeal’s definition are changes or additions. In this case, the only change is a new hasIngredient() method. Instead of examining $this->ingredients as an array, it treats it as an array of Entree objects and calls the hasIngredient() method on each of those objects. If any of those calls return true, it means that one of the entrees in the combo has the specified ingredient, so ComboMeal’s hasIngredient() method returns true. If, after iterating through all of the entrees, nothing has returned true, then the method returns false, which means that no entree has the ingredient in it. Example 6-11 shows the subclass at work.
Example 6-11. Using a subclass // Some soup with name and ingredients
$soup = new Entree(‘Chicken Soup’, array(‘chicken’, ‘water’));
// A sandwich with name and ingredients
$sandwich = new Entree(‘Chicken Sandwich’, array(‘chicken’, ‘bread’));
// A combo meal
$combo = new ComboMeal(‘Soup + Sandwich’, array($soup, $sandwich));
foreach ([‘chicken’,’water’,’pickles’] as $ing) { if ($combo->hasIngredient($ing)) {
print “Something in the combo contains $ing.\n”;
}
}
Extending an Object | 111
Because both the soup and the sandwich contain chicken, the soup contains water, but neither contains pickles, Example 6-11 prints:
Something in the combo contains chicken.
Something in the combo contains water.
This works well, but we don’t have any guarantee that the items passed to ComboMeal’s constructor are really Entree objects. If they’re not, then invoking hasIngredient() on them could cause an error. To fix this, we need to add a custom constructor to ComboMeal that checks this condition and also invokes the regular Entree constructor so that the properties are set properly. A version of ComboMeal with this constructor is shown in Example 6-12.
Example 6-12. Putting a constructor in a subclass
class ComboMeal extends Entree {
public function _ construct($name, $entrees) {
parent::_ construct($name, $entrees);
foreach ($entrees as $entree) {
if (! $entree instanceof Entree) {
throw new Exception(‘Elements of $entrees must be Entree objects’);
}
}
}
public function hasIngredient($ingredient) { foreach ($this->ingredients as $entree) {
if ($entree->hasIngredient($ingredient)) { return true;
}
}
return false;
}
}
The constructor in Example 6-12 uses the special syntax parent:: construct() to
refer to the constructor in Entree. Just as $this has a special meaning inside of object methods, so does parent. It refers to the class of which the current class is a subclass. Because ComboMeal extends Entree, parent inside of ComboMeal refers to Entree. So,
parent::_ construct() inside of ComboMeal refers to the construct() method
inside the Entree class.
In subclass constructors, it is important to remember that you have to call the parent
constructor explicitly. If you leave out the call to parent:: construct(), the parent
constructor never gets called and its presumably important behavior never gets executed by the PHP engine. In this case, Entree’s constructor makes sure that $ingredients is an array and sets the $name and $ingredients properties.
After the call to parent:: construct(), ComboMeal’s constructor ensures that each
provided ingredient of the combo is itself an Entree object. It uses the instanceof operator for this. The expression $entree instanceof Entree evaluates to true if $entree refers to an object instance of the Entree class.1 If any of the provided ingredients (which, for a ComboMeal, are really entrees) are not Entree objects, then the code throws an exception.
Source: Sklar David (2016), Learning PHP: A Gentle Introduction to the Web’s Most Popular Language, O’Reilly Media; 1st edition.