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:
1interface DownloadableReport2{3 public function getName(): string;4 5 public function getHeaders(): array;6 7 public function getData(): array;8}
According to php.net, interfaces serve two main purposes:
- 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.
- 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.
Benefits of Using Interfaces in PHP
Using PHP interfaces can have a number of benefits on the quality of your code. Let's take a look at some of the benefits of using interfaces in PHP:
Decouple Your Code
One of the main benefits of using interfaces in PHP is that it helps to decouple your code. This means that your code is less dependent on specific classes and objects, and instead, it's more dependent on the interfaces that they implement. This makes your code much more maintainable and flexible because you can easily swap out the classes that implement the interface without having to worry about breaking your code.
If we expect an interface to be passed to a function and we want to act on it, we don't need to care about the class that implements the interface. We're not bothered what the class is called, what methods it has, or how it works. We just care that it implements the interface and that it has the methods that we expect it to have.
Acts as Code-as-Documentation
As well as using interfaces to improve the actual code, I like PHP 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.
Encourages the Use of Dependency Injection
Another benefit of using interfaces is that it encourages the use of dependency injection in your code. Dependency injection is an incredibly powerful tool and can be useful for things like implementing the Manager pattern and the Strategy pattern. It can also be used to make your code more testable.
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:
1class BlogReport2{3 public function getName(): string4 {5 return 'Blog report';6 }7}
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:
1class ReportDownloadService2{3 public function downloadPDF(BlogReport $report)4 {5 $name = $report->getName();6 7 // Download the file here...8 }9}
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:
1class ReportDownloadService 2{ 3 public function downloadBlogReportPDF(BlogReport $report) 4 { 5 $name = $report->getName(); 6 7 // Download the file here... 8 } 9 10 public function downloadUsersReportPDF(UsersReport $report)11 {12 $name = $report->getName();13 14 // Download the file here...15 }16}
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:
1interface DownloadableReport2{3 public function getName(): string;4 5 public function getHeaders(): array;6 7 public function getData(): array;8}
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!
1class BlogReport implements DownloadableReport 2{ 3 public function getName(): string 4 { 5 return 'Blog report'; 6 } 7 8 public function getHeaders(): array 9 {10 return ['The headers go here'];11 }12 13 public function getData(): array14 {15 return ['The data for the report is here.'];16 }17}
1class UsersReport implements DownloadableReport 2{ 3 public function getName() 4 { 5 return ['Users Report']; 6 } 7 8 public function getData(): string 9 {10 return 'The data for the report is here.';11 }12}
If we were to try and run our code, we would get errors for the following reasons:
- The
getHeaders()
method is missing. - The
getName()
method doesn't include the return type that is defined in the interface's method signature. - 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:
1class UsersReport implements DownloadableReport 2{ 3 public function getName(): string 4 { 5 return 'Users Report'; 6 } 7 8 public function getHeaders(): array 9 {10 return [];11 }12 13 public function getData(): array14 {15 return ['The data for the report is here.'];16 }17}
Now that we have both of our report classes implementing the same interface, we can update our ReportDownloadService
like so:
1class ReportDownloadService 2{ 3 public function downloadReportPDF(DownloadableReport $report) 4 { 5 $name = $report->getName(); 6 7 // Download the file here... 8 } 9 10}
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.
Constants in Interfaces in PHP
As we've mentioned in this article, interfaces can contain constants. These constants can be used by classes that implement the interface. For example, if we were to imagine we had an interface with a constant called NAME
, we could use it in a class that implements the interface:
1interface Renderable 2{ 3 public const NAME = 'Renderable'; 4} 5 6class MyAwesomeClass implements Renderable 7{ 8 public function getName() 9 {10 return self::NAME;11 }12}13 14echo MyAwesomeClass::NAME; // Renderable
Overriding Constants in Interfaces in PHP
Prior to PHP 8.1, you could not override a constant defined by an interface in a class. However, as of PHP 8.1, the constants in an interface can be overridden in PHP. For example, if we were to imagine we had an interface with a constant called NAME
, we could override it in a class that implements the interface:
1interface Renderable 2{ 3 public const NAME = 'Renderable'; 4} 5 6class MyAwesomeClass implements Renderable 7{ 8 // ... 9}10 11class MyCoolClass implements Renderable12{13 public const NAME = 'My Cool Class';14}15 16echo MyAwesomeClass::NAME; // Renderable17echo MyCoolClass::NAME; // My Cool Class
As we can see in the example above, the MyAwesomeClass
class doesn't define the NAME
constant, so it uses the value defined in the Renderable
interface. However, the MyCoolClass
class does define the NAME
constant, so it uses the value defined in the class instead.
Using Typed Constants in Interfaces in PHP
As of PHP 8.3, it's possible to declare types for constants in classes and interfaces. For instance, if we stick with our previous example, we could declare the NAME
constant as a string
:
1interface Renderable2{3 public const string NAME = 'Renderable';4}
As a result of doing this, it enforces that any child interfaces extending this interface, or any classes implementing this interface, must also define the constant with the same type. For example, the following would fail because the NAME
constant is not defined as a string
and is defined as an int
instead:
1class MyAwesomeClass implements Renderable2{3 public const int NAME = 123;4}
Attempting to run the above code would result in the following error:
1Fatal error: Type of MyAwesomeClass::NAME must be compatible with Renderable::NAME of type string in /in/PgdZ0 on line 5
FAQ
Can a Class Implement Multiple Interfaces in PHP?
Yes! A class can implement multiple interfaces in PHP. For example, if we were to imagine we had two interfaces (Renderable
, and Reportable
), we could have a class that implements both of them:
1class Report implements Renderable, Reportable2{3 // ...4}
In this case, the Report
class would need to implement all the methods defined in both the Renderable
and Reportable
interfaces.
What is the Difference Between an Abstract Class and an Interface in PHP?
That's a great question! I've actually written a separate article that discusses the differences between the two: Interfaces vs Abstract Classes in PHP.
Can an Interface Extend Another Interface in PHP?
Yes! An interface can extend another interface in PHP. For example, if we were to imagine we had two interfaces (Renderable
, and Reportable
), we could have a third interface that extends both of them:
1interface Renderable 2{ 3 public function render(): void; 4} 5 6interface Reportable 7{ 8 public function getReport(): void; 9}10 11interface Report extends Renderable, Reportable12{13 public function generate(): void;14}
We could then create a class that implements these interfaces and requires all the methods defined in them:
1class MyAwesomeReport implements Report 2{ 3 public function render() 4 { 5 // ... 6 } 7 8 public function getReport() 9 {10 // ...11 }12 13 public function generate()14 {15 // ...16 }17}
Can an Interface Extend a Class in PHP?
No! An interface cannot extend a class in PHP. It can only extend another interface.
What's the difference between a Contract in Laravel and an Interface in PHP?
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 in this context, 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. However, a contract can also be an abstract class because it can define abstract methods that a child class must implement. But the majority of the time, when you hear the word "contract", it's generally referring to an interface.
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 strategy pattern in Laravel using interfaces. If you're interested in this, feel free to subscribe to my newsletter below 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 strategy pattern in Laravel has now been published. Check it out here!