LearnType hinting for interfaces

Type hinting for interfaces

In the previous tutorial, we learned that type hinting contributes to code organization and improves error messages. In this tutorial, we will learn how to implement type hinting for interfaces.

In the first part of the chapter we will see that the use of type hinting for objects is not always sufficient, and in the second part we will learn how to fix the problem by the use of type hinting for interfaces.

Why type hinting for objects may not be sufficient?

We will base our tutorial on the following imaginary scenario: A manager in a car rental company that rents only BMWs hired a programmer to write a program that calculates the price for a full tank of gas for each and every car that the company owns. In accordance with these demands, the programmer writes a class to which he decides to call Bmw that holds the code for the task. This class has the data about the BMWs including the license plate number, the car model and, most importantly, it has a method that calculates the volume of the fuel tank.

The function that calculates the volume is called calcTankVolume. This method calculates the tank volume by multiplying the base area (the square of the base ribs' length) and the height. The height, as well as the length of the base ribs and the license plate number, is introduced to the class through the constructor.

class Bmw {
 
  protected $model;
  protected $rib;
  protected $height;
   
  // The properties are introduced to the class through the constructor
  public function __construct($model, $rib, $height)
  {
    $this -> model = $model;
    $this -> rib = $rib;
    $this -> height = $height;
  }
 
  // Calculate the tank volume for rectangular tanks
  public function calcTankVolume()
  {
    return $this -> rib * $this -> rib * $this -> height;
  } 
}

Outside of the class, the programmer writes a function to calculate the price for a full tank of gas by multiplying the tank volume (gallons) and the price per gallon. However, since he doesn't want the function to get any argument other than those that belong to the Bmw class, he uses type hinting:

// Type hinting ensures that the function gets only Bmw objects as arguments
function calcTankPrice(Bmw $bmw, $pricePerGalon)
{
    return $bmw -> calcTankVolume() * 0.0043290 * $pricePerGalon . "$";
}

Now, he can easily calculate how much a full tank of gas costs for BMWs. For example, for a car with the license plate number of '62182791', a rib length of 14″ a height of 21″, and using gas priced at 3 dollars per gallon.

$bmw1 = new Bmw('62182791', 14, 21); 
echo calcTankPrice($bmw1, 3) ;

Result:
53.454492$

How to do type hinting for interfaces?

After a short time, the manager decides to introduce to his fleet of cars a brand new Mercedes, but the problem is that the calcTankPrice() function can only perform calculations for BMWs. It turns out that, while BMWs have a rectangular shaped gas tank, Mercedes have a cylindrical shaped gas tank. So, our programmer is summoned once again to write another class that can handle this new task at hand. To this end, our programmer now writes the following class with the name of Mercedes that can calculate the tank volume for cylindrical shaped tanks.

class Mercedes {
  protected $model;
  protected $radius;
  protected $height;
   
  public function __construct($model, $radius, $height)
  {
    $this -> model = $model;
    $this -> radius = $radius;
    $this -> height = $height;
  }
   
  // Calculates the volume of cylinder
  public function calcTankVolume()
  {
    return $this -> radius * $this -> radius * pi() * $this -> height;
  }
}

When our programmer completed the task of writing the Mercedes class, he tried to calculate the price of a full tank of gas for a Mercedes.

$mercedes1 = new Mercedes('12189796', 7, 28); 
echo calcTankPrice($mercedes1, 3);

However, the result wasn't quite what he had expected:

Result:
Catchable fatal error: Argument 1 passed to calcTankPrice() must be an instance of Bmw, instance of Mercedes given

This error message is the result of not passing the right object to the function, since he tried to pass a Mercedes object to the calcTankVolume() function whereas the function can only accept objects that belong to the Bmw class.

First, our programmer attempted to solve the problem by not using any type hinting, but then he understood that a better solution would be the use of type hinting for interfaces. Here I use interface in its wider meaning that includes both abstract classes as well as real interfaces.

Here I use interface in its wider meaning that includes both abstract classes as well as real interfaces.

So, first he created an abstract class with the name of Car that both the Bmw and the Mercedes (and, in fact, any other car model) can inherit from.

abstract class Car {
  protected $model;
  protected $height;
     
  abstract public function calcTankVolume();
}

Then he re-factored the Bmw and Mercedes classes so that they inherit from the Car class:

class Bmw extends Car {
 
  protected $rib;
  
  public function __construct($model, $rib, $height)
  {
    $this -> model = $model;
    $this -> rib = $rib;
    $this -> height = $height;
  }
   
  // Calculates a rectangular tank volume
  public function calcTankVolume()
  {
    return $this -> rib * $this -> rib * $this -> height;
  } 
}
 
 
class Mercedes extends Car {
  protected $radius;
   
  public function __construct($model, $radius, $height)
  {
    $this ->model = $model;
    $this -> radius = $radius;
    $this -> height = $height;
  }
 
  
  // Calculates the volume of cylinders
  public function calcTankVolume()
  {
    return $this -> radius * $this -> radius * pi() * $this -> height;
  }
}

The following diagram can help us to better understand the relation between the Car interface and the concrete classes (Bmw and Mercedes) that implement the interface for different gas tank shapes:

The Car interface and the concrete classes that implement it

Since both the classes inherit from the same interface, he could comfortably type hint the function calcTankPrice() with the Car interface, so that the function can get any object as long as it belongs to this interface.

// Type hinting ensures that the function gets only objects 
// that belong to the Car interface
function calcTankPrice(Car $car, $pricePerGalon)
{
  echo $car -> calcTankVolume() * 0.0043290 * $pricePerGalon . "$";
}

Now, let's see the result when we try to use the function calcTankPrice() on both a Bmw and a Mercedes objects:

$bmw1 = new Bmw('62182791', 14, 21); 
echo calcTankPrice($bmw1, 3);
 
$mercedes1 = new Mercedes('12189796', 7, 28); 
echo calcTankPrice($mercedes1, 3);

Result:
53.454492$
55.977413122858$

Whenever we need to do type hinting to more than one related classes, we should be using interface type hinting.

The message to take home is:

The message to take home from this tutorial is that, whenever we need to do type hinting to more than one related classes, we should be using interface type hinting.

Programming to an interface makes the code much more flexible and ready for changes.

Another very important lesson that the above examples can teach us, is the importance of programming to an interface. When the programmer had made the calcTankPrice function dependent on the Bmw class, he restricted the class use to only one car type. But when he chose to make the function dependent on an interface he allowed the function to be used for any car type, and so made it much more flexible. From this we can conclude that programming to an interface makes the code much more flexible and ready for changes.

Conclusion

In this tutorial, you have learned how to implement type hinting for interfaces in PHP. Click here to practice the subject. In the next tutorial, you will learn about static methods and properties that we use without the need to create an object.

comments powered by Disqus