What are interfaces, otherwise known as contracts in PHP?
Interfaces - often known as contracts - felt like a big leap in the early days of programming. But it's good to know - they're not that scary!
I remember first coming across interfaces (often referred to as Contracts, particularly in the Laravel world) and it really felt like the next level in programming. They may feel a little complex or too verbose - but I can assure you they're not, and are critical to any modern application. A quick Google and there's so many tutorials about interfaces out there, but I thought I'd quickly write this up as a segway to another article I'm writing.
Take Laravel, it makes heavy uses of interfaces throughout its framework to enforce some consistency - some agreement - on how particular elements and structures should look.
So let's use the Laravel term for a PHP interface - a contract. We all know contracts in the real world, they're something that you sign when you're getting a job or making a big purchase. That contract is an agreement, confirming that you agree to follow a certain set of rules and stipulations. And that's the exact same with programming interfaces/contracts. They are agreements - to the codebase - that you'll follow an agreed standard, and that standard is called an Interface
.
Take job roles as an example. Lets imagine that there's an office with 40 people. Each person has a job role, with their individual title, their annual salary and the roles that they are expected to carry out. In PHP, it might look a little something like below:
class WebDeveloper implements JobRole
{
public function title(): string
{
return 'Web Developer';
}
public function salaryInPence(): int
{
// insert the mega bucks here
return 2000000;
}
public function roles(): array
{
return [
'Write code',
'Make websites',
'Helping people with their IT problems because everyone thinks thats what you love to do',
];
}
}
You might be wondering about the implements JobRole
after the WebDeveloper
class definition. Lets take a closer look... This is our interface - often referred to as a contract - that agrees our WebDeveloper
class will contain certain methods. Lets have a look at that:
interface JobRole
{
public function title(): string;
public function salaryInPence(): int;
public function roles(): array;
}
Notice that all we've really done is replace class
for interface
and, unlike classes, our methods don't have any content. By writing out this agreement for our other classes to use, we're forcing our WebDeveloper
class to have at least the three methods we specified in the contract. This is key: when a class implements an interface, it's making a promise to fulfill the terms of the contract.
So I now know, with absolute certainty, that my little function below that accepts a JobRole
, will have the data it needs to make its calculation:
function friendlySalaryInYears(JobRole $role, int $years = 10): string
{
return sprintf(
'A %s will earn £%s over %d years',
$role->title(),
round(($role->salaryInPence() * 10)) / 100,
$years
);
}
And now the why
Great, so I've overengineered a single class, or have I? Imagine now that we have four different job roles, a DevOpsEngineer
a DatabaseEngineer
an AppDeveloper
and a FrontendDeveloper
(lame I know but thinking on the spot is hard). All of those can implement
the JobRole
interface, ensuring they all have at least the three methods and they can all be put into our salary calculator function.
That means we can safely throw it these roles into any function that accepts either an instance of JobRole
, and know with 100% certainty that'll it'll contain these three methods.
But further to this, there are a number of key benefits to interfaces, a few of which are noted below:
Enforcing standards
Don't underestimate the power of enforcing standards and procedures in your projects. Consistency is king to fast and efficient development, and reviewing code further down the line. Sometimes, a little extra code will save a huge headache further down the road.
Extensibility
So, to your dismay, another made up job role comes along and your boss asks you to add it to the system. Using the JobRole
interface, along with all the other necessary places you won't (shouldn't!) need to modify much, and you can sleep easy at night knowing your new role abides by the standards dictated by your application.
Thinking more broadly, imagine you're building an e-commerce platform and you want to support different payment gateways (Stripe, PayPal, etc.). You could define an interface like this:
interface PaymentGateway
{
public function processPayment(float $amount, array $customerData): bool;
public function refundPayment(float $amount, string $transactionId): bool;
}
Now, each payment provider can create a class that implements this PaymentGateway
interface. This makes your e-commerce platform extensible because you can easily add new payment gateways in the future without modifying the core code. Any class that implements this interface adheres to the "contract" - it guarantees that the processPayment
and refundPayment
methods will be available.
Testability
Your unit tests (and your future self) will thank you for using interfaces. You can quickly and easily mock up classes that inherit the JobRole
for testing purposes, meaning they can be easily mocked. Looking at a real world example, if your function is looking for a Filesystem
contract you can easily swap out your real Filesystem
class that writes to disk for one that will simply write to memory.
Reduced coupling
There's nothing worse than a class thats dependent on another class that needs another... With interfaces, you are actively promoting loose coupling and separation of concerns. Your functions elsewhere don't care what job role they receive, just that its a JobRole
instance.
Lets be real
I always like to leave you with a real-world example. You've just getting started on your next big thing with a blank Laravel project. Take a look at your User
model that ships by default with every new Laravel installation. Alas, it extends Laravel's base User
model which in turn implements a number of contracts:
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
Diving into the Authenticatable
interface we'll see a load of empty methods relating to Laravel's authentication mechanisms. So this means our User
model must implement the methods defined here.
The real magic here is that Laravel's core authentication logic doesn't care about your User
model, all it cares is that you give it something that inherits the Authenticatable
contract!
I started this article by writing quickly write this
. 1 hour and 40 minutes later I realised there was a lot to get out about interfaces! If you enjoyed this post, please subscribing for more like this