×
×

Interface

If you are new to object-oriented programming, you might be confused about what an Interface is and how to use it. Read this post to clear that confusion.

Interfaces solve two problems:

  1. They specify the methods that a class implementing an interface has to define. This makes for flexible architecture by facilitating swapping of one class with another without delving into implementation details.
  2. They help child classes inherit methods from multiple parent classes. But you should never use it for this purpose. You will learn why and what to do instead.

Make architecture flexible and modular

Let's start with the example where we left off in the post on inheritance in PHP.  We have two child classes: HondaAccord and ChevyRam, both extending the parent class Vehicle. All the methods and properties defined in the parent class are inherited by the child class. Child class can override these inherited properties and methods and add more of its own if it needs to. We all know that a person can buy and travel in his vehicle, be it Honda Accord or Chevy Ram. Let's create such a Person class in Person.php file.

<?php

require_once 'Vehicle.php';

/**
 * Person class.
 */
class Person {

  // Vehicle
  private $vehicle;

  /**
   * Buy a vehicle.
   *
   * param Vehicle $vehicle
   *   Vehicle to buy.
   */
  public function buy($vehicle) {
    $this->vehicle = $vehicle;
  }

  /**
   * Let the person travel in his vehicle.
   *
   * @param int $miles
   *   Number of miles that the person travels.
   */
  public function travel($miles) {
    $this->vehicle->drive($miles);
  }

  /**
   * Returns the number of miles that the person has traveled.
   *
   * @return int
   *   Number of miles that the person has traveled.
   */
  public function getDistanceTraveled() {
    return $this->vehicle->getMilesDriven();
  }
}

$you = new Person();
$yourCar = new HondaAccord('white');
$you->buy($yourCar);
$you->travel(2000);
echo 'You have traveled for ' . $you->getDistanceTraveled() . ' miles in your Honda Accord.';

?>

In the above code, I am creating a person $you and a HondaAccord object $yourCar. Then I make you buy the car and let you travel in it for 2000 miles. At the end, I am asking for the number of miles traveled. Pretty simple! On executing the file Person.php, I get the following output:

$ php Person.php
You have traveled for 2000 miles in your Honda Accord.

In the above code, the only methods of the Vehicle class that are being used are drive() and getMilesDriven(). So if we replace the Vehicle object with any other object which has these two methods, and then let the user buy that object, the code will work without any problem. An example could be a bullock cart. Based on how we have described our Vehicle class, with warranty and color, bullock cart is definitely not a vehicle. But a person can still buy it, travel in it and then get the number of miles traveled in it. So for the Person class, bullock cart is a practical replacement for a vehicle. Now how does the Person class specify that it can buy and travel in any object that defines two methods: drive() and getMilesDriven()? That's where an interface comes in handy.

Using an interface, we can define the methods that any class implementing it should have. The implementing class can have more methods but it should at least have the methods that the interface defines otherwise PHP will throw an error. In our case, we can create VehicleInterface which has only two methods: drive() and getMilesDriven(). Vehicle and BullockCart classes will implement this interface, and Person class can buy and drive the class that implements it. Here is how the VehicleInterface looks in VehicleInterface.php file.

<?php

/**
 * Interface VehicleInterface
 */
interface VehicleInterface {

  /**
   * Drive the vehicle. This will add to the miles driven.
   *
   * @param int $miles
   *   Number of miles driven.
   */
  public function drive($miles);

  /**
   * Return the number of miles driven.
   *
   * @return int
   *   Number of miles driven.
   */
  public function getMilesDriven();
}

?>

Class Vehicle.php now implements VehicleInterface.

<?php

require_once 'VehicleInterface.php';

/**
 * Vehicle class.
 */
class Vehicle implements VehicleInterface {

  // 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;
  }
}

?>

We also have the new BullockCart class that implements the VehicleInterface.

<?php

require_once 'VehicleInterface.php';

/**
 * BullockCart class.
 */
class BullockCart implements VehicleInterface {

  // Miles driven.
  private $miles;

  /**
   * Default constructor.
   */
  function __construct() {
    $this->miles = 0;
  }

  /**
   * Drive the bullock cart.
   *
   * @param int $miles
   *   Miles driven.
   */
  public function drive($miles) {
    $this->miles += $miles;
  }

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

?>

Finally we have the Person class that uses VehicleInterface instead of Vehicle. As a result, any object that implements VehicleInterface can be used with Person class without breaking the code.

<?php

require_once 'Vehicle.php';
require_once 'BullockCart.php';

/**
 * Person class.
 */
class Person {

  // VehicleInterface
  public $vehicle;

  /**
   * Buy.
   *
   * @param Vehicle $vehicle
   *   Vehicle to buy.
   */
  public function buy(VehicleInterface $vehicle) {
    $this->vehicle = $vehicle;
  }

  /**
   * Let the person travel in his vehicle.
   *
   * param int $miles
   *   Number of miles that the person travels.
   */
  public function travel($miles) {
    $this->vehicle->drive($miles);
  }

  /**
   * Returns the number of miles that the person has traveled.
   *
   * @return int
   *   Number of miles that the person has traveled.
   */
  public function getDistanceTraveled() {
    return $this->vehicle->getMilesDriven();
  }
}

$you = new Person();
$yourCar = new HondaAccord('white');
$you->buy($yourCar);
$you->travel(2000);
echo "You have traveled for " . $you->getDistanceTraveled() . " miles in your Honda Accord.\n";

$me = new Person();
$bullockCart = new BullockCart();
$me->buy($bullockCart);
$me->travel(5000);
echo "I have traveled for " . $me->getDistanceTraveled() . " miles in my bullock cart.\n";

You can see in lines 45-55 above that I bought a bullock cart and traveled in it for 5000 miles (poor bullock!) using almost the same code as you bought a Honda Accord and traveled in it for 2000 miles. Person class, as a result, is no longer dependent on the Vehicle class. In fact, it can work with any class that implements the VehicleInterface. This adds a lot of flexibility to our code and makes the architecture modular. Interfaces, combined with Dependency Injection, make it very easy to replace one class by another without making much changes in the code. In real life, a simple example is interacting with the database. MySQL class, Postgres class, Oracle class can all implement the DatabaseInterface which provides a few methods such as select(), insert(), etc. Now you can write your application without worrying about what database it is using as long as your application code uses the methods specified in the interface. If in future, MySQL in your application gets replaced by Oracle, it's as easy as initializing Oracle class instead of MySQL and using it without any further change in your code.

Circumvent PHP's limitation of child class being able to extend only one parent class

At the top of the post, we had mentioned that the second use of interface is circumventing the limitation in PHP that a child class can inherit only one parent class. As you already know, HondaAccord has two methods: putInTrunk() and takeFromTrunk() that ChevyRam does not. There are other objects which are not vehicles but have a trunk and you can put something in their trunk and take something out. A simple example is any container, such as a suitecase (which is sort of a trunk itself). You can put something in it and later take something out. So theoretically we can define a Container class with a property $stuff and two methods: putInTrunk() and takeFromTrunk(). Then we can make HondaAccord a child of Container class, and it will inherit these methods. But that's the rub. HondaAccord already extends the Vehicle class. Since PHP does not allow a child class to extend two parent classes, it can't extend Container class as well. Instead we can define a ContainerInterface with two methods: putInTrunk() and takeFromTrunk(). Container class will implement these methods and so will HondaAccord class. Once HondaAccord implements ContainerInterface, we can use it anywhere ContainerInterface can be used. Here is how ContainerInterface, Container class and HondaAccord class will look:

<?php

/**
 * Interface ContainerInterface
 */
interface ContainerInterface {

  /**
   * Put something trunk.
   *
   * @param mixed $stuff
   *   Stuff to be put in the trunk.
   */
  public function putInTrunk($stuff);

  /**
   * Take something out from the trunk.
   *
   * @return mixed
   *   Stuff to be taken out from the trunk.
   */
  public function takeFromTrunk();
}

?>
<?php

require_once 'ContainerInterface.php';

/**
 * Class Container
 */
class Container implements ContainerInterface {

  // Stuff
  private $stuff;

  /**
   * Put stuff in the 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 to be taken out from the trunk.
   */
  public function takeFromTrunk() {
    $stuff = $this->stuff;
    unset($this->stuff);
    return $stuff;
  }
}

?>
require_once 'ContainerInterface.php';

/**
 * Class HondaAccord
 */
class HondaAccord extends Vehicle implements ContainerInterface {

  // 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;
  }
}

Note that although in PHP, a child class can extend only one parent class, there is no such limitation on the number of interfaces a class can implement. A class can implement 1, 2 or even 100 interfaces. Once you make a class implement an interface, you can use it anywhere that interface is being used.

If you are of the opinion that the above code looks ugly, then you are correct. After all, Honda Accord and a container are not really related. We are putting them under the same interface just because both have a trunk, which we really shouldn't have to do. That's where Traits come in, which we'll discuss in the next post.

If you learned something from this post, please show your appreciation by commenting below. If you got stuck somewhere or found something difficult to understand, definitely let us know so that we can make that part of the post easier to understand.

Services: 
Drupal Development

Sign up for our weekly newsletter


Add new comment