DEV Community

loading...
Cover image for Interfaces vs Abstract Classes in PHP

Interfaces vs Abstract Classes in PHP

Ash Allen
I am Laravel web developer that specialises in building websites and systems for small businesses
Originally published at ashallendesign.co.uk ・7 min read

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;
}
Enter fullscreen mode Exit fullscreen mode

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...
    }
}
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 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...
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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',
        ];  
    }
}
Enter fullscreen mode Exit fullscreen mode
// This will not work:
$house = new House();

// This will work:
$house = new MyHouse();
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, let's say that we have 3 different classes that we want to be able to buld and pass to the buildHome() method:

  1. class MyHouse implements HomeInterface extends House
  2. class MyCaravan implements HomeInterface
  3. 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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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

Discussion (0)