Introduction
I recently published a blog post that talked about how to write better PHP code using interfaces. It covered the basics on what an interface was, what they could do and how you could use them to make your PHP code more extendable and maintainable.
One of the main comments that I got on the post was from developers who wanted to know "when would I use an interface instead of an abstract class?". So I thought I'd write this post to explain the differences between abstract classes and interfaces in PHP and give a brief overview of when you should use either of them.
What Are Interfaces?
In basic terms, an interface should describe how a class implementing them will be built, they're like a blueprint describing the public methods and constants they should include.
Interfaces can be:
- Used to define public method signatures 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 an interface is always meant to be implemented by a class, so this is where you define just the signature of a method, for example:
interface HomeInterface
{
const MATERIAL = 'Brick';
public function openDoor(): void;
public function getRooms(): array;
public function hasGarden(): bool;
}
and not something like this:
interface HomeInterface
{
public string $material = 'Brick';
public function openDoor(): void
{
// Open the door here...
}
public function getRooms(): array
{
// Get the room data here...
}
public function hasGarden(): bool
{
// Determine if the home has a garden...
}
}
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.
Using our interface above and sticking with the house analogy, we could create different classes that implement HomeInterface
, such as House
, Flat
or Caravan
. By using the interface we can be sure that our class contains the 3 necessary methods and all use the correct method signature. For example, we could have a House
class that looks like this:
class House implements HomeInterface
{
public function openDoor(): void
{
// Open the door here...
}
public function getRooms(): array
{
// Get the room data here...
}
public function hasGarden(): bool
{
// Determine if the home has a garden...
}
}
What Are Abstract Classes?
Abstract classes are very similar to interfaces; they're not designed to be instantiated on their own and provide a base line implementation for you to extend from.
Taking our example above of homes, if an interface is your blueprint then an abstract class is your show room model. It works, and it's a great example of a home but you still need to furnish and decorate it to make it your own.
Abstract classes can be:
- Used to define method signatures for a class using "abstract" methods (similar to interfaces).
- Used to define methods.
- Used to define constants for a class.- Used to define properties for a class.
- Extended by a child class.
Abstract classes cannot be:
-Instantiated on their own.
To get an idea of what this means, let's look at an example abstract class:
abstract class House
{
const MATERIAL = 'Brick';
abstract public function openDoor(): void;
public function getRooms(): array
{
return [
'Bedroom',
'Bathroom',
'Living Room',
'Kitchen',
];
}
public function hasGarden(): bool
{
return true;
}
}
Our House
class is abstract which means that we can't instantiate one of these directly. To be able to use it, we'd need to inherit from it. For example, let's create a MyHouse
class that extends our House
abstract class:
class MyHouse extends House
{
public function openDoor(): void
{
// Open the door...
}
public function getRooms(): array
{
return [
'Bedroom One',
'Bedroom Two',
'Bathroom',
'Living Room',
'Kitchen',
];
}
}
// This will not work:
$house = new House();
// This will work:
$house = new MyHouse();
You might have noticed that in the House
class that we have declared an abstract public method called openDoor()
. This is basically allowing us to define a method's signature that a child class should include in a similar way as we would with an interface. This is really handy if you want to share some functionality with your child classes but also enforce that they include their own implementations of some methods.
In this particular instance, a child class could override the getRooms()
and hasGarden()
methods as usual, but wouldn't be required to include them. To show this, we've overridden the getRooms()
method to show how we could change it's behaviour in the child class.
How to Decide Which to Use
It's really going to depend on what your goal is. To keep to our house analogy, if you're creating blueprints that can be used later to design different types of houses then you need an interface.
If you've built a house and now you need to make copies with customization then you need an abstract class.
Let me give you some examples:
When to Use an Interface
To help us understand when to use an interface, let's look at an example. Let's say that we have a ConstructionCompany
class that includes a buildHome()
method that looks like this:
class ConstructionCompany
{
public function buildHome($home)
{
// Build the home here...
return $home;
}
}
Now, let's say that we have 3 different classes that we want to be able to buld and pass to the buildHome()
method:
class MyHouse implements HomeInterface extends House
class MyCaravan implements HomeInterface
class MyFlat implements HomeInterface
As we can see, the MyHouse
class extends the House
abstract class; and this makes total sense from a conceptual point of view because the house is a house. However, it wouldn't make sense for the MyCaravan
or MyFlat
class to extend from the abstract class because neither of them are houses.
So, because our construction company is able to build houses, caravans and flats, this rules out us type hinting the $home
parameter in the buildHome()
method to be an instance of House
.
However, this would be a perfect place for us to type hint our method to only allow classes that implement the HomeInterface
to be passed. As an example, we could update the method to be:
class ConstructionCompany
{
public function buildHome(HomeInterface $home)
{
// Build the home here...
return $home;
}
}
As a result of doing this, we can be sure that whether we pass a house, caravan or flat, that our ConstructionCompany
class will have the information it needs because the home object passed in will always contain the necessary methods that we need.
You might have also thought to yourself "why don't we just create a Home
abstract class instead of an interface?". However, it's important to remember that PHP only supports single inheritance and that a class can't extend more than one parent class. So, this would make it pretty difficult if you ever wanted to extend one of your classes in the future.
When to Use an Abstract Class
Let's take a scenario similar to our example above. Let's imagine that we have a HouseConstructionCompany
that's similar to our ConstructionCompany
. But, in this example, we'll assume that the HouseConstructionCompany
only build houses and nothing else.
Because we know that we only need to be able to build houses, we could type hint our method to only accept classes that extend the House
abstract class. This can be really useful because we can always be sure that we're not passing any other types of homes to the method that the construction company doesn't build. For example:
class HouseConstructionCompany
{
public function buildHouse(House $house)
{
// Build the house here...
return $house;
}
}
Conclusion
Hopefully, this post will have given you an insight into the differences between interfaces and abstract classes in PHP. It should have also given you a brief overview of the different scenarios when you should use either one of them.
If this post helped you out, I'd love to hear about it. Likewise, if you have any feedback to improve this post, I'd also love to hear that too.
If you're interested in getting updated each time I publish a new post, feel free to sign up for my newsletter here.
For any of my Laravel developer readers who are looking for any further reading around interfaces, you can read about how you can use interfaces to use the strategy pattern in Laravel here.
A massive thanks to James Mahy, Aditya Kadam and Andrew Palfrey for proofreading this article and giving me feedback on it! I'd recommend checking out a cool social network that James is building: SoSa, a fun, friendly and privacy first community that makes it easy and fun to socialise online!
Also a huge thanks to Jess Pickup for creating another cool blog post image as usual!
Keep on building awesome stuff! 🚀
Top comments (0)