Conflict when dealing with abstract methods and interfaces

On Saturday 14 August 2010 in Symfony, PHP / AFUP

Yesterday, a colleague at Sensio showed me a very weird error when combining abstract classes, abstract methods, concrete classes and interfaces together. The implementation I'm going to describe in the following lines has already been reported on the PHP's bugs tracker but the core team decided that it wasn't a bug but an expected result. In the PHP world, it's not a bug, but it's "feature"...

The Data Model

Let's use a real life data model as example to illustrate the problem.

The Aircraft abstract class

The following PHP class tries to modelize any kind of aircraft objects like planes, UFOs, parachutes, hang-gliders, zeppelins or spaceships. So, that's why there is an abstract class called Aircraft, which encapsulates generic aircrafts' properties and methods like the getAltitude() method.

<?php
 
// Aircraft.php
abstract class Aircraft {
 
  abstract public function getAltitude();
}

There is nothing special to say unlike the fact that the getAltitude() method has been declared as abstract. Consider it was a decision taken by the programmer.

The ILocalizable interface

Consider the specifications want that only some kind of aircrafts can be localizable when they are in the sky. It's typically the case for civil planes, which transport people from point A to point B on the Earth. On the contrary, it's difficult to locate flying means likes parachutes or UFOs (the truth is out there...).

To gain in flexibility and to take advantage of object oriented programming, the localization API is managed by an interface, called ILocalizable.

<?php
 
// ILocalizable.php
interface ILocalizable {
 
  public function locate();
 
  public function getLongitude();
 
  public function getLatitude();
 
  public function getAltitude();
 
  public function getSpeed();
}

The ILocalizable interface defines the five following methods:

  • locate(), which gives information about the object's position,
  • getLatitude(), which returns the latitude coordinate,
  • getLongitude(), which returns the longitude coordinate,
  • getSpeed(), which returns the object's ground speed,
  • getAltitude(), which returns the object's altitude.

Thanks to this interface, any type of object can become localizable thanks to the same API. For instance, objects like boats, cars, cellphones or even human beings... can be localizable if they conform to the ILocalizable interface.

The important thing to notice is that the getAltitude() method is defined twice. It's defined in both Aircraft abstract class and ILocalizable interface.

The Plane concrete class

The Aircraft class and the ILocalizable interface are ready. Let's create a Plane concrete class that inherits Aircraft and implements the ILocalizable interface.

<?php
 
// Plane.php
 
require_once __DIR__ .'/ILocalizable.php';
require_once __DIR__ .'/Aircraft.php';
 
class Plane extends Aircraft implements ILocalizable {
 
  public function locate() {
 
    return array(
      'Latitude'  => $this->getLatitude(),
      'Longitude' => $this->getLongitude(),
      'Altitude'  => $this->getAltitude(),
      'Speed'     => $this->getSpeed()
    );
  }
 
  public function getLatitude() {
 
    return 49.09181;
  }
 
  public function getLongitude() {
 
    return 3.98507;
  }
 
  public function getAltitude() {
 
    return 26000;
  }
 
  public function getSpeed() {
 
    return 560;
  }
}

The Plane concrete class implements all ILocalizable interface's methods. The locate() method returns an array containing the latitude, the longitude, the altitude in feet and the ground speed in miles per hour.

How to reproduce and explain the bug?

Now everything is ready. Let's test it by executing the following index.php file.

<?php
 
// index.php
 
require_once __DIR__ .'/Plane.php';
 
$plane = new Plane();
echo $plane->getAltitude() ."\n";

Can you guess the result? Obviously, it should be the following one.

26000

Well, it's not... Execute the index.php in the command line as described below:

Hugo:BlogPost Hugo$ php index.php 
 
Fatal error: Can't inherit abstract function ILocalizable::getAltitude() 
(previously declared abstract in Aircraft) in 
/Users/Hugo/Sites/BlogPost/Plane.php on line 6
 
Call Stack:
    0.0005     629376   1. {main}() /Users/Hugo/Sites/BlogPost/index.php:0
    0.0007     641208   2. require_once('/Users/Hugo/Sites/BlogPost/Plane.php') 
    /Users/Hugo/Sites/BlogPost/index.php:3

The PHP interpreter throws a fatal error saying that we are trying to declare an already declared abstract method getAltitude() when implementing the ILocalizable interface.

The interpreter doesn't accept to implement an abstract method coming from an interface if it's already declared abstract in the parent class. There is a conflict as the interpreter thinks we are declaring the abstract method getAltitude() twice. Weird isn't?

This strange behavior has already been reported to the PHP core team twice:

According to the core team, it's not a bug. Well maybe, but what is the behavior with another object oriented programming language? The following section describes the same implementation in Java code.

The Java implementation

Let's implement our real world example with Java. But why Java? In fact, because the PHP's object oriented API is mainly inspired on the Java's object oriented model. As they are very close with their syntax and OO philosophy, we could be able to compare both PHP and Java results.

First, let's create the Aircraft abstract class in Java.

// Aircraft.java
abstract public class Aircraft {
 
  abstract public int getAltitude();
}

Then, the ILocalizable interface.

// ILocalizable.java
 
import java.util.HashMap;
 
public interface ILocalizable {
 
  public HashMap locate();
 
  public float getLatitude();
 
  public float getLongitude();
 
  public int getSpeed();
 
  public int getAltitude();
}

Then, the Plane concrete class.

// Plane.java
 
import java.util.HashMap;
 
public class Plane extends Aircraft implements ILocalizable {
 
  public int getAltitude() {
 
    return 26000;
  }
 
  public HashMap locate() {
 
    HashMap coords = new HashMap();
    coords.put("Latitude", this.getLatitude());
    coords.put("Longitude", this.getLongitude());
    coords.put("Altitude", this.getAltitude());
    coords.put("Speed", this.getSpeed());
 
    return coords;
  }
 
  public float getLatitude() {
 
    return (float) 49.09181;
  }
 
  public float getLongitude() {
 
    return (float) 3.98507;
  }
 
  public int getSpeed() {
 
    return 560;
  }
}

And finally, the Main class, which will be executed.

// Main.java
 
public class Main {
 
  public static void main(String[] args) {
 
    Plane plane = new Plane();
    System.out.println(plane.getAltitude());
  }
}

Compile and run the Java project and watch the printed result in the Java console tool.

26000

The result is the expected one. The IDE, NetBeans, and the compiler didn't throw any exception or warning for this implementation. In Java, there is no problem to declare an abstract method in an abstract class, and then redeclare it from an interface unlike PHP...

How to solve it?

The way to "solve" this problem is to not declare the getAltitude() method as abstract in the Aircraft class if the method has to be declared in an interface. Just declare the getAltitude() method and leave its body empty.

<?php
 
// Aircraft.php
abstract class Aircraft {
 
  public function getAltitude() {
 
  }
}

This way, the PHP interpreter doesn't generate any error and everything works as expected! Why does PHP generate an error if the method is declared as abstract whereas it doesn't with concrete methods...

Conclusion

So, finally this Java implementation proves that PHP is still buggy. As of today, the code has been tested with a PHP 5.3.1 version but I'm pretty sure it's not fixed in PHP 5.3.2 or PHP 5.3.3. This strange behavior has been reported five years ago and was never fixed...

Comments

Posted by Mikael RANDY - about 1 year ago

Why PHP must be work as the same way as Java ?

Okay, i'm not sure the current problem must be a "feature", but i dont understand why you compare it with Java implementation.

Posted by Hugo HAMON - about 1 year ago

I'm just kidding when I write it's a "feature" :)

I compare it with Java as they are similar. PHP takes a lot of inspiration from Java. The behavior I described seems to be buggy only with PHP according I read from the two tickets I linked in this article. I was wondering why PHP doesn't interpret this design as Java (or any other OO language) does.

I understand PHP is not Java but I think every OO developper would have expected the Java result instead of the leveraged fatal error.

Posted by Thibault - about 1 year ago

Thanks for this interesting reading.
I'd like to add that a better "solution" to this issue would be to make the unimplemented getAltitude method throw an exception like:

abstract class Aircraft {

public function getAltitude() {
throw new Exception('The Aircraft::getAltitude method must be implemented');
}
}

This way we come closer to the expected behavior of the abstract class.

Posted by mageekguy - about 1 year ago

And why your Aicraft class does not implement your Ilocalized interface ?
An aircraft is not mandatory localizable ?

Posted by Hugo Hamon - about 1 year ago

@mageekguy That was I said in the article. I don't want all my aircrafts to be localizable as UFOs or parachutes can't be located.

Posted by Romain Dorgueil - about 1 year ago

Hehe glad to learn you're now my official scribe :-). Seems a bit abstract tho, I'd not be very confident about taking a php-coded plane.

Posted by Victor Nicoller - about 1 year ago

So, the object-oriented parts of PHP are still not on the same level as modern object-oriented languages. What a surprise :-p

I guess what this tells us is that PHP does not have interfaces in the classic Object-Oriented sense of imposing compile-time constraints on class members (this class must have members "X", "Y", and "Z"). What we have here is more like a trick to allow multiple inheritance for type-checking purposes.

Implementing an "interface" concept as extending a class with only abstract member functions makes technical sense (it's easier to do it this way) but runs into the typical multiple inheritance issues of having two parents with two members with the same name.

Leave a comment

Comments are closed for this post