Blog/ Inheritance

By neerav.mehta Thu, 08/20/2015 - 07:11 Comments

In Object Oriented PHP Programming post, you learned how to create classes and objects and how to use them in your code. In this post, we'll dig a little deeper and introduce the concept of inheritance. You will understand when to use it and the benefits associated with its use. You will understand Method Overriding in PHP. You will also learn when to set the visibility of properties and methods to public, protected or private. We'll continue where we left off in the previous post. So if you haven't read that post yet, please go ahead and read it.

In Object-Oriented PHP post, we created a Vehicle class and initialized two objects: $myTruck and $yourCar. For this example, let's assume that your car is Honda Accord and my truck is Chevrolet RAM. Honda Accord has a trunk while Chevy RAM doesn't. To use the trunk, Honda Accord needs two methods: putInTruck() and getFromTrunk(). If we add these methods to Vehicle.php, they will be available for my truck as well, which doesn't have a trunk. We need to make sure that only your car supports the above two methods and not my truck. One easy way to deal with this is to delete the Vehicle class and use two separate classes: HondaAccord and ChevyRam.

<?php

/**
 * HondaAccord class.
 */
class HondaAccord {

  // Color of the vehicle.
  var $color;

  // Miles driven.
  var $milesDriven;

  // Warranty available.
  var $warranty;

  // Stuff in the trunk.
  var $stuff;

  /**
   * Default constructor.
   *
   * @param string $color
   *  Color of the vehicle.
   * @param int $milesDriven
   *  Number of miles driven already.
   * @param int $warrantyProvided
   *   Number of miles of warranty provided with the vehicle.
   */
  function __construct($color, $milesDriven = 0, $warranty = 100000) {
    $this->color = $color;
    $this->milesDriven = $milesDriven;
    $this->warranty = $warranty;
  }

  /**
   * Returns miles of warranty left on the vehicle.
   *
   * @return int
   *   Miles of warrnty left on the vehicle.
   */
  function getWarrantyLeft() {
    if ($this->warranty - $this->milesDriven > 0) {
      return ($this->warranty - $this->milesDriven);
    }

    return 0;
  }

  /**
   * Put stuff in trunk.
   *
   * @param mixed $stuff
   *   Stuff to be put in the trunk.
   */
  function putInTrunk($stuff) {
    $this->stuff = $stuff;
  }

  /**
   * Take stuff out from the trunk.
   *
   * @return mixed
   *   Stuff returned from the trunk.
   */
  function takeFromTrunk() {
    $stuff = $this->stuff;
    unset($this->stuff);
    return $stuff;
  }
}

/**
 * ChevyRam class.
 */
class ChevyRam {

  // Color of the vehicle.
  var $color;

  // Miles driven.
  var $milesDriven;

  // Warranty available.
  var $warranty;

  /**
   * Default constructor.
   *
   * @param string $color
   *  Color of the vehicle.
   * @param int $milesDriven
   *  Number of miles driven already.
   * @param int $warrantyProvided
   *   Number of miles of warranty provided with the vehicle.
   */
  function __construct($color, $milesDriven = 0, $warranty = 100000) {
    $this->color = $color;
    $this->milesDriven = $milesDriven;
    $this->warranty = $warranty;
  }

  /**
   * Returns miles of warranty left on the vehicle.
   *
   * @return int
   *   Miles of warrnty left on the vehicle.
   */
  function getWarrantyLeft() {
    if ($this->warranty - $this->milesDriven > 0) {
      return ($this->warranty - $this->milesDriven);
    }

    return 0;
  }
}

$yourCar = new HondaAccord('white');
$myTruck = new ChevyRam('red', 25000, 200000);

echo 'Warranty left on your car: ' . $yourCar->getWarrantyLeft() . "\n";
echo 'Warranty left on my truck: ' . $myTruck->getWarrantyLeft() . "\n";

$stuff = 'My Stuff';
$yourCar->putInTrunk($stuff);
echo "What is in Honda Accord's trunk: " . $yourCar->takeFromTrunk() . "\n";

?>

As you can see above, ChevyRam class is identical to the old Vehicle class. HondaAccord class is similar to Vehicle class except that we have added a property $stuff and two methods putInTrunk() and takeFromTrunk() in it. This property and the methods do not exist in ChevyRam class since it does not have a trunk. If you execute the above file, you will get the following result:

$ php Vehicle.php
Warranty left on your car: 100000
Warranty left on my truck: 175000
What is in Honda Accord's trunk: My Stuff

The above code will work absolutely fine. The problem comes in maintaining it. Suppose you want to change how miles of warranty left on the vehicle is being calculated. You will have to update the method getWarrantyLeft() in both the classes, HondaAccord and ChevyRam. In future, you may want to add another method that applies to all the vehicles such as getNumberOfTires(), which returns the number of tires the vehicle runs on. Again you will need to add this method in both the classes: HondaAccord and ChevyRam. The problem is compounded when you have not just two but five different vehicles of different types. In order to reduce the complications in managing multiple classes, which are similar but not exactly the same, we can use Inheritance.

At a very basic level, inheritance is the definition of parent-child relationship between two classes. In our example, both Honda Accord and Chevy Ram are vehicles. So we can define HondaAccord and ChevyRam classes as children of the Vehicle class. We use extends keyword to define a child class from a parent class:

<?php

/**
 * Vehicle class.
 */
class Vehicle {

  // Color of the vehicle.
  var $color;

  // Miles driven.
  var $milesDriven;

  // Warranty available.
  var $warranty;

  /**
   * Default constructor.
   *
   * @param string $color
   *  Color of the vehicle.
   * @param int $milesDriven
   *  Number of miles driven already.
   * @param int $warrantyProvided
   *   Number of miles of warranty provided with the vehicle.
   */
  function __construct($color, $milesDriven = 0, $warranty = 100000) {
    $this->color = $color;
    $this->milesDriven = $milesDriven;
    $this->warranty = $warranty;
  }

  /**
   * Returns miles of warranty left on the vehicle.
   *
   * @return int
   *   Miles of warrnty left on the vehicle.
   */
  function getWarrantyLeft() {
    if ($this->warranty - $this->milesDriven > 0) {
      return ($this->warranty - $this->milesDriven);
    }

    return 0;
  }
}

/**
 * HondaAccord class.
 */
class HondaAccord extends Vehicle {

  // Stuff in the trunk.
  var $stuff;

  /**
   * Put stuff in trunk.
   *
   * @param mixed $stuff
   *   Stuff to be put in the trunk.
   */
  function putInTrunk($stuff) {
    $this->stuff = $stuff;
  }

  /**
   * Take stuff out from the trunk.
   *
   * @return mixed
   *   Stuff returned from the trunk.
   */
  function takeFromTrunk() {
    $stuff = $this->stuff;
    unset($this->stuff);
    return $stuff;
  }
}

/**
 * ChevyRam class.
 */
class ChevyRam extends Vehicle {

}

$yourCar = new HondaAccord('white');
$myTruck = new ChevyRam('red', 25000, 200000);

echo 'Warranty left on your car: ' . $yourCar->getWarrantyLeft() . "\n";
echo 'Warranty left on my truck: ' . $myTruck->getWarrantyLeft() . "\n";

$stuff = 'My Stuff';
$yourCar->putInTrunk($stuff);
echo "What is in Honda Accord's trunk: " . $yourCar->takeFromTrunk() . "\n";

?>

On executing the Vehicle.php file, we get the following output:

$ php Vehicle.php
Warranty left on your car: 100000
Warranty left on my truck: 175000
What is in Honda Accord's trunk: My Stuff

This is identical to the output obtained earlier. You might be wondering that we haven't defined getWarrantyLeft() method in either HondaAccord or ChevyRam classes, then how is PHP returning the correct answer? That's where the concept of inheritance is useful. By default, a child class will inherit all the properties and methods of its parent class. In our case, both HondaAccord and ChevyRam classes will inherit the properties $color, $milesDriven and $warranty and the method getWarrantyLeft() from their parent class Vehicle. As a result, we don't need to define them in the child classes again.

Method Overriding

Let's take a fictitious scenario. Suppose that Chevrolet announced that starting today, they are doubling the warranty left on the truck. So if the warranty left on the truck is 0 miles, then unfortunately you will still have 0 miles of warranty left. But if waranty left on the truck is 175000 miles, as my truck does, then it will effecitve double to 350000 miles. Calculation of miles of warranty left on Chevy Ram will change now. But the calculation of miles of warranty left on Honda Accord and every other car will still remain the same. How do we change the method definition on one of the child classes but not all the others? We use Method Overriding. We redefine getWarrantyLeft() in the child class, i.e. ChevyRam. Code in Vehicle.php will now be:

<?php

/**
 * Vehicle class.
 */
class Vehicle {

  // Color of the vehicle.
  var $color;

  // Miles driven.
  var $milesDriven;

  // Warranty available.
  var $warranty;

  /**
   * Default constructor.
   *
   * @param string $color
   *  Color of the vehicle.
   * @param int $milesDriven
   *  Number of miles driven already.
   * @param int $warrantyProvided
   *   Number of miles of warranty provided with the vehicle.
   */
  function __construct($color, $milesDriven = 0, $warranty = 100000) {
    $this->color = $color;
    $this->milesDriven = $milesDriven;
    $this->warranty = $warranty;
  }

  /**
   * Returns miles of warranty left on the vehicle.
   *
   * @return int
   *   Miles of warrnty left on the vehicle.
   */
  function getWarrantyLeft() {
    if ($this->warranty - $this->milesDriven > 0) {
      return ($this->warranty - $this->milesDriven);
    }

    return 0;
  }
}

/**
 * HondaAccord class.
 */
class HondaAccord extends Vehicle {

  // Stuff in the trunk.
  var $stuff;

  /**
   * Put stuff in trunk.
   *
   * @param mixed $stuff
   *   Stuff to be put in the trunk.
   */
  function putInTrunk($stuff) {
    $this->stuff = $stuff;
  }

  /**
   * Take stuff out from the trunk.
   *
   * @return mixed
   *   Stuff returned from the trunk.
   */
  function takeFromTrunk() {
    $stuff = $this->stuff;
    unset($this->stuff);
    return $stuff;
  }
}

/**
 * ChevyRam class.
 */
class ChevyRam extends Vehicle {

  /**
   * Returns miles of warranty left on the vehicle.
   *
   * @return int
   *   Miles of warrnty left on the vehicle.
   */
  function getWarrantyLeft() {
    if ($this->warranty - $this->milesDriven > 0) {
      return (2 * ($this->warranty - $this->milesDriven));
    }

    return 0;
  }

}

$yourCar = new HondaAccord('white');
$myTruck = new ChevyRam('red', 25000, 200000);

echo 'Warranty left on your car: ' . $yourCar->getWarrantyLeft() . "\n";
echo 'Warranty left on my truck: ' . $myTruck->getWarrantyLeft() . "\n";

$stuff = 'My Stuff';
$yourCar->putInTrunk($stuff);
echo "What is in Honda Accord's trunk: " . $yourCar->takeFromTrunk() . "\n";

?>

On executing the file, you will see the output:

$ php Vehicle.php
Warranty left on your car: 100000
Warranty left on my truck: 350000
What is in Honda Accord's trunk: My Stuff

You will notice that the miles of warranty left on Chevy Ram reflects the new calculation method but that on Honda Accord reflects the old calculation method. That's because ChevyRam class redefines (overrides) Vehicle class' method getWarrantyLeft() but HondaAccord class doesn't. That's why when you call getWarrantyLeft() method on ChevyRam object, the method defined in ChevyRam class will get executed and when you call the same method on HondaAccord object, the method defined in Vehicle class will get executed.

Visibility

One thing that we have completely omitted till now from the discussion of object oriented programming in PHP is the concept of visibility. Every property and method in a class can be defined to have one of three different types of visibilities: public, protected or private. If visibility is set to public, then that property or method can be accessed from anywhere outside the class. If visibility is set to protected, then that property or method can be accessed from the class itself or any of its child classes only. If visibility is set to private, then that property or method can be accessed from the class itself and nowhere else. If you haven't defined visibility explicitly for any property or method, then it's assumed to be public.

Why do we need Visibility? Why not make everything public?

For better separation of concern and security. Let's take an example. If you live in the US, you must be aware that it is illegal to tamper with the vehicle's odometer, which records the number of miles that the vehicle has driven. This means that $milesDriven should never go down and can only increase. But if visibility of $milesDriven is set to public, then any code outside the Vehicle class can easily change it back to 0. In other words, I could write the following code at the end of Vehicle.php file and set $milesDriven to 0 for my truck. This in turn increases the number of miles of warranty left on it.

$myTruck->milesDriven = 0;
echo 'Warranty left on my truck after setting miles driven to 0: ' . $myTruck->getWarrantyLeft() . "\n";

If you execute Vehicle.php file, you will get the following output:

$ php Vehicle.php
Warranty left on your car: 100000
Warranty left on my truck: 350000
What is in Honda Accord's trunk: My Stuff
Warranty left on my truck after setting miles driven to 0: 400000

You can see that warranty left on my truck increased to 400000 miles. Setting $milesDriven to 0 shouldn't be allowed. To prevent such an operation, let's set visibility of $milesDriven to be protected. Just to make the code more readable, let's explicitly define visibility of all other properties and methods to be public.

<?php

/**
 * Vehicle class.
 */
class Vehicle {

  // Color of the vehicle.
  public $color;

  // Miles driven.
  protected $milesDriven;

  // Warranty available.
  public $warranty;

  /**
   * Default constructor.
   *
   * @param string $color
   *  Color of the vehicle.
   * @param int $milesDriven
   *  Number of miles driven already.
   * @param int $warrantyProvided
   *   Number of miles of warranty provided with the vehicle.
   */
  function __construct($color, $milesDriven = 0, $warranty = 100000) {
    $this->color = $color;
    $this->milesDriven = $milesDriven;
    $this->warranty = $warranty;
  }

  /**
   * Returns miles of warranty left on the vehicle.
   *
   * @return int
   *   Miles of warrnty left on the vehicle.
   */
  public function getWarrantyLeft() {
    if ($this->warranty - $this->milesDriven > 0) {
      return ($this->warranty - $this->milesDriven);
    }

    return 0;
  }
}

/**
 * HondaAccord class.
 */
class HondaAccord extends Vehicle {

  // Stuff in the trunk.
  public $stuff;

  /**
   * Put stuff in trunk.
   *
   * @param mixed $stuff
   *   Stuff to be put in the trunk.
   */
  public function putInTrunk($stuff) {
    $this->stuff = $stuff;
  }

  /**
   * Take stuff out from the trunk.
   *
   * @return mixed
   *   Stuff returned from the trunk.
   */
  public function takeFromTrunk() {
    $stuff = $this->stuff;
    unset($this->stuff);
    return $stuff;
  }
}

/**
 * ChevyRam class.
 */
class ChevyRam extends Vehicle {

  /**
   * Returns miles of warranty left on the vehicle.
   *
   * @return int
   *   Miles of warrnty left on the vehicle.
   */
  public function getWarrantyLeft() {
    if ($this->warranty - $this->milesDriven > 0) {
      return (2 * ($this->warranty - $this->milesDriven));
    }

    return 0;
  }

}

$yourCar = new HondaAccord('white');
$myTruck = new ChevyRam('red', 25000, 200000);

echo 'Warranty left on your car: ' . $yourCar->getWarrantyLeft() . "\n";
echo 'Warranty left on my truck: ' . $myTruck->getWarrantyLeft() . "\n";

$stuff = 'My Stuff';
$yourCar->putInTrunk($stuff);
echo "What is in Honda Accord's trunk: " . $yourCar->takeFromTrunk() . "\n";

$myTruck->milesDriven = 0;
echo 'Warranty left on my truck after setting miles driven to 0: ' . $myTruck->getWarrantyLeft() . "\n";

?>

On executing the file, you will see the output:

Now $milesDriven can only be accessed by the class Vehicle and its child classes: ChevyRam and HondaAccord. On executing the Vehicle.php file, you will see the following error:

$ php Vehicle.php
Warranty left on your car: 100000
Warranty left on my truck: 350000
What is in Honda Accord's trunk: My Stuff
PHP Fatal error:  Cannot access protected property ChevyRam::$milesDriven in Vehicle.php on line 110

PHP is giving an error because we are accessing the property $milesDriven by code that it outside the Vehicle class or any of its child classes. This is a good thing because now no other code can decrease the value of $milesDriven variable even by mistake. Next we need to add a method to increase $milesDriven variable. Let's call that method drive().

/**
 * Drive the vehicle. This will add to miles driven.
 *
 * @param int $miles
 *   Number of miles driven in the current trip.
 */
public function drive($miles) {
  if ($miles > 0) {
    $this->milesDriven += $miles;
  }
}

In drive() method, we are first checking if the input is greater than 0 and if it is, then we add the input to $milesDriven variable. This way we ensure that $milesDriven will never decrease. There is only one problem now. What if in future, you add one more child of Vehicle class and then decrease $milesDriven from that child class? PHP will not prevent it because visibility of $milesDriven is set to protected. This means that any child class will be able to access that property and change its value. To make the code even more idiot-proof, let's set visibility of $milesDriven to private and add a new public method getMilesDriven() that child classes can use to calcuate the miles of warranty left. Now even child classes will not be able to decrease the value of the property $milesDriven but they can call the method getMilesDriven() to get its value. Here is how the updated Vehicle.php file looks:

<?php

/**
 * Vehicle class.
 */
class Vehicle {

  // Color of the vehicle.
  public $color;

  // Miles driven.
  private $milesDriven;

  // Warranty available.
  public $warranty;

  /**
   * Default constructor.
   *
   * @param string $color
   *  Color of the vehicle.
   * @param int $milesDriven
   *  Number of miles driven already.
   * @param int $warrantyProvided
   *   Number of miles of warranty provided with the vehicle.
   */
  function __construct($color, $milesDriven = 0, $warranty = 100000) {
    $this->color = $color;
    $this->milesDriven = $milesDriven;
    $this->warranty = $warranty;
  }

  /**
   * Returns miles of warranty left on the vehicle.
   *
   * @return int
   *   Miles of warrnty left on the vehicle.
   */
  public function getWarrantyLeft() {
    if ($this->warranty - $this->getMilesDriven() > 0) {
      return ($this->warranty - $this->getMilesDriven());
    }

    return 0;
  }

  /**
   * Drive the vehicle. This will add to miles driven.
   *
   * @param int $miles
   *   Number of miles driven in the current trip.
   */
  public function drive($miles) {
    if ($miles > 0) {
      $this->milesDriven += $miles;
    }
  }

  /**
   * Returns the number of miles driven in total.
   *
   * @param int
   *   Total number of miles driven.
   */
  public function getMilesDriven() {
    return $this->milesDriven;
  }
}

/**
 * HondaAccord class.
 */
class HondaAccord extends Vehicle {

  // Stuff in the trunk.
  public $stuff;

  /**
   * Put stuff in trunk.
   *
   * @param mixed $stuff
   *   Stuff to be put in the trunk.
   */
  public function putInTrunk($stuff) {
    $this->stuff = $stuff;
  }

  /**
   * Take stuff out from the trunk.
   *
   * @return mixed
   *   Stuff returned from the trunk.
   */
  public function takeFromTrunk() {
    $stuff = $this->stuff;
    unset($this->stuff);
    return $stuff;
  }
}

/**
 * ChevyRam class.
 */
class ChevyRam extends Vehicle {

  /**
   * Returns miles of warranty left on the vehicle.
   *
   * @return int
   *   Miles of warrnty left on the vehicle.
   */
  public function getWarrantyLeft() {
    if ($this->warranty - $this->getMilesDriven() > 0) {
      return (2 * ($this->warranty - $this->getMilesDriven()));
    }

    return 0;
  }

}

$yourCar = new HondaAccord('white');
$myTruck = new ChevyRam('red', 25000, 200000);

echo 'Warranty left on your car: ' . $yourCar->getWarrantyLeft() . "\n";
echo 'Warranty left on my truck: ' . $myTruck->getWarrantyLeft() . "\n";

$stuff = 'My Stuff';
$yourCar->putInTrunk($stuff);
echo "What is in Honda Accord's trunk: " . $yourCar->takeFromTrunk() . "\n";

// Drive the truck for 25000 more miles.
$myTruck->drive(25000);
echo 'Warranty left on my truck after for 25000 more miles: ' . $myTruck->getWarrantyLeft() . "\n";

?>

On executing the file Vehicle.php, you will see the output:

$ php Vehicle.php
Warranty left on your car: 100000
Warranty left on my truck: 350000
What is in Honda Accord's trunk: My Stuff
Warranty left on my truck after driving for 25000 more miles: 300000

As expected, miles of warranty left on Chevy Ram reduced to 300000 miles.

Awesome!! You now know the concepts of Inheritance, Method Overriding and Visibility. In the next post, we'll introduce the concept of static and abstract properties and functions.

If there is anything in this post that you felt difficult to understand, please write it in the comments below and I'll explain it again in simpler terms. If you understood everything, please leave a comment so that I know that you enjoyed it.

Ready to get Started?