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.

Intende…



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;
}

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';
    }
}

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...
    }
}

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...
    }
}

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;
}

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.'];
    }
}
class UsersReport implements DownloadableReport
{
    public function getName()
    {
        return ['Users Report'];
    }

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

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.'];
    }
}

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...
    }

}

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 adapter 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!


Print Share Comment Cite Upload Translate
APA
Ash Allen | Sciencx (2024-03-29T09:21:20+00:00) » Using Interfaces to Write Better PHP Code. Retrieved from https://www.scien.cx/2021/06/08/using-interfaces-to-write-better-php-code/.
MLA
" » Using Interfaces to Write Better PHP Code." Ash Allen | Sciencx - Tuesday June 8, 2021, https://www.scien.cx/2021/06/08/using-interfaces-to-write-better-php-code/
HARVARD
Ash Allen | Sciencx Tuesday June 8, 2021 » Using Interfaces to Write Better PHP Code., viewed 2024-03-29T09:21:20+00:00,<https://www.scien.cx/2021/06/08/using-interfaces-to-write-better-php-code/>
VANCOUVER
Ash Allen | Sciencx - » Using Interfaces to Write Better PHP Code. [Internet]. [Accessed 2024-03-29T09:21:20+00:00]. Available from: https://www.scien.cx/2021/06/08/using-interfaces-to-write-better-php-code/
CHICAGO
" » Using Interfaces to Write Better PHP Code." Ash Allen | Sciencx - Accessed 2024-03-29T09:21:20+00:00. https://www.scien.cx/2021/06/08/using-interfaces-to-write-better-php-code/
IEEE
" » Using Interfaces to Write Better PHP Code." Ash Allen | Sciencx [Online]. Available: https://www.scien.cx/2021/06/08/using-interfaces-to-write-better-php-code/. [Accessed: 2024-03-29T09:21:20+00:00]
rf:citation
» Using Interfaces to Write Better PHP Code | Ash Allen | Sciencx | https://www.scien.cx/2021/06/08/using-interfaces-to-write-better-php-code/ | 2024-03-29T09:21:20+00:00
https://github.com/addpipe/simple-recorderjs-demo