Skip to content
Accueil » [PHP] What are interfaces and what are they for?

[PHP] What are interfaces and what are they for?

You may have seen in some source code, or heard from a colleague of yours, the word “Interface”. Without knowing what it is, you think it must be a complex concept, and which is not necessarily useful in PHP?

Think again! When we talk about OOP (Object Oriented Programming, or OPP in English), interfaces are essential tools and very easy to learn.

Let’s see :

What is an interface and why use them?

Before getting to the heart of the matter – ie the source code – let’s define what an interface is.

An interface: what’s that?

On its own, it is useless. But other classes will be able to implement it. A framework is thus defined for all the classes which will “adhere” to this contract. In fact, we will know that no matter what they contain, they will have a common structure and methods: those of the interface.

We will define in an interface the list of methods that a class that implements it will have to override, so that the said contract is respected.

A few rules apply to interfaces:

  • All methods that are defined MUST be implemented in the class using the interface
  • All methods of an interface must be public
  • A class can implement multiple interfaces

What good will it be for me?

Here are some of the main reasons that, in my opinion, make interfaces very interesting to use:

  • When using an interface and when calling one of its methods, the calling class will only care about the interface of the object, not the implementations of its methods. You can therefore modify the implementations of all the classes using the interface without affecting its operation.
  • You will prioritize your code, having the possibility of having several classes, not having at first sight large thing to see, grouped together within the same implementation. You will therefore gain in stability: If you want to modify the interface, you can be sure that all the implementations will follow.
  • An interface allows you to access multiple inheritance, since a class can

How do the interfaces work?

A class of type interface will unsurprisingly be declared using the interface keyword. As for the class that implements it, it will have to use the keyword implement, then specify the interface to use:

<?php

interface InterfaceClass
{
    public function doSomething(): string;
}

class ChildClass implements InterfaceClass
{
    public function doSomething(): string
    {
        return 'Hello guys!';
    }
}

$childClas = new ChildClass();
echo $childClas->doSomething();

So far nothing rocket science, we have declared a method in an interface, which indicates that the return type is text. The child class implements it, and returns the correct type. Running the code above, we get:

Hello guys!

If you decide not to respect the rules of the interface contract, you will get an error. For example, if you don’t implement all the interface methods:

<?php

interface InterfaceClass
{
    public function doSomething(): string;
}

class ChildClass implements InterfaceClass
{
    public function doSomethingElse(): string
    {
        return 'Hello folks';
    }
}

$childClas = new ChildClass();
echo $childClas->doSomethingElse();

…you will then get the following exception:

Fatal error: Class ChildClass contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (InterfaceClass::doSomething)

Or, if you implement the correct method, but it does not exactly match the signature declared upstream, for example like this:

<?php

interface InterfaceClass
{
    public function doSomething(): string;
}

class ChildClass implements InterfaceClass
{
    public function doSomething(): int
    {
        return 1;
    }
}

$childClas = new ChildClass();
echo $childClas->doSomething();

… you will then have this exception:

Fatal error: Declaration of ChildClass::doSomething(): int must be compatible with InterfaceClass::doSomething(): string

All these rules ensure the integrity of your classes between them, so that all the classes that implement your interfaces are consistent and maintainable!

Use case ?

That’s all well and good, but so far we’ve seen the definition of interfaces, and how to use them via an example, but that’s not very telling. Is this concept really useful within a project?

Exactly! (unsurprisingly 😁) Let’s imagine that you want to create a logger, to manage your errors. One could imagine that the common system would collect the data, record it. However, each system has its specific way of recovering its error messages. You see what I mean ? Come on, I’ll give you the example

On commence par créer une interface Logger, avec 2 méthodes assez explicites :

<?php

// Those 2 methods must be implemented in each class that implements this interface
interface Logger
{
    public function getLogs(): array;

    public function saveLogs(array $logs): void;
}

Then, we will create a first class implementing this interface: WebLogger.

<?php

class WebLogger implements Logger
{
    public function getLogs(): array
    {
        // get logs file. Let's say it is stored as raw to this location
        $logLocation = '/var/www/log/apache2/error.log';
        $logs = file_get_contents($logLocation);

        // return wanted format
        return json_decode($logs);
    }

    public function saveLogs(array $logs): void
    {
        // let's loop into our logs and store them where we want, for instance a file
        $logLocation = '/etc/log/apache.log';
        file_put_contents(
            filename: $logLocation,
            data: json_encode($logs),
        );
    }
}

Then, let’s create a second class implementing this same interface: SqlLogger.

<?php

class SqlLogger implements Logger
{
    private PDO $pdo;

    public function __construct(PDO $pdo)
    {
        $this->pdo = $pdo;
    }

    public function getLogs(): array
    {
        // get logs file. Let's say it is stored as raw to this location
        $logLocation = '/var/www/log/mysql/error.log';
        $logs = file_get_contents($logLocation);

        // return wanted format
        return json_decode($logs);
    }

    public function saveLogs(array $logs): void
    {
        // let's loop into our logs and store them where we want, for instance a database table
        foreach ($logs as $log) {
            $sql = 'INSERT INTO web_logs(date, message) VALUES (:date, :message)';
            $query = $this->pdo->prepare($sql);
            $query->execute([
                'date' => new DateTimeImmutable(),
                'message' => $log
            ]);
        }
    }
}

These two classes work in quite different ways, yet are similar in construction. As they implement the same methods, we will be able to use them in exactly the same way:

<?php

use Logger;
use WebLogger;
use SqlLogger;

// initialize the PDO, that will be used to initialize the SqlLogger object
$pdo = new PDO(
    dsn: 'my-host',
    username: 'username',
    password: 'password',
);

$webLogger = new WebLogger();
$sqlLogger = new SqlLogger($pdo);

foreach ([$webLogger, $sqlLogger] as $logger) {
    // $logger will either be WebLogger or SqlLogger instance, so will always be Logger instance
    assert($logger instanceof Logger);

    // wo whatever the object, they implement the same class, then wa can call the same methods
    $logger->saveLogs(
        logs: $logger->getLogs()
    );
}

And now, the two instances of Logger will be well recognized as such, and will work exactly the same way.

Conclusion

I think you got the idea, you can implement as many Logger children as you want, and call the methods getLogs() and saveLogs() independently of their own implementation. We therefore completely ignore the operation, as long as the contract described by the interface is respected.

It is also possible to extend an abstract class using interfaces, to have access to even more functionality on class inheritance. The only limit will be your imagination!

Leave a Reply

Your email address will not be published. Required fields are marked *