Support the ongoing development of Laravel.io →

Interfaces vs Abstract Classes in PHP

21 Aug, 2021 8 min read

Photo by Cytonn Photography on Unsplash

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:

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

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:

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

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 article, feel free to sign up for my newsletter.

For any of my 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.

Keep on building awesome stuff! 🚀

Last updated 2 weeks ago.

joedixon, driesvints, manishpeter, tim-frensch, ash-jc-allen, kenny201, ajmeireles, mikenk2010, ovillafuerte94, rsmsp and more liked this article

11
Like this article? Let the author know and give them a clap!
ash-jc-allen (Ash Allen) I'm a freelance Laravel web developer from Preston, UK. I maintain the Ash Allen Design blog and get to work on loads of cool and exciting projects 🚀

Other articles you might like

November 18th 2024

Laravel Custom Query Builders Over Scopes

Hello 👋 Alright, let's talk about Query Scopes. They're awesome, they make queries much easier to r...

Read article
November 19th 2024

Access Laravel before and after running Pest tests

How to access the Laravel ecosystem by simulating the beforeAll and afterAll methods in a Pest test....

Read article
November 11th 2024

🍣 Sushi — Your Eloquent model driver for other data sources

In Laravel projects, we usually store data in databases, create tables, and run migrations. But not...

Read article

We'd like to thank these amazing companies for supporting us

Your logo here?

Laravel.io

The Laravel portal for problem solving, knowledge sharing and community building.

© 2024 Laravel.io - All rights reserved.