DEV Community

Cover image for Using Interfaces to Write Better PHP Code
Ash Allen
Ash Allen

Posted on • Edited on • Originally published at ashallendesign.co.uk

Using Interfaces to Write Better PHP Code

Introduction

In programming, it's important to make sure that your code is readable, maintainable, extendable and easily testable. One of the ways that we can improve all of these factors in our code is by using interfaces.

Intended Audience

This article is aimed at developers who have a basic understanding of OOP (object oriented programming) concepts and using inheritance in PHP. If you know how to use inheritance in your PHP code, this article should hopefully be understandable.

What Are Interfaces?

In basic terms, interfaces are just descriptions of what a class should do. They can be used to ensure that any class implementing the interface will include each public method that is defined inside it.

Interfaces can be:

  • Used to define public methods for a class.
  • Used to define constants for a class.

Interfaces cannot be:

  • Instantiated on their own.
  • Used to define private or protected methods for a class.
  • Used to define properties for a class.

Interfaces are used to define the public methods that a class should include. It's important to remember that only the method signatures are defined and that they don't include the method body (like you would typically see in a method in a class). This is because the interfaces are only used to define the communication between objects, rather than defining the communication and behaviour like in a class. To give this a bit of context, this example shows an example interface that defines several public methods:

interface DownloadableReport
{
    public function getName(): string;

    public function getHeaders(): array;

    public function getData(): array;
}
Enter fullscreen mode Exit fullscreen mode

According to php.net, interfaces serve two main purposes:

  1. To allow developers to create objects of different classes that may be used interchangeably because they implement the same interface or interfaces. A common example is multiple database access services, multiple payment gateways, or different caching strategies. Different implementations may be swapped out without requiring any changes to the code that uses them.
  2. To allow a function or method to accept and operate on a parameter that conforms to an interface, while not caring what else the object may do or how it is implemented. These interfaces are often named like Iterable, Cacheable, Renderable, or so on to describe the significance of the behavior.

Using Interfaces in PHP

Interfaces can be an invaluable part of OOP (object oriented programming) codebases. They allow us to decouple our code and improve extendability. To give a example of this, let's take a look at this class below:

class BlogReport
{
    public function getName(): string
    {
        return 'Blog report';
    }
}
Enter fullscreen mode Exit fullscreen mode

As you can see, we have defined a class with a method that returns a string. By doing this, we have defined the behaviour of the method so we can see how getName() is building up the string that is returned. However, let's say that we call this method in our code inside another class. The other class won't care how the string was built up, it will just care that it was returned. For example, let's look at how we could call this method in another class:

class ReportDownloadService
{
    public function downloadPDF(BlogReport $report)
    {
        $name = $report->getName();

        // Download the file here...
    }
}
Enter fullscreen mode Exit fullscreen mode

Although the code above works, let's imagine that we now wanted to add the functionality to download a users report that's wrapped inside a UsersReport class. Of couse, we can't use the existing method in our ReportDownloadService because we have enforced that only a BlogReport class can be passed. So, we'll have to rename the existing method and then add a new method, like below:

class ReportDownloadService
{
    public function downloadBlogReportPDF(BlogReport $report)
    {
        $name = $report->getName();

        // Download the file here...
    }

    public function downloadUsersReportPDF(UsersReport $report)
    {
        $name = $report->getName();

        // Download the file here...
    }
}
Enter fullscreen mode Exit fullscreen mode

Although you can't actually see it, let's assume that the rest of the methods in the class above use identical code to build the download. We could lift the shared code into methods but we will still likely have some shared code. As well as this, we're going to have multiple points of entry into the class that runs near-identical code. This can potentially lead to extra work in the future when trying to extend the code or add tests.

For example, let's imagine that we create a new AnalyticsReport; we'd now need to add a new downloadAnalyticsReportPDF() method to the class. You can likely see how this file could start growing quickly. This could be a perfect place to use an interface!

Let's start by creating one; we'll call it DownloadableReport and define it like so:

interface DownloadableReport
{
    public function getName(): string;

    public function getHeaders(): array;

    public function getData(): array;
}
Enter fullscreen mode Exit fullscreen mode

We can now update the BlogReport and UsersReport to implement the DownloadableReport interface like seen in the example below. But please note, I have purposely written the code for the UsersReport wrong so that I can demonstrate something!

class BlogReport implements DownloadableReport
{
    public function getName(): string
    {
        return 'Blog report';
    }

    public function getHeaders(): array
    {
        return ['The headers go here'];
    }

    public function getData(): array
    {
        return ['The data for the report is here.'];
    }
}
Enter fullscreen mode Exit fullscreen mode
class UsersReport implements DownloadableReport
{
    public function getName()
    {
        return ['Users Report'];
    }

    public function getData(): string
    {
        return 'The data for the report is here.';
    }
}
Enter fullscreen mode Exit fullscreen mode

If we were to try and run our code, we would get errors for the following reasons:

  1. The getHeaders() method is missing.
  2. The getName() method doesn't include the return type that is defined in the interface's method signature.
  3. The getData() method defines a return type, but it isn't the same as the one defined in the interface's method signature.

So, to update the UsersReport so that it correctly implements the DownloadableReport interface, we could change it to the following:

class UsersReport implements DownloadableReport
{
    public function getName(): string
    {
        return 'Users Report';
    }

    public function getHeaders(): array
    {
       return [];
    }

    public function getData(): array
    {
        return ['The data for the report is here.'];
    }
}
Enter fullscreen mode Exit fullscreen mode

Now that we have both of our report classes implementing the same interface, we can update our ReportDownloadService like so:

class ReportDownloadService
{
    public function downloadReportPDF(DownloadableReport $report)
    {
        $name = $report->getName();

        // Download the file here...
    }

}
Enter fullscreen mode Exit fullscreen mode

We could now pass in a UsersReport or BlogReport object into the downloadReportPDF() method without any errors. This is because we now know that the necessary methods needed on the report classes exist and return data in the type that we expect.

As a result of passing in an interface to the method rather than a class, this has allowed us to loosely-couple the ReportDownloadService and the report classes based on what the methods do, rather than how they do it.

If we wanted to create a new AnalyticsReport, we could make it implement the same interface and then this would allow us to pass the report object into the same downloadReportPDF() method without needing to add any new methods. This can be particularly useful if you are building your own package or framework and want to give the developer the ability to create their own class. You can simply tell them which interface to implement and they can then create their own new class. For example, in Laravel, you can create your own custom cache driver class by implementing the Illuminate\Contracts\Cache\Store interface.

As well as using interfaces to improve the actual code, I tend to like interfaces because they act as code-as-documentation. For example, if I'm trying to figure out what a class can and can't do, I tend to look at the interface first before a class that is using it. It tells you all of the methods that be called without me needing to care too much about how the methods are running under the hood.

It's worth noting for any of my Laravel developer readers that you'll quite often see the terms "contract" and "interface" used interchangeably. According to the Laravel documentation, "Laravel's contracts are a set of interfaces that define the core services provided by the framework". So, it's important to remember that a contract is an interface, but an interface isn't necessarily a contract. Usually, a contract is just an interface that is provided by the framework. For more information on using the contracts, I'd recommend giving the documentation a read as I think it does a good job of breaking down what they are, how to use them and when to use them.

Conclusion

Hopefully, through reading this article, it should have given you a brief overview of what interfaces are, how they can be used in PHP, and the benefits of using them.

For any of my Laravel developer readers, I will be writing a new blog post for next week that will show you how to use the bridge pattern in Laravel using interfaces. If you're interested in this, feel free to subscribe to my newsletter on my website so that you can get notified when I release it.

I'd love to hear in the comments if this article has helped with your understanding of interfaces. Keep on building awesome stuff! πŸš€

Massive thanks for Aditya Kadam, Jae Toole and Hannah Tinkler for proof-reading this post and helping me improve it!

UPDATE: The follow-up post about how to use the bridge pattern in Laravel has now been published. Check it out here!

Top comments (4)

Collapse
 
dev_nishant28 profile image
dev_nishant28

I have always been haunted by concepts like Interface, Abstract Classes, Design Patterns in oops cause even if I find the examples on some website, it's always has been difficult to know how to implement. Until I read this. Kudos to u for explaining this in such an easy way. I would request you to write an article for abstract classes & when to use what ( Interface or Abstract Class ).
ThanksπŸ™

Collapse
 
ashallendesign profile image
Ash Allen

Hey! Thanks, I appreciate you saying that, it means a lot. I'm really glad that the article's helped you out!

I did consider writing a bit more in the article about when to use abstract classes vs interfaces, but didn't know if it would be too much information at once. I think I'll be writing another article soon about interfaces and abstraction, so I'll make sure to include it then. But to be honest, I think ShinProg has given a perfect explanation! πŸ˜ƒ

Collapse
 
shinprog profile image
ShinProg (Logan Tann) • Edited

Abstraction is one of the hardest chapter of my 1st year IT studies. If you're learning self taught and only in php, it's more difficult. Still, getting the right architecture relies on understanding the basics and a lot of practice of course.

About when to use abstract and interface, there is the common pattern during my studies where you create first the interface and use it like explained in this article, then the abstract class that implements the interface (if you feel like you need it though). Your classes can extends the abstract class, whereas others ppl working in your project that might not want to hinerit from your abstract class can just rather implements the interface.

Because in java (and I think it's the case with php) you can inherit from only one class. If someone wants to add a feature he would be forced to extends the abstract class, and that would not be the case using an interface + abstract that implements it (bc he can choose to just implements the interface + extends another class and the code will run just fine)

Collapse
 
dev_nishant28 profile image
dev_nishant28

Hey, thanks for the brief explaination. Can u suggest any particular Java OOPS book which illustrates all these concepts with examples & practices. Will really appreciate it!