tagged on a cowInterfaces are not class templates

Interfaces are an important part of OOP, in PHP as in other OOP languages. According to the manual, “Object interfaces allow you to create code which specifies which methods a class must implement, without having to define how these methods are implemented.”

<?php

interface i {
    // interfaces may provide constants
  const I = 1;

    // definition of a method, no body
  function foo() : void;
}

?>

Besides interfaces, PHP also supports abstract classes, which may define both abstract methods and complete or concrete methods. The abstract class can’t be instantiated as long as there are abstract methods. That way, they provide a template for any child class, ensuring that those methods are available. They may also include properties definitions and full methods, unlike interfaces. They need to be extended, not implemented, which is a different modus operandi than interfaces. Yet, abstract classes and interfaces are pretty much interchangeable.

Just fill the holes

The templating nature of interfaces is checked by PHP at linting time. This is the most obvious to any PHP coder, since PHP will refuse to lint or execute when the interface-template is not correctly filled. Indeed, the following code yield an error :

<?php

interface i {
    // definition of a method, no body
  function foo() : void;
}

class x implements i {}

?>

Error : PHP Fatal error: Class x contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (i::foo)

Once the method has been created, aka it has a body, or when it is also marked as abstract, aka, it has no body, then PHP will move on with execution.

interfaces usage

At that point, we can notice two interesting side effects of the definitions :

  • An interface defines methods, from none to a lot
  • An interface has little scope after initial linting

Let’s review these points in order.

Constant only interfaces

An interface made only of constants is an interface without methods. To avoid defining global constants, it happens that constant-only interfaces are created. That makes theses constant easy to import in any class, or also, act as global constants, since the constants are public. This is a niche use of non-method interfaces.

<?php

interface i {
    const Y = 1;
    const Z = 2;
}

class x implements i {
  function foo() {
      echo i::Y .' '.x::Z;
  }
}

?>

No method interfaces

With or without constants, interfaces may offer zero methods, while being a valid interface.

<?php

interface i { }

class x extends root implements i {}
class y extends root implements i {}
class z extends root implements i {}
class a extends root {}

?>

This way, the interface acts merely as a class tag, or an alias or even some kind of weak attribute.

When several classes are tagged with i, it is possible to filter on that arbitrary group of classes with the i interface. In the example above, x, y and z have been grouped, for some reason, and ahas been singled out. Class hierarchy would not allow that grouping : a should not be in that group, yet it extends the root class.

<?php

// This would include a
function foo(root $arg) : bool {} 

// union type works, hopefully they are updated when the group is updated
function goo(x|y|z $arg) : bool {} 

// add and remove classes from their definition
function hoo(i $arg) : bool {} 

?>

Obviously, it may be hard to use completely independant classes together : such classes require different processing methods. Yet, when those classes belong to a common hierarchy, or implements yet another interface, using an empty interface acts as a refinement. Having the special interface extends the refined one may be more secure, until the code can use the intersectional types of PHP 8.1.

<?php

# PHP 8.0 and older
interface i extends ArrayAccess {}

class x implements i {}
class y implements i {}
class z implements i {}
class a implements ArrayAccess {}

function foo(i $arg) {}

#PHP 8.1 and more recent
interface j {}

class x2 implements i, ArrayAccess {}
class y2 implements i, ArrayAccess {}
class z2 implements i, ArrayAccess {}
class a2 implements    ArrayAccess {}

function foo(ArrayAcces &amp; i $arg) {}

?>

No typehint interfaces

Now, back to the second point : when an interface has methods, then it acts as a template for the implementing class. PHP does check the presence of the actual methods, then proceeds to execution.

And, one can only notice that once the methods are implemented, the interface is itself is actually useless.

<?php

interface i {
    function foo() ; 
}

class x implements i {
    function foo() { return 1; }
}

class y {
    function foo() { return 1; }
}

?>

Here, x implements i, and so does y. But y does not wear the implements keyword.

implements methods, skips interface

In fact, it is possible that some classes satisfy the constraints of an arbitrary interface, but do not use the implements keyword. We should check existing classes for possible missed implementations. In the example above, class y is missing the implements i keyword.

This is the actual goal of the Exakat’s Forgotten Interfaces rule, which spots classes that have the necessary methods to implement available interfaces, but do not use the implementskeyword.

The ultimate decision of using an interface or not is actually a conception level one. The notion of grouping or tagging classes with interfaces, as we saw above, may be arbitrary. And also, it might be a simple forgotten keyword.

Non-useless interfaces

So, if the interface is not a template, how does it get useful beyond the linting phase? Just as we saw for empty interfaces : by using them with typehints and instanceof. Both of those features check objects for a class or an interface.

<?php

interface i {
    function foo() ; 
}

function foo(i $arg1, $arg2) {
  if ($arg2 instanceof i) {
      $args2-&gt;add($arg1);
  } else {
    $args2-&gt;addInt(intval($arg2));
  }
}

?>

As soon as the interface is used in a typehint or instanceof, it cannot be removed anymore without impacting the code. The interface is now needed at execution time, to branch execution or filter data. Such interfaces are not useless anymore.

Conclusion

When using interfaces, remember that they are mere templates until they are actually used in a typehint. Otherwise, they may be dropped without impacting the code. They may also be end up being missing in classes that implements their methods, but not the interface.

In fact, usage and definitions may also help programmers define new interfaces : when two (or more) classes define two (or more) methods with the same signature, they could be considered eligible for wearing a common interface. This will help typehinting strategy.

In the following example, several interfaces may be extracted from this list of classes : one with function a(); only for 3 classes, one with function a(); function b(); for 2 classes, one with function a(); function c(); for 2 classes,

<?php

class x {
    function a() {}
    function b() {}
    function c() {}
}  

class y {
    function a() {}
    function c() {}
}  

class y {
    function a() {}
    function b() {}
}  

?>

The combinaisons will soon make the challenge of naming all those interfaces very hard. This is a typical example of organically growing code, where new interfaces are infered from the code, rather than from the conception.

Yet, it show the evolution of the code and that may be crucial to any auditor.