08.07.2007 / Object-oriented PHP :: A guide for fellow ISys junkies
Abstraction
Now that we’ve talked about interfaces, we’re prepared to talk about abstract classes. As you may remember, an abstract class defines the core identity of its descendants. In other words, if we’re dealing with a dog, a cat, a chicken, and a pig, they all have a few things in common: they eat, they poop, and they make sounds. Rather than making those methods for the dog class and then copying them over to the cat class, the chicken class, and the pig class, it’ll be much more convenient and easier to maintain if we use an abstract class. In this case, our abstract class could be called Animal. The different types of animals will then extend this class. Let’s see an example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | // Declare the abstract class abstract class Animal { protected $stomach; public function eat($food) { $this->stomach[] = $food; echo "I ate $food<br />"; } public function poop() { echo "<p>Poo contents:"; echo "<ul>"; foreach($this->stomach as $poo_nugget) { echo "<li>$poo_nugget</li>"; } echo "</ul>"; unset($this->stomach); } public function makeSound($sound) { echo "$sound<br />"; } } // Extend the abstract class class Dog extends Animal { public function catchFrisbee($frisbee) { echo "I caught the $frisbee<br />"; } public function doIHavePlaydohInMyStomach() { if(in_array("play-doh", $this->stomach)) { echo "I have play-doh in my stomach<br />"; } else { echo "I don't have any play-doh in my stomach<br />"; } } } // Extend the abstract class class Cat extends Animal { public function climbTree() { echo "I'm climbing a tree<br />"; } } echo "<p><b>Dog actions</b><p>"; $myDog = new Dog(); $myDog->eat("dog food"); $myDog->eat("grass"); $myDog->catchFrisbee("red aerobie"); $myDog->eat("play-doh"); $myDog->doIHavePlaydohInMyStomach(); $myDog->poop(); $myDog->eat("a bone"); $myDog->makeSound("Bark!"); $myDog->doIHavePlaydohInMyStomach(); $myDog->eat("dog food"); $myDog->poop(); echo "<p><b>Cat actions</b><p>"; $myCat = new Cat(); $myCat->eat("fish"); $myCat->eat("cat food"); $myCat->makeSound("Meow"); $myCat->climbTree(); $myCat->poop(); |
As you can see, both Dog and Cat extend the abstract class Animal. The functions in Animal are common between both dogs and cats, which is why they are in the abstract class rather than the Dog or Cat; it allows us to re-use the code without having to duplicate it.
In the abstract class, there’s a class variable that’s “protected.” This is a visibility keyword like public or private. While public means anyone can access it and private means only the containing class can access it, protected means the containing class and any inheriting class can access it. In other words, our Animal class, our Dog class, and our Cat class can all access the $stomach variable because it is protected, but nothing outside these classes can currently access that variable directly. You can see an example of our Dog class accessing the $stomach variable in it’s doIHavePlaydohInMyStomach() function.
You know you want to see what that code outputs. I won’t make you wait in overbearing anxiety any longer:
I ate dog food
I ate grass
I caught the red aerobie
I ate play-doh
I have play-doh in my stomach
Poo contents:
- dog food
- grass
- play-doh
I ate a bone
Bark!
I don’t have any play-doh in my stomach
I ate dog food
Poo contents:
- a bone
- dog food
Cat actions
I ate fish
I ate cat food
Meow
I’m climbing a tree
Poo contents:
- fish
- cat food
Before explaining what I call interface-esque abstraction, I need to mention something about abstract classes that’s rather insignificant but insightful none-the-less. Every class we’ve looked at so far can be an abstract class (can be extended by other classes) as it currently is–without any changes. Code-wise, we did not have to declare our Animal class as abstract in order to extend it from other classes. In fact, if we hadn’t declared the class as abstract, we still could have used it as an abstract class by extending it from our Dog class (like we did) or we could’ve instantiated it like a regular class. On the other hand, because we did declare the class as abstract, we now cannot instantiate it directly (we’d get an error saying “Cannot instantiate abstract class.”) As a rule of thumb, if I know a class will be extended at all, I’ll usually declare the class as abstract. I have yet to find a need for a class that needs to be both extended as an abstract class and also needs to be directly instantiated. I’m not saying there’s not a case for it, I just haven’t personally found one and I like the clarity of declaring classes I intend to be abstract as abstract. With that in mind, if you’re going to use what I call interface-esque abstraction (covered in the next section), it is at that point in time that you are actually required to declare your class abstract. Got it? Got it. If I completely lost you on this last paragraph, just brush it under the rug; it’s not a big deal. Declare your abstract classes as abstract like we did in our last example and you’ll be good. Onward.
Interface-esque abstraction
While abstract classes provide the functionality just mentioned, it can also be a type of interface. So, beside providing functions and variables to extending classes, it can also force extending classes to have certain functions in them, just like an interface does. Here’s how:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | abstract class Animal { protected $stomach; abstract protected function shedHair(); public function eat($food) { $this->stomach[] = $food; echo "I ate $food<br />"; } public function poop() { echo "<p>Poo contents:"; echo "<ul>"; foreach($this->stomach as $poo_nugget) { echo "<li>$poo_nugget</li>"; } echo "</ul>"; unset($this->stomach); } public function makeSound($sound) { echo "$sound<br />"; } } |
In this example, any class that extends Animal must have a shedHair() function defined.
PHP’s documentation explains it well: “When inheriting from an abstract class, all methods marked abstract in the parent’s class declaration must be defined by the child; additionally, these methods must be defined with the same (or a less restricted) visibility. For example, if the abstract method is defined as protected, the function implementation must be defined as either protected or public, but not private.”
Overriding inherited functions
At times, we’ll want to override a function that is inherited from an abstract class. That’s no problem. Let’s take a gander at another example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | abstract class Animal { protected $stomach; public function eat($food) { $this->stomach[] = $food; echo "I ate $food<br />"; } public function poop() { echo "<p>Poo contents:"; echo "<ul>"; foreach($this->stomach as $poo_nugget) { echo "<li>$poo_nugget</li>"; } echo "</ul>"; unset($this->stomach); } public function makeSound($sound) { echo "$sound<br />"; } } // Extend the abstract class class Dog extends Animal { public function eat() { parent::eat("bacon bits"); } public function catchFrisbee($frisbee) { echo "I caught the $frisbee<br />"; } public function doIHavePlaydohInMyStomach() { if(in_array("play-doh", $this->stomach)) { echo "I have play-doh in my stomach<br />"; } else { echo "I don't have any play-doh in my stomach<br />"; } } } $myDog = new Dog(); $myDog->eat(); |
In the example, our Animal class is exactly the same as the first Animal class we cooked up a while back. What’s different is that we added the eat() function to our Dog class and by doing so we overrode the eat() function in the parent Animal class. We learn a couple other things from this example as well…
First, notice that the eat() function in our parent class (Animal) accepts one argument ($food). On the other hand, the overriding eat() function in our child class (Dog) accepts no arguments. In other words, the overriding function does not need to accept the same number and/or types of arguments as the function it’s overriding.
Second, notice the line that says parent::eat(”bacon bits”);. The parent:: operator, as you may have assumed, refers to the parent class–in this case, Animal. While I wasn’t required to call the parent’s eat() function at all, I just wanted to show you how to do it in case you came across the need in the future. In the same way, you could’ve called the parent’s poop() or makeSound() functions if you had the hankerin’.

Nice site you have here Aaron! There’s just one small point that I think you might be interested in. You mentioned that PHP doesn’t support object overloading, but it actually does. Several special methods can be set up on objects, including __get, __set, and __call. This may not be the same implementation as is found in other languages, but it is overloading. Check it out.