The power of OOP lies in its three main characteristics: inheritance, encapsulation, and polymorphism.
Inheritance in OOP allows you to create new classes that inherit an existing class's behaviour (methods) and attributes (properties). Let's consider the following example. Assume that we have a class called Fruit
. It is a generalized type of class for different fruits, and its common attributes are color and weight. In OOP, we can subclass Fruit
to create new classes Apple
and Banana
. Both these classes (being subclasses of Fruit) will have the same properties: weight
and color
. (Note we are speaking about properties as such, not about their values). An apple can have a green color, while a Banana
can have a yellow color. But any code that interacts with Apple
or Banana
class instances does not need to know what kind of fruit it is communicating with.
Let's put this example into code:
class Fruit { public $color; public $weight; } class Apple extends Fruit { function __construct() { $this->color = 'green'; $this->weight = 200; } } class Banana extends Fruit { function __construct() { $this->color = 'yellow'; $this->weight = 250; } } $a[] = new Apple(); $a[] = new Banana(); foreach($a as $f) { echo $f->color, "\t", $f->weight, "\n"; }
As you can see, in this small application we have one Apple
object and one Banana
object. We iterate over them in a loop, but access their properties regardless of their type, since both classes use the same property names. But these properties carry different values for each fruit.
Inheritance also allows to extend or completely override the behavior of the parent classes. Let's assume that our Fruit
class has one more characteristic—price per kg. It also has a new method—getPrice() that just multiplies the weight (which we have in grams) by the price:
class Fruit { public $color; public $weight; public $price; function getPrice() { return $this->weight / 1000 * $this->price; } }
Now we can use this method in the subclasses:
class Apple extends Fruit { function __construct() { $this->color = 'green'; $this->weight = 200; $this->price = 2; } } class Banana extends Fruit { function __construct() { $this->color = 'yellow'; $this->weight = 250; $this->price = 3; } } $a[] = new Apple(); $a[] = new Banana(); foreach($a as $f) { echo $f->getPrice(), "\n"; }
Next, we will assume that the Banana
class has another method for calculating price so that a discount is applied:
class Banana extends Fruit { function __construct() { $this->color = 'yellow'; $this->weight = 250; $this->price = 3; } function getPrice() { return $this->weight / 1000 * $this->price * 0.9; } }
As you can see, we changed the method in the Banana
class so that the code calling the Banana
class's implementation of the getPrice()
method will get discounted price, while the Apple
class's getPrice()
method returns full price.
On the other hand, we could reuse the Fruit
class's implementation of the getPrice()
method in the Banana
class (so that we don't have to duplicate the code contained in the base class):
function getPrice() { return parent::getPrice() * 0.9; }
Encapsulation (sometimes called information hiding) is a more theoretical concept. It involves defining methods in a class in such a way that we hide the implementation details from the client code. We have already seen this when we redefined the price calculation in the Banana
class. From the application's point of view, nothing changed: we still call the getPrice()
method, but we don't know how this calculation is performed.
In other words, classes are accessible through their methods, which have the same names so that, even if the code behind these names changes, the names themselves do not change. This ensures that existing code does not need to be changed to work with new versions of methods.
We can do more to hide implementation details from client code, PHP5, like other object-oriented languages, supports visibility modifiers for methods and properties. For example, we could add a private property, which will be hidden from the rest of the application, to the Banana
class:
class Banana extends Fruit
{
private $mySecretProperty;
function __construct()
{
$this->color = 'yellow';
$this->weight = 250;
$this->price = 3;
}
function getPrice()
{
return parent::getPrice() * 0.9;
}
}
The $mySecretProperty
property is only accessible (or visible) in the Banana
class; an attempt to access it from outside the Banana
class's methods would trigger a run-time error. (In a compiled language, this would lead to a compilation error.)
In PHP5, there exist two more modifiers: public (which we have already used), and protected. Public method or property is accessible from all the application, while protected is accessible inside the class and its subclasses only.
Polymorphism is a feature of OOP that allows us to write code that will work with objects belonging to different classes provided that these classes have the same base class. We have already seen polymorphism in action in the above example when we were accessing properties and methods of different objects using their names but returning different values and taking different actions.
The subclasses implement all the properties and methods belonging to the base class, and all future subclasses of the base class are guaranteed to implement these properties and methods so that the existing code can work even with subclasses which do not yet exist.
PHP5 supports interfaces. An interface is a construct that describes certain behaviour in different classes and class hierarchies. For example, let’s consider a Tradeable
interface that has a single method, isImported():
interface Tradeable { public isImported(); }
Now, we can declare in the definition of the Fruit
class that it implements the Tradeable interface:
class Fruit implements Tradeable
{
public $color;
public $weight;
public $price;
function getPrice()
{
return $this->weight / 1000 * $this->price;
}
function isImported()
{
return false;
}
}
We have made Fruit
objects and all objects belonging to its subclasses (Apple and Banans)
non-imported by default. Now we can make bananas imported while leaving apples domestic:
class Banana extends Fruit { function __construct() { $this->color = 'yellow'; $this->weight = 250; $this->price = 3; } function getPrice() { return parent::getPrice() * 0.9; } function isImported() { return true; } }
Next we will create an imaginary Car
class that implements the Tradeable
interface:
class Car implements Tradeable { public $year; public $make; public $model; function isImported() { return true; } }
Note that Car
does not extend Fruit
, but it still has the isImported()
method. Now we can call this method from the application:
$a[] = new Apple(); $a[] = new Banana(); $a[] = new Car(); foreach($a as $item) { echo $item->isImported(); }
This small example shows how objects from different class hierarchies can be treated in the same way by giving them a common interface. By doing this, objects that normally have quite different meanings can be manipulated in the same way, and this makes them polymorphic.